From: Tobias Diekershoff Date: Thu, 7 Apr 2016 19:06:32 +0000 (+0200) Subject: Merge pull request #2441 from rabuzarus/0704_doxygen_forum X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=dfa5183774e24fe2b1a355c9427372d4cd55ea70;hp=d5bf386cf74fdb928ffe022344d2544363a04bf3;p=friendica.git Merge pull request #2441 from rabuzarus/0704_doxygen_forum Update ForumManager.php --- diff --git a/boot.php b/boot.php index 4ef30eadac..58b4bc0983 100644 --- a/boot.php +++ b/boot.php @@ -6,17 +6,19 @@ /** * 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 * 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'); @@ -28,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'); @@ -463,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; @@ -588,15 +591,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']; /* @@ -862,11 +856,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( @@ -945,6 +939,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($url)); + + $url = normalise_link($url); + $base = normalise_link($this->get_baseurl()); + $url = str_replace($base."/", "", $url); + return $url; + } + /** * @brief Register template engine class * @@ -1034,22 +1047,42 @@ 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; - // Trace the different functions with their timestamps - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); + $callstack = $this->callstack(); - array_shift($trace); + if (!isset($this->callstack[$value][$callstack])) { + // Prevent ugly E_NOTICE + $this->callstack[$value][$callstack] = 0; + } - $function = array(); - foreach ($trace AS $func) - $function[] = $func["function"]; + $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); - $function = implode(", ", $function); + // We remove the first two items from the list since they contain data that we don't need. + array_shift($trace); + array_shift($trace); - $this->callstack[$value][$function] += (float)$duration; + $callstack = array(); + foreach ($trace AS $func) + $callstack[] = $func["function"]; + return implode(", ", $callstack); } function mark_timestamp($mark) { @@ -1065,6 +1098,55 @@ 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')); + if ($maxsysload < 1) + $maxsysload = 50; + + $load = current_load(); + if ($load) { + if (intval($load) > $maxsysload) { + logger('system: load '.$load.' too high.'); + return true; + } + } + 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(); + 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; + } } /** @@ -1416,7 +1498,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"); @@ -1476,6 +1558,9 @@ function killme() { * @brief Redirect to another URL and terminate this process. */ function goaway($s) { + if (!strstr(normalise_link($s), "http://")) + $s = App::get_baseurl()."/".$s; + header("Location: $s"); killme(); } @@ -1735,9 +1820,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) { diff --git a/database.sql b/database.sql index 70b315ea24..89b821e23a 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, @@ -200,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 -- @@ -411,21 +401,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 -- @@ -926,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/Accesskeys.md b/doc/Accesskeys.md index c49e79c0ab..4f16ba2536 100644 --- a/doc/Accesskeys.md +++ b/doc/Accesskeys.md @@ -37,10 +37,7 @@ General * o: Profile * t: Contacts * d: Common friends -* b: Toggle Blocked status -* i: Toggle Ignored status -* v: Toggle Archive status -* r: Repair +* r: Advanced /message -------- diff --git a/doc/BBCode.md b/doc/BBCode.md index fe7c1481f6..186b1cda93 100644 --- a/doc/BBCode.md +++ b/doc/BBCode.md @@ -143,6 +143,56 @@ 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: + +
[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.
+ +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: + +
+[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 ...
+
+ +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. + +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: + +
+[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 ... 
+ +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 +200,3 @@ Special If you need to put literal bbcode in a message, [noparse], [nobb] or [pre] are used to escape bbcode:
[noparse][b]bold[/b][/noparse]
: [b]bold[/b] - - 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. 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: - -* [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). +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. + +* 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) 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/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/api.md b/doc/api.md index ced078f556..7d6f440c58 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,12 +1,27 @@ 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. ## 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 @@ -38,9 +53,9 @@ 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" } ``` @@ -54,19 +69,20 @@ xml: ``` --- -### 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/ -#### parameters -* id: item id - -Add or remove an activity from an item. -'verb' can be one of: -- like -- dislike -- attendyes -- attendno -- attendmaybe - -To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike" -Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes". -Attend verbs should be used only with event-related items (there is no check at the moment) - -#### Return values - -On success: -json -```"ok"``` - -xml -```true``` - -On error: -HTTP 400 BadRequest - ---- -### friendica/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": { - "": "url to image" - ... - }, - // if 'scale' is set - "datasize": "size in byte", - "data": "base64 encoded image data" - } -``` - -xml -``` - - photo id - date(YYYY-MM-GG HH:MM:SS) - date(YYYY-MM-GG HH:MM:SS) - photo title - photo description - album name - original file name - mime type - number - number - 1 if is profile photo - - - ... - - -``` - ---- -### friendica/photos/list - -Returns a list of all photo resources of the logged in user. - -#### Return values - -json -``` - [ - { - id: "resource_id", - album: "album name", - filename: "original file name", - type: "image mime type", - thumb: "url to thumb sized image" - }, - ... - ] -``` - -xml -``` - - - "url to thumb sized image" - - ... - -``` - ---- -### 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,28 @@ 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/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 +### statusnet/version (*) #### Unsupported parameters * user_id @@ -507,7 +411,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 +421,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 +437,39 @@ Friendica doesn't allow showing friends of other users. ## Implemented API calls (not compatible with other APIs) + --- -### friendica/group_show +### friendica/activity/ +#### parameters +* id: item id + +Add or remove an activity from an item. +'verb' can be one of: + +- like +- dislike +- attendyes +- attendno +- attendmaybe + +To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike" +Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes". +Attend verbs should be used only with event-related items (there is no check at the moment) + +#### Return values + +On success: +json +```"ok"``` + +xml +```true``` + +On error: +HTTP 400 BadRequest + +--- +### friendica/group_show (*; AUTH) Return all or a specified group of the user with the containing contacts as array. #### Parameters @@ -542,22 +477,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 +502,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 +526,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": { + "": "url to image" + ... + }, + // if 'scale' is set + "datasize": "size in byte", + "data": "base64 encoded image data" + } +``` + +xml +``` + + photo id + date(YYYY-MM-GG HH:MM:SS) + date(YYYY-MM-GG HH:MM:SS) + photo title + photo description + album name + original file name + mime type + number + number + 1 if is profile photo + + + ... + + +``` + +--- +### friendica/photos/list (*; AUTH) + +Returns a list of all photo resources of the logged in user. + +#### Return values + +json +``` + [ + { + id: "resource_id", + album: "album name", + filename: "original file name", + type: "image mime type", + thumb: "url to thumb sized image" + }, + ... + ] +``` + +xml +``` + + + "url to thumb sized image" + + ... + +``` + + --- ## Not Implemented API calls The following API calls are implemented in GNU Social but not in Friendica: (incomplete) @@ -702,13 +790,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. diff --git a/doc/autoloader.md b/doc/autoloader.md new file mode 100644 index 0000000000..947eade23c --- /dev/null +++ b/doc/autoloader.md @@ -0,0 +1,209 @@ +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. + + +## 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 + 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 + 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 + [url]*url*[/url] -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 @@ -145,11 +144,54 @@ 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: + +
[abstract]Total spannend! Unbedingt diesen Link anklicken![/abstract]
+Hier erzähle ich euch eine total langweilige Geschichte, die ihr noch 
+nie hören wolltet.
+ +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: + +
+[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 ...
+
+ +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: + +
+[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 ... 
+ +Das [abstract]-Element greift nicht bei der nativen OStatus-Verbindung oder bei Connectoren, die den HTML-Text posten wie z.B. die Connectoren zu Tumblr, Wordpress oder Pump.io. + Spezielle Tags ------- Wenn Du ü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:
[noparse][b]fett[/b][/noparse]
: [b]fett[/b] - - 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. diff --git a/doc/htconfig.md b/doc/htconfig.md index 4764c287c8..a36e0bef22 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. @@ -63,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/doc/smarty3-templates.md b/doc/smarty3-templates.md new file mode 100644 index 0000000000..751ef20b31 --- /dev/null +++ b/doc/smarty3-templates.md @@ -0,0 +1,173 @@ +Friendica Templating Documentation +================================== + +* [Home](help) + +Friendica uses [Smarty 3](http://www.smarty.net/) as PHP templating engine. The main templates are found in + + /view/templates + +theme authors may overwrite the default templates by putting a files with the same name into the + + /view/themes/$themename/templates + +directory. + +Templates that are only used by addons shall be placed in the + + /addon/$addonname/templates + +directory. + +To render a template use the function *get_markup_template* to load the template and *replace_macros* to replace the macros/variables in the just loaded template file. + + $tpl = get_markup_template('install_settings.tpl'); + $o .= replace_macros($tpl, array( ... )); + +the array consists of an association of an identifier and the value for that identifier, i.e. + + '$title' => $install_title, + +where the value may as well be an array by its own. + +Form Templates +-------------- + +To guarantee a consistent look and feel for input forms, i.e. in the settings sections, there are templates for the basic form fields. They are initialized with an array of data, depending on the tyle of the field. + +All of these take an array for holding the values, i.e. for an one line text input field, which is required and should be used to type email addesses use something along + + '$adminmail' => array('adminmail', t('Site administrator email address'), $adminmail, t('Your account email address must match this in order to use the web admin panel.'), 'required', '', 'email'), + +To evaluate the input value, you can then use the $_POST array, more precisely the $_POST['adminemail'] variable. + +Listed below are the template file names, the general purpose of the template and their field parameters. + +### field_checkbox.tpl + +A checkbox. If the checkbox is checked its value is **1**. Field parameter: + +0. Name of the checkbox, +1. Label for the checkbox, +2. State checked? if true then the checkbox will be marked as checked, +3. Help text for the checkbox. + +### field_combobox.tpl + +A combobox, combining a pull down selection and a textual input field. Field parameter: + +0. Name of the combobox, +1. Label for the combobox, +2. Current value of the variable, +3. Help text for the combobox, +4. Array holding the possible values for the textual input, +5. Array holding the possible values for the pull down selection. + +### field_custom.tpl + +A customizeable template to include a custom element in the form with the usual surroundings, Field parameter: + +0. Name of the field, +1. Label for the field, +2. the field, +3. Help text for the field. + +### field_input.tpl + +A single line input field for textual input. Field parameter: + +0. Name of the field, +1. Label for the input box, +2. Current value of the variable, +3. Help text for the input box, +4. if set to "required" modern browser will check that this input box is filled when submitting the form, +5. if set to "autofocus" modern browser will put the cursur into this box once the page is loaded, +6. if set to "email" or "url" modern browser will check that the filled in value corresponds to an email address or URL. + +### field_intcheckbox.tpl + +A checkbox (see above) but you can define the value of it. Field parameter: + +0. Name of the checkbox, +1. Label for the checkbox, +2. State checked? if true then the checkbox will be marked as checked, +3. Value of the checkbox, +4. Help text for the checkbox. + +### field_openid.tpl + +An input box (see above) but prepared for special CSS styling for openID input. Field parameter: + +0. Name of the field, +1. Label for the input box, +2. Current value of the variable, +3. Help text for the input field. + +### field_password.tpl + +A single line input field (see above) for textual input. The characters typed in will not be shown by the browser. Field parameter: + +0. Name of the field, +1. Label for the field, +2. Value for the field, e.g. the old password, +3. Help text for the input field, +4. if set to "required" modern browser will check that this field is filled out, +5. if set to "autofocus" modern browser will put the cursor automatically into this input field. + +### field_radio.tpl + +A radio button. Field parameter: + +0. Name of the radio button, +1. Label for the radio button, +2. Current value of the variable, +3. Help text for the button, +4. if set, the radio button will be checked. + +### field_richtext.tpl + +A multi-line input field for *rich* textual content. Field parameter: + +0. Name of the input field, +1. Label for the input box, +2. Current text for the box, +3. Help text for the input box. + +### field_select.tpl + +A drop down selection box. Field parameter: + +0. Name of the field, +1. Label of the selection box, +2. Current selected value, +3. Help text for the selection box, +4. Array holding the possible values of the selection drop down. + +### field_select_raw.tpl + +A drop down selection box (see above) but you have to prepare the values yourself. Field parameter: + +0. Name of the field, +1. Label of the selection box, +2. Current selected value, +3. Help text for the selection box, +4. Possible values of the selection drop down. + +### field_textarea.tpl + +A multi-line input field for (plain) textual content. Field parameter: + +0. Name of the input field, +1. Label for the input box, +2. Current text for the box, +3. Help text for the input box. + +### field_yesno.tpl + +A button that has two states *yes* or *no*. Field parameter: + +0. Name of the input field, +1. Label for the button, +2. Current value, +3. Help text for the button +4. if set to an array of two values, these two will be used, otherwise "off" and "on". diff --git a/doc/snarty3-templates.md b/doc/snarty3-templates.md deleted file mode 100644 index 751ef20b31..0000000000 --- a/doc/snarty3-templates.md +++ /dev/null @@ -1,173 +0,0 @@ -Friendica Templating Documentation -================================== - -* [Home](help) - -Friendica uses [Smarty 3](http://www.smarty.net/) as PHP templating engine. The main templates are found in - - /view/templates - -theme authors may overwrite the default templates by putting a files with the same name into the - - /view/themes/$themename/templates - -directory. - -Templates that are only used by addons shall be placed in the - - /addon/$addonname/templates - -directory. - -To render a template use the function *get_markup_template* to load the template and *replace_macros* to replace the macros/variables in the just loaded template file. - - $tpl = get_markup_template('install_settings.tpl'); - $o .= replace_macros($tpl, array( ... )); - -the array consists of an association of an identifier and the value for that identifier, i.e. - - '$title' => $install_title, - -where the value may as well be an array by its own. - -Form Templates --------------- - -To guarantee a consistent look and feel for input forms, i.e. in the settings sections, there are templates for the basic form fields. They are initialized with an array of data, depending on the tyle of the field. - -All of these take an array for holding the values, i.e. for an one line text input field, which is required and should be used to type email addesses use something along - - '$adminmail' => array('adminmail', t('Site administrator email address'), $adminmail, t('Your account email address must match this in order to use the web admin panel.'), 'required', '', 'email'), - -To evaluate the input value, you can then use the $_POST array, more precisely the $_POST['adminemail'] variable. - -Listed below are the template file names, the general purpose of the template and their field parameters. - -### field_checkbox.tpl - -A checkbox. If the checkbox is checked its value is **1**. Field parameter: - -0. Name of the checkbox, -1. Label for the checkbox, -2. State checked? if true then the checkbox will be marked as checked, -3. Help text for the checkbox. - -### field_combobox.tpl - -A combobox, combining a pull down selection and a textual input field. Field parameter: - -0. Name of the combobox, -1. Label for the combobox, -2. Current value of the variable, -3. Help text for the combobox, -4. Array holding the possible values for the textual input, -5. Array holding the possible values for the pull down selection. - -### field_custom.tpl - -A customizeable template to include a custom element in the form with the usual surroundings, Field parameter: - -0. Name of the field, -1. Label for the field, -2. the field, -3. Help text for the field. - -### field_input.tpl - -A single line input field for textual input. Field parameter: - -0. Name of the field, -1. Label for the input box, -2. Current value of the variable, -3. Help text for the input box, -4. if set to "required" modern browser will check that this input box is filled when submitting the form, -5. if set to "autofocus" modern browser will put the cursur into this box once the page is loaded, -6. if set to "email" or "url" modern browser will check that the filled in value corresponds to an email address or URL. - -### field_intcheckbox.tpl - -A checkbox (see above) but you can define the value of it. Field parameter: - -0. Name of the checkbox, -1. Label for the checkbox, -2. State checked? if true then the checkbox will be marked as checked, -3. Value of the checkbox, -4. Help text for the checkbox. - -### field_openid.tpl - -An input box (see above) but prepared for special CSS styling for openID input. Field parameter: - -0. Name of the field, -1. Label for the input box, -2. Current value of the variable, -3. Help text for the input field. - -### field_password.tpl - -A single line input field (see above) for textual input. The characters typed in will not be shown by the browser. Field parameter: - -0. Name of the field, -1. Label for the field, -2. Value for the field, e.g. the old password, -3. Help text for the input field, -4. if set to "required" modern browser will check that this field is filled out, -5. if set to "autofocus" modern browser will put the cursor automatically into this input field. - -### field_radio.tpl - -A radio button. Field parameter: - -0. Name of the radio button, -1. Label for the radio button, -2. Current value of the variable, -3. Help text for the button, -4. if set, the radio button will be checked. - -### field_richtext.tpl - -A multi-line input field for *rich* textual content. Field parameter: - -0. Name of the input field, -1. Label for the input box, -2. Current text for the box, -3. Help text for the input box. - -### field_select.tpl - -A drop down selection box. Field parameter: - -0. Name of the field, -1. Label of the selection box, -2. Current selected value, -3. Help text for the selection box, -4. Array holding the possible values of the selection drop down. - -### field_select_raw.tpl - -A drop down selection box (see above) but you have to prepare the values yourself. Field parameter: - -0. Name of the field, -1. Label of the selection box, -2. Current selected value, -3. Help text for the selection box, -4. Possible values of the selection drop down. - -### field_textarea.tpl - -A multi-line input field for (plain) textual content. Field parameter: - -0. Name of the input field, -1. Label for the input box, -2. Current text for the box, -3. Help text for the input box. - -### field_yesno.tpl - -A button that has two states *yes* or *no*. Field parameter: - -0. Name of the input field, -1. Label for the button, -2. Current value, -3. Help text for the button -4. if set to an array of two values, these two will be used, otherwise "off" and "on". 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/include/Contact.php b/include/Contact.php index 3799e0b189..79a14ab581 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'); @@ -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/ForumManager.php b/include/ForumManager.php index 7b5fb1c2f6..17a6b6730b 100644 --- a/include/ForumManager.php +++ b/include/ForumManager.php @@ -95,12 +95,12 @@ class ForumManager { $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; diff --git a/include/NotificationsManager.php b/include/NotificationsManager.php new file mode 100644 index 0000000000..5f8211eb87 --- /dev/null +++ b/include/NotificationsManager.php @@ -0,0 +1,136 @@ +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; + } + + + /** + * @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/Scrape.php b/include/Scrape.php index ca6489b16a..68926a997e 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) { @@ -12,9 +13,25 @@ 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); + else + unset($noscrapedata["nick"]); + } else + $noscrapedata = array(); + } + $s = fetch_url($url); - if(! $s) + if (!$s) return $ret; if (!$dont_probe) { @@ -91,8 +108,7 @@ function scrape_dfrn($url, $dont_probe = false) { } } } - - return $ret; + return array_merge($ret, $noscrapedata); }} @@ -342,7 +358,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); @@ -351,6 +367,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { return $result; } + $original_url = $url; $network = null; $diaspora = false; $diaspora_base = ''; @@ -366,8 +383,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); @@ -381,7 +396,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); @@ -428,12 +448,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) - $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']); @@ -441,6 +470,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; @@ -604,21 +636,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 +679,41 @@ 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); + if ($xml == "") { + logger("scrape_feed: XML is empty for feed ".$poll); + $network = NETWORK_PHANTOM; + } else { + $data = feed_import($xml,$dummy1,$dummy2, $dummy3, true); - // Don't try and parse an empty string - $feed->set_raw_data(($xml) ? $xml : ''); + 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"]; - $feed->init(); - if($feed->error()) { - logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error()); - $network = NETWORK_PHANTOM; - } + if (($vcard["fn"] == "") AND ($data["header"]["author-name"] != "")) + $vcard["fn"] = $data["header"]["author-name"]; - if(! x($vcard,'photo')) - $vcard['photo'] = $feed->get_image_url(); - $author = $feed->get_author(); - - 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'],'@')); - - $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 ($network == NETWORK_OSTATUS) { + if ($data["header"]["author-id"] != "") + $alias = $data["header"]["author-id"]; + + 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"]; } } @@ -783,27 +756,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 +771,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) @@ -828,18 +783,21 @@ 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)); + + if (substr($baseurl, -10) == "/index.php") + $baseurl = str_replace("/index.php", "", $baseurl); $baseurl = rtrim($baseurl, "/"); @@ -888,25 +846,82 @@ 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 ($mode == PROBE_NORMAL) AND + $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'])) + ); + } 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/api.php b/include/api.php index 4d206da28e..a494e3cdd9 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','*'); @@ -160,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']; @@ -215,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; @@ -250,7 +249,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"; @@ -330,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); @@ -680,6 +680,34 @@ } + /** + * @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=""; + if (count($data)==1 && !is_array($data[0])) { + $ename = array_keys($data)[0]; + $v = $data[$ename]; + return "<$ename>$v"; + } + 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"; + return $res; + } + /** * load api $templatename for $type and replace $data array */ @@ -692,13 +720,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 ''."\n".'not implemented'; - killme(); + if ($templatename==="") { + $ret = api_array_to_xml($data); + } else { + $tpl = get_markup_template("api_".$templatename."_".$type.".tpl"); + if(! $tpl) { + header ("Content-Type: text/xml"); + echo ''."\n".'not implemented'; + killme(); + } + $ret = replace_macros($tpl, $data); } - $ret = replace_macros($tpl, $data); break; case "json": $ret = $data; @@ -781,8 +813,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 +852,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(); @@ -875,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)); } } @@ -894,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)); + } } @@ -913,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)); } } @@ -1519,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); /** @@ -1660,13 +1692,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 ", @@ -1781,7 +1813,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]); @@ -3386,6 +3418,64 @@ 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); + /** + * @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(); + + $notes = $nm->getAll(array(), "+seen -date", 50); + return api_apply_template("", $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); + } + // the item can't be found, but we set the note as seen, so we count this as a success + } + return api_apply_template('', $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: [pagename] => api/1.1/statuses/lookup.json 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/autoloader.php b/include/autoloader.php new file mode 100644 index 0000000000..6caa082915 --- /dev/null +++ b/include/autoloader.php @@ -0,0 +1,69 @@ + $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 @@ + + * Jordi Boggiano + * + * 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 + * @author Jordi Boggiano + */ +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 @@ + $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 @@ + 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 @@ +title = $match[2]; @@ -858,6 +861,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); @@ -1300,4 +1305,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/contact_selectors.php b/include/contact_selectors.php index f104866232..3bf68f764e 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; } diff --git a/include/conversation.php b/include/conversation.php index 6c33be84fb..a52502ec39 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); @@ -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/cron.php b/include/cron.php index 3acf711dd1..00dd500704 100644 --- a/include/cron.php +++ b/include/cron.php @@ -34,22 +34,18 @@ 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'); + require_once('include/post_update.php'); load_config('config'); load_config('system'); - $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.'); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + if (App::maxload_reached()) + return; + if (App::is_already_running('cron', 'include/cron.php', 540)) return; - } } $last = get_config('system','last_cron'); @@ -66,23 +62,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(); @@ -93,10 +72,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"); @@ -127,13 +102,14 @@ 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(); + // Call possible post update functions + // see include/post_update.php for more details + post_update(); // update nodeinfo data nodeinfo_cron(); @@ -361,35 +337,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"]; + // 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()); @@ -429,6 +407,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/cronhooks.php b/include/cronhooks.php index 8c70008e45..b6cf0e7237 100644 --- a/include/cronhooks.php +++ b/include/cronhooks.php @@ -19,21 +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'); - $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.'); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + if (App::maxload_reached()) + return; + if (App::is_already_running('cronhooks', 'include/cronhooks.php', 1140)) return; - } } $last = get_config('system','last_cronhook'); @@ -50,21 +45,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/dbstructure.php b/include/dbstructure.php index 96d18cd789..e34e409023 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"), @@ -748,21 +737,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"), @@ -1261,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" => ""), @@ -1269,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/delivery.php b/include/delivery.php index 021ceb9968..fe33774382 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,21 +53,12 @@ function delivery_run(&$argv, &$argc){ dbesc($item_id), dbesc($contact_id) ); - if(! count($r)) { + if (!count($r)) { 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. @@ -77,7 +68,7 @@ function delivery_run(&$argv, &$argc){ dbesc($contact_id) ); - if((! $item_id) || (! $contact_id)) + if (!$item_id || !$contact_id) continue; $expire = false; @@ -93,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 @@ -115,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; @@ -140,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; } @@ -154,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; } @@ -193,7 +184,7 @@ function delivery_run(&$argv, &$argc){ intval($uid) ); - if(! count($r)) + if (!count($r)) continue; $owner = $r[0]; @@ -202,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]; @@ -226,7 +217,7 @@ function delivery_run(&$argv, &$argc){ $localhost = $a->get_hostname(); - if(strpos($localhost,':')) + if (strpos($localhost,':')) $localhost = substr($localhost,0,strpos($localhost,':')); /** @@ -239,20 +230,21 @@ 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']))) { + || (strlen($parent['deny_gid'])) + || $parent["private"]) { $public_message = false; // private recipients, not public } @@ -262,10 +254,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; @@ -275,7 +267,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']); @@ -285,13 +277,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; } } @@ -299,19 +291,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; @@ -326,15 +318,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'])); @@ -356,10 +348,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; @@ -379,14 +371,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); } @@ -394,9 +386,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. @@ -406,67 +398,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 @@ -474,23 +466,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); @@ -498,60 +490,59 @@ 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); 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); - - diaspora_send_retraction($target_item,$owner,$contact,$public_message); + logger('diaspora retract: '.$loc); + 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); + logger('diaspora followup: '.$loc); + 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('diaspora relay: '.$loc); + 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('diaspora status: '.$loc); + 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/dfrn.php b/include/dfrn.php index c6c8deef58..14be747305 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -18,7 +18,8 @@ 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"); +require_once("include/bbcode.php"); +require_once("include/xml.php"); /** * @brief This class contain functions to create and send DFRN XML files @@ -85,7 +86,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]; } } @@ -96,7 +97,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) @@ -106,7 +107,7 @@ class dfrn { killme(); $owner = $r[0]; - $owner_id = $owner['user_uid']; + $owner_id = $owner['uid']; $owner_nick = $owner['nickname']; $sql_post_table = ""; @@ -244,7 +245,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 @@ -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['user_uid'], $owner['timezone']); + $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) @@ -499,28 +500,28 @@ 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"]); - 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,28 +623,35 @@ 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(substr($r->link,0,1) == '<') { if(strstr($r->link,'&') && (! strstr($r->link,'&'))) $r->link = str_replace('&','&', $r->link); $r->link = preg_replace('/\/','',$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("".$r->link."", 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")); + xml::add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html")); return $entry; } @@ -677,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); } } } @@ -704,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"); @@ -714,6 +722,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; @@ -735,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) @@ -809,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)) @@ -822,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)); } @@ -1108,13 +1119,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)); @@ -1123,6 +1134,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; @@ -1152,38 +1166,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 @@ -1200,7 +1217,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"]; @@ -1210,7 +1227,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" @@ -1225,9 +1242,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); @@ -1238,6 +1257,7 @@ class dfrn { unset($fields["id"]); unset($fields["uid"]); + unset($fields["url"]); unset($fields["avatar-date"]); unset($fields["name-date"]); unset($fields["uri-date"]); @@ -1245,8 +1265,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]) { @@ -1255,7 +1277,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', @@ -1274,9 +1296,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); @@ -1301,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)) @@ -1311,9 +1334,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)) @@ -1750,6 +1774,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 @@ -1780,11 +1807,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; @@ -1810,7 +1837,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); @@ -1945,6 +1972,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; @@ -1997,14 +2026,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]"; } + } } } @@ -2043,10 +2086,14 @@ 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; - } + // 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; + //} } if ($entrytype == DFRN_REPLY_RC) { @@ -2218,15 +2265,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"]) @@ -2355,8 +2404,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/diaspora.php b/include/diaspora.php index 93fe2a472f..e3a3dcd78c 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1,3134 +1,3226 @@ 0, "page-flags" => PAGE_FREELOVE); - $result = diaspora_dispatch($importer,$msg); - logger("Dispatcher reported ".$result, LOGGER_DEBUG); - - // 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); - } - } - 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; - } +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"); +require_once("include/queue_fn.php"); - // php doesn't like dashes in variable names - - $msg['message'] = str_replace( - array('',''), - array('',''), - $msg['message']); +/** + * @brief This class contain functions to create and send Diaspora XML files + * + */ +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() { - $parsed_xml = parse_xml_string($msg['message'],false); + $serverdata = get_config("system", "relay_server"); + if ($serverdata == "") + return array(); - $xmlbase = $parsed_xml->post; + $relay = array(); - logger('diaspora_dispatch: ' . print_r($xmlbase,true), LOGGER_DEBUG); + $servers = explode(",", $serverdata); + foreach($servers AS $server) { + $server = trim($server); + $batch = $server."/receive/public"; - if($xmlbase->request) { - $ret = diaspora_request($importer,$xmlbase->request); - } - elseif($xmlbase->status_message) { - $ret = diaspora_post($importer,$xmlbase->status_message,$msg); - } - elseif($xmlbase->profile) { - $ret = diaspora_profile($importer,$xmlbase->profile,$msg); - } - elseif($xmlbase->comment) { - $ret = diaspora_comment($importer,$xmlbase->comment,$msg); - } - elseif($xmlbase->like) { - $ret = diaspora_like($importer,$xmlbase->like,$msg); - } - elseif($xmlbase->asphoto) { - $ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg); - } - elseif($xmlbase->reshare) { - $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg); - } - elseif($xmlbase->retraction) { - $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); - } - elseif($xmlbase->signed_retraction) { - $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); - } - elseif($xmlbase->relayable_retraction) { - $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg); - } - elseif($xmlbase->photo) { - $ret = diaspora_photo($importer,$xmlbase->photo,$msg,$attempt); - } - elseif($xmlbase->conversation) { - $ret = diaspora_conversation($importer,$xmlbase->conversation,$msg); - } - elseif($xmlbase->message) { - $ret = diaspora_message($importer,$xmlbase->message,$msg); - } - elseif($xmlbase->participation) { - $ret = diaspora_participation($importer,$xmlbase->participation); - } - else { - 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); + $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); - if($contact['network'] === NETWORK_DIASPORA) { - $handle = $contact['addr']; + 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()) + ); -// logger("diaspora_handle_from_contact: contact id is a Diaspora person, handle = " . $handle, LOGGER_DEBUG); + $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]; } - 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 $relay; } - return $handle; -} - -function diaspora_get_contact_by_handle($uid,$handle) { - $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `addr` = '%s' LIMIT 1", - dbesc(NETWORK_DIASPORA), - intval($uid), - dbesc($handle) - ); - if($r && count($r)) - return $r[0]; - - $handle_parts = explode("@", $handle); - $nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0]; - $r = q("SELECT * FROM contact WHERE network = '%s' AND uid = %d AND nurl LIKE '%s' LIMIT 1", - dbesc(NETWORK_DFRN), - intval($uid), - dbesc($nurl_sql) - ); - if($r && count($r)) - return $r[0]; - - return false; -} - -function find_diaspora_person_by_handle($handle) { - - $person = false; - $update = false; - $got_lock = false; - - $endlessloop = 0; - $maxloops = 10; - - do { - $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('find_diaspora_person_by handle: 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; - } + /** + * @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 + * + * @return string the repaired signature + */ + private function repair_signature($signature, $handle = "", $level = 1) { + if ($signature == "") + return ($signature); - // 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); - } + 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); - 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'); - } + // Do a recursive call to be able to fix even multiple levels + if ($level < 10) + $signature = self::repair_signature($signature, $handle, ++$level); } - } 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 ''; -} + return($signature); + } + /** + * @brief: Decodes incoming Diaspora message + * + * @param array $importer Array of the importer user + * @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) + */ + public static function decode($importer, $xml) { -function diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey) { - $a = get_app(); + $public = false; + $basedom = parse_xml_string($xml); - logger('diaspora_pubmsg_build: ' . $msg, LOGGER_DATA); + if (!is_object($basedom)) + return false; + $children = $basedom->children('https://joindiaspora.com/protocol'); - $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); + if($children->header) { + $public = true; + $author_link = str_replace('acct:','',$children->header->author_id); + } else { -// $b64_data = base64_encode($msg); -// $b64url_data = base64url_encode($b64_data); + $encrypted_header = json_decode(base64_decode($children->encrypted_header)); - $b64url_data = base64url_encode($msg); + $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); + $ciphertext = base64_decode($encrypted_header->ciphertext); - $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data); + $outer_key_bundle = ''; + openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); - $type = 'application/xml'; - $encoding = 'base64url'; - $alg = 'RSA-SHA256'; + $j_outer_key_bundle = json_decode($outer_key_bundle); - $signable_data = $data . '.' . base64url_encode($type) . '.' - . base64url_encode($encoding) . '.' . base64url_encode($alg) ; + $outer_iv = base64_decode($j_outer_key_bundle->iv); + $outer_key = base64_decode($j_outer_key_bundle->key); - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); + $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); -$magic_env = <<< EOT - - -
- $handle -
- - base64url - RSA-SHA256 - $data - $sig - -
-EOT; - logger('diaspora_pubmsg_build: magic_env: ' . $magic_env, LOGGER_DATA); - return $magic_env; + $decrypted = pkcs5_unpad($decrypted); -} + 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); -function diaspora_msg_build($msg,$user,$contact,$prvkey,$pubkey,$public = false) { - $a = get_app(); + // figure out where in the DOM tree our data is hiding - if($public) - return diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey); + if($dom->provenance->data) + $base = $dom->provenance; + elseif($dom->env->data) + $base = $dom->env; + elseif($dom->data) + $base = $dom; - logger('diaspora_msg_build: ' . $msg, LOGGER_DATA); + if (!$base) { + logger('unable to locate salmon data in xml'); + http_status_exit(400); + } - // without a public key nothing will work - if(! $pubkey) { - logger('diaspora_msg_build: pubkey missing: contact id: ' . $contact['id']); - return ''; - } + // 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); - $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); + // unpack the data - $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); + // strip whitespace so our data element will return to one big base64 blob + $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); - $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); + // stash away some other stuff for later - $b64_data = base64_encode($inner_encrypted); + $type = $base->data[0]->attributes()->type[0]; + $keyhash = $base->sig[0]->attributes()->keyhash[0]; + $encoding = $base->encoding; + $alg = $base->alg; - $b64url_data = base64url_encode($b64_data); - $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data); + $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg); - $type = 'application/xml'; - $encoding = 'base64url'; - $alg = 'RSA-SHA256'; - $signable_data = $data . '.' . base64url_encode($type) . '.' - . base64url_encode($encoding) . '.' . base64url_encode($alg) ; + // decode the data + $data = base64url_decode($data); - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); -$decrypted_header = <<< EOT - - $b_inner_iv - $b_inner_aes_key - $handle - -EOT; + if($public) + $inner_decrypted = $data; + else { - $decrypted_header = pkcs5_pad($decrypted_header,16); + // Decode the encrypted blob - $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); + $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); + } - $outer_json = json_encode(array('iv' => $b_outer_iv,'key' => $b_outer_aes_key)); + 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 - $encrypted_outer_key_bundle = ''; - openssl_public_encrypt($outer_json,$encrypted_outer_key_bundle,$pubkey); + logger('Fetching key for '.$author_link); + $key = self::key($author_link); - $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); + if (!$key) { + logger('Could not retrieve author key.'); + http_status_exit(400); + } - logger('outer_bundle: ' . $b64_encrypted_outer_key_bundle . ' key: ' . $pubkey, LOGGER_DATA); + $verify = rsa_verify($signed_data,$signature,$key); - $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); + if (!$verify) { + logger('Message did not verify. Discarding.'); + http_status_exit(400); + } - $encrypted_header = '' . $cipher_json . ''; + logger('Message verified.'); -$magic_env = <<< EOT - - - $encrypted_header - - base64url - RSA-SHA256 - $data - $sig - - -EOT; + return array('message' => (string)$inner_decrypted, + 'author' => unxmlify($author_link), + 'key' => (string)$key); - 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 - */ + /** + * @brief Dispatches public messages and find the fitting receivers + * + * @param array $msg The post that will be dispatched + * + * @return int The message id of the generated message, "true" or "false" if there was an error + */ + public static function dispatch_public($msg) { + $enabled = intval(get_config("system", "diaspora_enabled")); + if (!$enabled) { + logger("diaspora is disabled"); + return false; + } -function diaspora_decode($importer,$xml) { - - $public = false; - $basedom = parse_xml_string($xml); + // Use a dummy importer to import the data for the public copy + $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); + $message_id = self::dispatch($importer,$msg); - $children = $basedom->children('https://joindiaspora.com/protocol'); + // 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)); - if($children->header) { - $public = true; - $author_link = str_replace('acct:','',$children->header->author_id); + return $message_id; } - else { - $encrypted_header = json_decode(base64_decode($children->encrypted_header)); + /** + * @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 int The message id of the generated message, "true" or "false" if there was an error + */ + public static function dispatch($importer, $msg) { - $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); - $ciphertext = base64_decode($encrypted_header->ciphertext); + // 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"]; - $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); + if (!diaspora::valid_posting($msg, $fields)) { + logger("Invalid posting"); + return false; + } + $type = $fields->getName(); - $decrypted = pkcs5_unpad($decrypted); + logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG); - /** - * $decrypted now contains something like - * - * - * 8e+G2+ET8l5BPuW0sVTnQw== - * UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU= + switch ($type) { + case "account_deletion": + return self::receive_account_deletion($importer, $fields); -***** OBSOLETE + case "comment": + return self::receive_comment($importer, $sender, $fields, $msg["message"]); - * - * Ryan Hughes - * acct:galaxor@diaspora.pirateship.org - * + case "contact": + return self::receive_contact_request($importer, $fields); -***** CURRENT + case "conversation": + return self::receive_conversation($importer, $msg, $fields); - * galaxor@diaspora.priateship.org + case "like": + return self::receive_like($importer, $sender, $fields); -***** END DIFFS + case "message": + return self::receive_message($importer, $fields); - * - */ + case "participation": // Not implemented + return self::receive_participation($importer, $fields); - logger('decrypted: ' . $decrypted, LOGGER_DEBUG); - $idom = parse_xml_string($decrypted,false); + case "photo": // Not implemented + return self::receive_photo($importer, $fields); - $inner_iv = base64_decode($idom->iv); - $inner_aes_key = base64_decode($idom->aes_key); + case "poll_participation": // Not implemented + return self::receive_poll_participation($importer, $fields); - $author_link = str_replace('acct:','',$idom->author_id); + case "profile": + return self::receive_profile($importer, $fields); - } + case "reshare": + return self::receive_reshare($importer, $fields, $msg["message"]); - $dom = $basedom->children(NAMESPACE_SALMON_ME); + case "retraction": + return self::receive_retraction($importer, $sender, $fields); - // figure out where in the DOM tree our data is hiding + case "status_message": + return self::receive_status_message($importer, $fields, $msg["message"]); - if($dom->provenance->data) - $base = $dom->provenance; - elseif($dom->env->data) - $base = $dom->env; - elseif($dom->data) - $base = $dom; + default: + logger("Unknown message type ".$type); + return false; + } - if(! $base) { - logger('mod-diaspora: unable to locate salmon data in xml '); - http_status_exit(400); + 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) { - // 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); + $data = parse_xml_string($msg["message"], false); - // 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); + if (!is_object($data)) + return false; + $first_child = $data->getName(); - // stash away some other stuff for later + // 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 = $base->data[0]->attributes()->type[0]; - $keyhash = $base->sig[0]->attributes()->keyhash[0]; - $encoding = $base->encoding; - $alg = $base->alg; + $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"; - $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg); + if ($type == "request") + $type = "contact"; + $fields = new SimpleXMLElement("<".$type."/>"); - // decode the data - $data = base64url_decode($data); + $signed_data = ""; + foreach ($element->children() AS $fieldname => $entry) { + if ($oldXML) { + // Translation for the old XML structure + if ($fieldname == "diaspora_handle") + $fieldname = "author"; - if($public) { - $inner_decrypted = $data; - } - else { + if ($fieldname == "participant_handles") + $fieldname = "participants"; - // Decode the encrypted blob + if (in_array($type, array("like", "participation"))) { + if ($fieldname == "target_type") + $fieldname = "parent_type"; + } - $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 ($fieldname == "sender_handle") + $fieldname = "author"; - if(! $author_link) { - logger('mod-diaspora: Could not retrieve author URI.'); - http_status_exit(400); - } + if ($fieldname == "recipient_handle") + $fieldname = "recipient"; - // 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 + if ($fieldname == "root_diaspora_id") + $fieldname = "root_author"; - logger('mod-diaspora: Fetching key for ' . $author_link ); - $key = get_diaspora_key($author_link); + if ($type == "retraction") { + if ($fieldname == "post_guid") + $fieldname = "target_guid"; - if(! $key) { - logger('mod-diaspora: Could not retrieve author key.'); - http_status_exit(400); - } + if ($fieldname == "type") + $fieldname = "target_type"; + } + } - $verify = rsa_verify($signed_data,$signature,$key); + 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 .= ";"; + } - if(! $verify) { - logger('mod-diaspora: Message did not verify. Discarding.'); - http_status_exit(400); - } + $signed_data .= $entry; + } + if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR + ($orig_type == "relayable_retraction")) + xml::copy($entry, $fields, $fieldname); + } - logger('mod-diaspora: Message verified.'); + // 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; + } - return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); + // 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"]); -function diaspora_request($importer,$xml) { + if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) + return false; + } - $a = get_app(); + $key = self::key($fields->author); - $sender_handle = unxmlify($xml->sender_handle); - $recipient_handle = unxmlify($xml->recipient_handle); + return rsa_verify($signed_data, $author_signature, $key, "sha256"); + } - if(! $sender_handle || ! $recipient_handle) - return; + /** + * @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); - $contact = diaspora_get_contact_by_handle($importer['uid'],$sender_handle); + logger("Fetching diaspora key for: ".$handle); - if($contact) { + $r = self::person_by_handle($handle); + if($r) + return $r["pubkey"]; - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. + return ""; + } - 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 + /** + * @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 `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", - intval($importer['uid']) + $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); - 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'] = '' . ACTIVITY_OBJ_PERSON . '' . $contact['name'] . '' - . '' . $contact['url'] . '/' . $contact['name'] . ''; - $arr['object'] .= '' . xmlify('' . "\n"); - $arr['object'] .= xmlify('' . "\n"); - $arr['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']; + // update record occasionally so it doesn't get stale + $d = strtotime($person["updated"]." +00:00"); + if ($d < strtotime("now - 14 days")) + $update = true; + } - $i = item_store($arr); - if($i) - proc_run('php',"include/notifier.php","activity","$i"); + 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) { + + 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; - } - - $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; + return $r; } - $batch = (($ret['batch']) ? $ret['batch'] : implode('/', array_slice(explode('/',$ret['url']),0,3)) . '/receive/public'); + /** + * @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; + 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("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 + $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", + intval($contact_id)); + if ($r) { + $contact = $r[0]; - $contact_record = diaspora_get_contact_by_handle($importer['uid'],$sender_handle); + logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); - if(! $contact_record) { - logger('diaspora_request: unable to locate newly created contact record.'); - return; - } + if($contact['addr'] != "") + $handle = $contact['addr']; + 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); + $handle = $contact['nick'].'@'.$baseurl; + } + } - $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']); + return $handle; } - 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()) + /** + * @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), + dbesc($handle) ); - } - else { - - // automatic friend approval - - require_once('include/Photo.php'); - update_contact_avatar($contact_record['photo'],$importer['uid'],$contact_record['id']); + if ($r) + return $r[0]; - // 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']) + $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]; - $u = q("select * from user where uid = %d limit 1",intval($importer['uid'])); - if($u) - $ret = diaspora_share($u[0],$contact_record); + return false; } - return; -} - -function diaspora_post_allow($importer,$contact, $is_comment = 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. - // 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'); - } + // 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) + 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; -} - -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); + // Messages for the global users are always accepted + if ($importer["uid"] == 0) + return true; - // Do a recursive call to be able to fix even multiple levels - if ($level < 10) - $signature = diaspora_repair_signature($signature, $handle, ++$level); + return false; } - return($signature); -} + /** + * @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 array The contact data + */ + 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; + } -function diaspora_post($importer,$xml,$msg) { + 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; + } + + /** + * @brief Does the message already exists on the system? + * + * @param int $uid The user id + * @param string $guid The guid of the message + * + * @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", + intval($uid), + dbesc($guid) + ); - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); + if($r) { + logger("message ".$guid." already exists for user ".$uid); + return $r[0]["id"]; + } - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; + return false; } - $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; - } + /** + * @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){ + return(self::fetch_guid_sub($match, $item)); + },$item["body"]); + } + + /** + * @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 + */ + 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"]; + + logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); + + $msg = self::message($guid, $server); + + if (!$msg) + return false; - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_post: Ignoring this author.'); - return 202; - } + logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); - $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; + // Now call the dispatcher + return self::dispatch_public($msg); } - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); + /** + * @brief Fetches a message from a server + * + * @param string $guid message guid + * @param string $server The url of the server + * @param int $level Endless loop prevention + * + * @return array + * 'message' => The message XML + * 'author' => The author handle + * 'key' => The public key of the author + */ + private function message($guid, $server, $level = 0) { - $body = diaspora2bb($xml->raw_message); - - $datarray = array(); + if ($level > 5) + return false; - $datarray["object"] = json_encode($xml); + // This will work for Diaspora and newer Friendica servers + $source_url = $server."/p/".$guid.".xml"; + $x = fetch_url($source_url); + if(!$x) + return false; - 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); - } + $source_xml = parse_xml_string($x, false); - $str_tags = ''; + if (!is_object($source_xml)) + return false; - $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]'; + 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); } - } - - $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); + $author = ""; - $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"]; + // 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; - $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); + // If this isn't a "status_message" then quit + if (!$author) 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; -} - -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('',''),array('',''),$x); - $source_xml = parse_xml_string($x,false); + $msg = array("message" => $x, "author" => $author); - $item = array(); - $item["app"] = 'Diaspora'; - $item["guid"] = $guid; - $body = ""; + $msg["key"] = self::key($msg["author"]); - 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; + return $msg; } - if (trim($body) == "") - return false; - - $item["tag"] = ''; - $item["body"] = $body; + /** + * @brief Fetches the item record of a given guid + * + * @param int $uid The user id + * @param string $guid message guid + * @param string $author The handle of the item + * @param array $contact The contact of the item owner + * + * @return array the item record + */ + 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)); - return $item; -} + if(!$r) { + $result = self::store_by_guid($guid, $contact["url"], $uid); -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; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; + if (!$result) { + $person = self::person_by_handle($author); + $result = self::store_by_guid($guid, $person["url"], $uid); + } - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_reshare: Ignoring this author: ' . $diaspora_handle . ' ' . print_r($xml,true)); - return 202; - } + if ($result) { + logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG); - $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; - } + $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)); + } + } - $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 (!$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 = ""; - - $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); + /** + * @brief returns contact details + * + * @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 array + * 'cid' => contact id + * 'network' => network type + */ + 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; } - 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)); + } + + /** + * @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 string $addr The user handle + * @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)); + + // 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; + } + + /** + * @brief Processes an account deletion + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success + */ + 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; } - } - $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; + // We now remove the contact + contact_remove($contact["id"]); + return true; } - $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"); + /** + * @brief Processes an incoming comment + * + * @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)); + $text = unxmlify($data->text); + $author = notags(unxmlify($data->author)); + + $contact = self::allowed_contact_by_handle($importer, $sender, true); + if (!$contact) + return false; - $datarray2 = $datarray; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; - $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; + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; - $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; + $person = self::person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author details"); + return false; + } - DiasporaFetchGuid($datarray2); - $message_id = item_store($datarray2); + // Fetch the contact id - if we know this contact + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); - logger("Store original item ".$orig_guid." under message id ".$message_id); - } + $datarray = array(); - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; - return; + $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; -function diaspora_asphoto($importer,$xml,$msg) { - logger('diaspora_asphoto called'); + $datarray["type"] = "remote-comment"; + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_COMMENT; + $datarray["parent-uri"] = $parent_item["uri"]; - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); + $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; + $datarray["object"] = $xml; - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } + $datarray["body"] = diaspora2bb($text); - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; + self::fetch_guid($datarray); - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_asphoto: Ignoring this author.'); - return 202; - } + $message_id = item_store($datarray); - $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; - } + if ($message_id) + logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { - 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; - } + // 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)) + ); - $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; + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); + } -} + return $message_id; + } + + /** + * @brief processes and stores private messages + * + * @param array $importer Array of the importer user + * @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 + * @param array $conversation The conversation record to which this message belongs + * + * @return bool "true" if it was successful + */ + 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)); -function diaspora_comment($importer,$xml,$msg) { + $reply = 0; - $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)); + $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; - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); + $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_comment: cannot find contact: ' . $msg['author']); - return; - } + if($msg_conversation_guid != $guid) { + logger("message conversation guid does not belong to the current conversation."); + return false; + } - if(! diaspora_post_allow($importer,$contact, true)) { - logger('diaspora_comment: Ignoring this author.'); - return 202; - } + $body = diaspora2bb($msg_text); + $message_uri = $msg_author.":".$msg_guid; - $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; - } + $author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid; - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); + $author_signature = base64_decode($msg_author_signature); - if(!count($r)) { - $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); + if(strcasecmp($msg_author,$msg["author"]) == 0) { + $person = $contact; + $key = $msg["key"]; + } else { + $person = self::person_by_handle($msg_author); - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); + if (is_array($person) && x($person, "pubkey")) + $key = $person["pubkey"]; + else { + logger("unable to find author details"); + return false; + } } - 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 (!rsa_verify($author_signed_data, $author_signature, $key, "sha256")) { + logger("verification failed."); + return false; } - } - if(! count($r)) { - logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid); - return; - } - $parent_item = $r[0]; + 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); - /* How Diaspora performs comment signature checking: + $key = $msg["key"]; - - 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 - */ + if (!rsa_verify($owner_signed_data, $parent_author_signature, $key, "sha256")) { + logger("owner verification failed."); + return false; + } + } - $signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle; - $key = $msg['key']; + $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; + } - 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 + 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) + ); - $parent_author_signature = base64_decode($parent_author_signature); + q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", + dbesc(datetime_convert()), + intval($conversation["id"]) + ); - if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_comment: top-level owner verification failed.'); - return; - } + 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; } - 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; + /** + * @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 bool Success + */ + 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; } - } - // Phew! Everything checks out. Now create an item. + $contact = self::allowed_contact_by_handle($importer, $msg["author"], true); + if (!$contact) + return false; + + $conversation = null; - // 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); + $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(! is_array($person)) { - logger('diaspora_comment: unable to find author details'); + if($c) + $conversation = $c[0]; + } + if (!$conversation) { + logger("unable to create conversation."); 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; + foreach($messages as $mesg) + self::receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation); + + return true; } - $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) - //); - //} + /** + * @brief Creates the body for a "like" message + * + * @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 string the body + */ + 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); + } + + /** + * @brief Creates a XML object for a "like" + * + * @param array $importer Array of the importer user + * @param array $parent_item The item array of the parent item + * + * @return string The XML + */ + private function construct_like_object($importer, $parent_item) { + $objtype = ACTIVITY_OBJ_NOTE; + $link = ''; + $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); + } + + /** + * @brief Processes "like" messages + * + * @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)); + $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; - // 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); + $contact = self::allowed_contact_by_handle($importer, $sender, true); + if (!$contact) + return false; - 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) - ); + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; - // notify others - proc_run('php','include/notifier.php','comment-import',$message_id); - } + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; - return; -} + $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(); -function diaspora_conversation($importer,$xml,$msg) { + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; - $a = get_app(); + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); - $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))); + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - $parent_uri = $diaspora_handle . ':' . $guid; + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; - $messages = $xml->message; + $datarray["type"] = "activity"; + $datarray["verb"] = $verb; + $datarray["gravity"] = GRAVITY_LIKE; + $datarray["parent-uri"] = $parent_item["uri"]; - if(! count($messages)) { - logger('diaspora_conversation: empty conversation'); - return; - } + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + $datarray["object"] = self::construct_like_object($importer, $parent_item); - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_conversation: cannot find contact: ' . $msg['author']); - return; - } + $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); - if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { - logger('diaspora_conversation: Ignoring this author.'); - return 202; - } + $message_id = item_store($datarray); - $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; - } + if ($message_id) + logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - foreach($messages as $mesg) { + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { - $reply = 0; + // 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)) + ); - $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; + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); } - $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; + return $message_id; + } + + /** + * @brief Processes private messages + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success? + */ + 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; - $author_signature = base64_decode($msg_author_signature); + $conversation = null; - if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) { - $person = $contact; - $key = $msg['key']; - } + $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 { - $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; + logger("conversation not available."); + 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; - - $parent_author_signature = base64_decode($msg_parent_author_signature); + $reply = 0; - $key = $msg['key']; + $body = diaspora2bb($text); + $message_uri = $author.":".$guid; - if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_conversation: owner verification failed.'); - continue; - } + $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' limit 1", - dbesc($message_id) + $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($message_uri), + intval($importer["uid"]) ); - 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']), - dbesc($msg_guid), - intval($conversation['id']), - dbesc($person['name']), - dbesc($person['photo']), - dbesc($person['url']), - intval($contact['id']), - dbesc($subject), + 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, - 0, - dbesc($message_id), - dbesc($parent_uri), - dbesc($msg_created_at) + 1, + dbesc($message_uri), + dbesc($author.":".$parent_guid), + dbesc($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' - )); + return true; } - return; -} + /** + * @brief Processes participations - unsupported by now + * + * @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; + } -function diaspora_message($importer,$xml,$msg) { + /** + * @brief Processes photos - unneeded + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @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 + return true; + } - $a = get_app(); + /** + * @brief Processes poll participations - unssupported + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool always true + */ + private function receive_poll_participation($importer, $data) { + // We don't support polls by now + return true; + } - $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)); + /** + * @brief Processes incoming profile updates + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success + */ + private function receive_profile($importer, $data) { + $author = notags(unxmlify($data->author)); - $parent_uri = $msg_diaspora_handle . ':' . $msg_parent_guid; + $contact = self::contact_by_handle($importer["uid"], $author); + if (!$contact) + return false; - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg_diaspora_handle); - if(! $contact) { - logger('diaspora_message: cannot find contact: ' . $msg_diaspora_handle); - 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; + } - if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { - logger('diaspora_message: Ignoring this author.'); - return 202; - } + $keywords = implode(", ", $keywords); - $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; - } + $handle_parts = explode("@", $author); + $nick = $handle_parts[0]; - $reply = 0; + if($name === "") + $name = $handle_parts[0]; - $body = diaspora2bb($msg_text); - $message_id = $msg_diaspora_handle . ':' . $msg_guid; + if( preg_match("|^https?://|", $image_url) === 0) + $image_url = "http://".$handle_parts[1].$image_url; - $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($xml->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + update_contact_avatar($image_url, $importer["uid"], $contact["id"]); + // Generic birthday. We don't know the timezone. The year is irrelevant. - $author_signature = base64_decode($msg_author_signature); + $birthday = str_replace("1000", "1901", $birthday); - $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 ($birthday != "") + $birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d"); - if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_message: verification failed.'); - return; - } + // 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 - $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; - } + if(substr($birthday,5) === substr($contact["bd"],5)) + $birthday = $contact["bd"]; - 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; -} + $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"]) + ); -function diaspora_participation($importer,$xml) { - logger("Unsupported message type 'participation' ".print_r($xml, true)); -} + if ($searchable) { + poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", + datetime_convert(), 2, $contact["id"], $importer["uid"]); + } -function diaspora_photo($importer,$xml,$msg,$attempt=1) { + $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); - $a = get_app(); + update_gcontact($gcontact); - logger('diaspora_photo: init',LOGGER_DEBUG); + logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG); - $remote_photo_path = notags(unxmlify($xml->remote_photo_path)); + return true; + } - $remote_photo_name = notags(unxmlify($xml->remote_photo_name)); + /** + * @brief Processes incoming friend requests + * + * @param array $importer Array of the importer user + * @param array $contact The contact that send the request + */ + private function receive_request_make_friend($importer, $contact) { - $status_message_guid = notags(unxmlify($xml->status_message_guid)); + $a = get_app(); - $guid = notags(unxmlify($xml->guid)); + 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 - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); + $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", + intval($importer["uid"]) + ); - $public = notags(unxmlify($xml->public)); + if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) { - $created_at = notags(unxmlify($xml_created_at)); + $self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", + intval($importer["uid"]) + ); - logger('diaspora_photo: status_message_guid: ' . $status_message_guid, LOGGER_DEBUG); + // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array - $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($self && $contact["rel"] == CONTACT_IS_FOLLOWER) { - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_photo: Ignoring this author.'); - return 202; + $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"] = self::construct_new_friend_object($contact); + + $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); + } + } } - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($status_message_guid) - ); + /** + * @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 = ''."\n". + ''."\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 + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success + */ + private function receive_contact_request($importer, $data) { + $author = unxmlify($data->author); + $recipient = unxmlify($data->recipient); + + if (!$author || !$recipient) + return false; -/* 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']); + // 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 (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($status_message_guid, $person['url'], $importer['uid']); - } + if (isset($data->sharing)) + $sharing = (unxmlify($data->sharing) == "true"); + else + $sharing = true; - if ($result) { - logger("Fetched missing item ".$status_message_guid." - result: ".$result, LOGGER_DEBUG); + $contact = self::contact_by_handle($importer["uid"],$author); - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($status_message_guid) - ); + // 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; } - } -*/ - 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 (!$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; } - logger('diaspora_photo: attempt = ' . $attempt . '; status message not found: ' . $status_message_guid . ' for photo: ' . $guid); - return; - } + $ret = self::person_by_handle($author); - $parent_item = $r[0]; + if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { + logger("Cannot resolve diaspora handle ".$author." for ".$recipient); + return false; + } - $link_text = '[img]' . $remote_photo_path . $remote_photo_name . '[/img]' . "\n"; + $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 + ); - $link_text = scale_external_images($link_text, true, - array($remote_photo_name, 'scaled_full_' . $remote_photo_name)); + // find the contact record we just created - if(strpos($parent_item['body'],$link_text) === false) { + $contact_record = self::contact_by_handle($importer["uid"],$author); - $parent_item['body'] = $link_text . $parent_item['body']; + if (!$contact_record) { + logger("unable to locate newly created contact record."); + return; + } - $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']) + $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", + intval($importer["uid"]) ); - put_item_in_cache($parent_item, true); - update_thread($parent_item['id']); - } - - return; -} - - + if($g && intval($g[0]["def_gid"])) + group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]); -function diaspora_like($importer,$xml,$msg) { + if($importer["page-flags"] == PAGE_NORMAL) { - $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)); + $hash = random_string().(string)time(); // Generate a confirm_key - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); + $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 { - // likes on comments not supported here and likes on photos not supported by Diaspora + // 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) 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; + + $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"]) + ); -// if($target_type !== 'Post') -// return; + $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); + if($u) + $ret = self::send_share($u[0], $contact_record); + } - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_like: cannot find contact: ' . $msg['author']); - return; + return true; } - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_like: Ignoring this author.'); - return 202; - } + /** + * @brief Fetches a message with a given guid + * + * @param string $guid message guid + * @param string $orig_author handle of the original post + * @param string $author handle of the sharer + * + * @return array The fetched item + */ + 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, 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(); + 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]; + } - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); + 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(!count($r)) { - $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); + 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); + } - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); - } + // 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 ($result) { - logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG); + if ($r) + return $r[0]; - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); + } } + return false; } - if(! count($r)) { - logger('diaspora_like: parent item not found: ' . $guid); - return; - } - - $parent_item = $r[0]; + /** + * @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 int the message id + */ + 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)); + $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; - $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; - } + $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) + return false; - /* How Diaspora performs "like" signature checking: + $orig_url = App::get_baseurl()."/display/".$original_item["guid"]; - - 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 - */ + $datarray = array(); - // 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; + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; - $signed_data = $positive . ';' . $guid . ';' . $target_type . ';' . $parent_guid . ';' . $diaspora_handle; + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - $key = $msg['key']; + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; - 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 + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; - $parent_author_signature = base64_decode($parent_author_signature); + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; - if (!rsa_verify($signed_data,$parent_author_signature,$key,'sha256') AND - !rsa_verify($old_signed_data,$parent_author_signature,$key,'sha256')) { + $datarray["object"] = $xml; - 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'] + $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]"; - $author_signature = base64_decode($author_signature); + $datarray["tag"] = $original_item["tag"]; + $datarray["app"] = $original_item["app"]; - if (!rsa_verify($signed_data,$author_signature,$key,'sha256') AND - !rsa_verify($old_signed_data,$author_signature,$key,'sha256')) { + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - logger('diaspora_like: like creator verification failed.'); - return; - } - } + $datarray["object-type"] = $original_item["object-type"]; - // Phew! Everything checks out. Now create an item. + self::fetch_guid($datarray); + $message_id = item_store($datarray); - // 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 ($message_id) + logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - if(! is_array($person)) { - logger('diaspora_like: unable to find author details'); - return; - } + return $message_id; } - $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('' . "\n") ; - $body = $parent_item['body']; - - $obj = <<< EOT - - - $objtype - 1 - {$parent_item['uri']} - $link - - $body - -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; - } + /** + * @brief Processes retractions + * + * @param array $importer Array of the importer user + * @param array $contact The contact of the item owner + * @param object $data The message object + * + * @return bool success + */ + 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)); - $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) + $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; - // notify others - proc_run('php','include/notifier.php','comment-import',$message_id); - } + // 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; + } - return; -} + // Check if the sender is the thread owner + $p = q("SELECT `id`, `author-link`, `origin` FROM `item` WHERE `id` = %d", + intval($r[0]["parent"])); -function diaspora_retraction($importer,$xml) { + // 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"]); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $type = notags(unxmlify($xml->type)); + logger("Deleted target ".$target_guid." (".$r[0]["id"].") from user ".$importer["uid"]." parent: ".$p[0]["id"], LOGGER_DEBUG); - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; + // Now check if the retraction needs to be relayed by us + if($p[0]["origin"]) { + // notify others + proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); + } - if($type === 'Person') { - require_once('include/Contact.php'); - contact_remove($contact['id']); - } elseif($type === 'StatusMessage') { - $guid = notags(unxmlify($xml->post_guid)); + return true; + } - $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']); - } + /** + * @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 bool Success + */ + 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; } - } 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']) - ); - 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']); - } + + 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; } - return 202; - // NOTREACHED -} + /** + * @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 int The message id of the newly created item + */ + private function receive_status_message($importer, $data, $xml) { + + $raw_message = unxmlify($data->raw_message); + $guid = notags(unxmlify($data->guid)); + $author = notags(unxmlify($data->author)); + $public = notags(unxmlify($data->public)); + $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; -function diaspora_signed_retraction($importer,$xml,$msg) { + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; + $address = array(); + if ($data->location) + foreach ($data->location->children() AS $fieldname => $data) + $address[$fieldname] = notags(unxmlify($data)); - $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)); + $body = diaspora2bb($raw_message); - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); + $datarray = array(); - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) { - logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['uid']); - return; - } + // Attach embedded pictures to the body + if ($data->photo) { + foreach ($data->photo AS $photo) + $body = "[img]".unxmlify($photo->remote_photo_path). + unxmlify($photo->remote_photo_name)."[/img]\n".$body; + $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; + } else { + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; - $signed_data = $guid . ';' . $type ; - $key = $msg['key']; + // Add OEmbed and other information to the body + if (!self::is_redmatrix($contact["url"])) + $body = add_page_info_to_body($body, false, true); + } - /* How Diaspora performs relayable_retraction signature checking: + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; - - 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 - */ + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - if($parent_author_signature) { + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; - $parent_author_signature = base64_decode($parent_author_signature); + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; - if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_signed_retraction: top-level post owner verification failed'); - return; - } + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; - } else { + $datarray["object"] = $xml; - $sig_decode = base64_decode($sig); + $datarray["body"] = $body; - if(! rsa_verify($signed_data,$sig_decode,$key,'sha256')) { - logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg,true)); - return; - } - } + if ($provider_display_name != "") + $datarray["app"] = $provider_display_name; - 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']) - ); - 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) - ); - - // the existence of parent_author_signature would have meant the parent_author or owner - // is already relaying. - logger('diaspora_signed_retraction: relaying relayable_retraction'); - - proc_run('php','include/notifier.php','drop',$r[0]['id']); - } - } - } - } - } - else - logger('diaspora_signed_retraction: unknown type: ' . $type); + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - return 202; - // NOTREACHED -} + if (isset($address["address"])) + $datarray["location"] = $address["address"]; -function diaspora_profile($importer,$xml,$msg) { + if (isset($address["lat"]) AND isset($address["lng"])) + $datarray["coord"] = $address["lat"]." ".$address["lng"]; - $a = get_app(); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); + self::fetch_guid($datarray); + $message_id = item_store($datarray); + if ($message_id) + logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; + return $message_id; } - $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; - } + /* ************************************************************************************** * + * Here are all the functions that are needed to transmit data with the Diaspora protocol * + * ************************************************************************************** */ - $keywords = implode(", ", $keywords); + /** + * @brief returnes the handle of a contact + * + * @param array $me contact array + * + * @return string the handle in the format user@domain.tld + */ + private function my_handle($contact) { + if ($contact["addr"] != "") + return $contact["addr"]; - $handle_parts = explode("@", $diaspora_handle); - $nick = $handle_parts[0]; + // 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. + if ($contact["nickname"] != "") + $nick = $contact["nickname"]; + else + $nick = $contact["nick"]; - if($name === '') { - $name = $handle_parts[0]; + return $nick."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); } - if( preg_match("|^https?://|", $image_url) === 0) { - $image_url = "http://" . $handle_parts[1] . $image_url; - } + /** + * @brief Creates the envelope for a public message + * + * @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 string The envelope + */ + private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { -/* $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);*/ + logger("Message: ".$msg, LOGGER_DATA); - require_once('include/Photo.php'); + $handle = self::my_handle($user); - update_contact_avatar($image_url,$importer['uid'],$contact['id']); + $b64url_data = base64url_encode($msg); - // Generic birthday. We don't know the timezone. The year is irrelevant. + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); - $birthday = str_replace('1000','1901',$birthday); + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; - if ($birthday != "") - $birthday = datetime_convert('UTC','UTC',$birthday,'Y-m-d'); + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); - // 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 + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); - if(substr($birthday,5) === substr($contact['bd'],5)) - $birthday = $contact['bd']; + $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))); - /// @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). + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); - $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']) - ); + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); - 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']); + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; } - 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']) - ); - } + /** + * @brief Creates the envelope for a private message + * + * @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 string The envelope + */ + 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; } - } */ - return; + $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); -function diaspora_share($me,$contact) { - $a = get_app(); - $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - $theiraddr = $contact['addr']; + $handle = self::my_handle($user); - $tpl = get_markup_template('diaspora_share.tpl'); - $msg = replace_macros($tpl, array( - '$sender' => $myaddr, - '$recipient' => $theiraddr - )); + $padded_data = pkcs5_pad($msg,16); + $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); - $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'])); + $b64_data = base64_encode($inner_encrypted); - return(diaspora_transmit($owner,$contact,$slap, false)); -} -function diaspora_unshare($me,$contact) { + $b64url_data = base64url_encode($b64_data); + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); - $a = get_app(); - $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; - $tpl = get_markup_template('diaspora_retract.tpl'); - $msg = replace_macros($tpl, array( - '$guid' => $me['guid'], - '$type' => 'Person', - '$handle' => $myaddr - )); + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); - $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'])); + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); - return(diaspora_transmit($owner,$contact,$slap, false)); + $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); -function diaspora_send_status($item,$owner,$contact,$public_batch = false) { + $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key)); - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - $theiraddr = $contact['addr']; + $encrypted_outer_key_bundle = ""; + openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey); - $images = array(); + $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); - $title = $item['title']; - $body = $item['body']; + logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA); -/* - // 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. + $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); - $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); - } + $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; } -*/ - //if(strlen($title)) - // $body = "[b]".html_entity_decode($title)."[/b]\n\n".$body; + /** + * @brief Create the envelope for a message + * + * @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 string The message that will be transmitted to other servers + */ + private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { - // convert to markdown - $body = xmlify(html_entity_decode(bb2diaspora($body))); - //$body = bb2diaspora($body); + 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; + } + + /** + * @brief Creates a signature for a message + * + * @param array $owner the array of the owner of the message + * @param array $message The message that is to be signed + * + * @return string The signature + */ + 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")); + } + + /** + * @brief Transmit a message to a target server + * + * @param array $owner the array of the item owner + * @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 bool $queue_run Is the transmission called from the queue? + * @param string $guid message guid + * + * @return int Result of the transmission + */ + 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; + } - // Adding the title - if(strlen($title)) - $body = "## ".html_entity_decode($title)."\n\n".$body; + logger("transmit: ".$logid."-".$guid." ".$dest_url); - 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"; + 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); - $public = (($item['private']) ? 'false' : 'true'); + if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) { + logger("queue message"); - require_once('include/datetime.php'); - $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C'); + $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); + } + } - // 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"] - )); + return(($return_code) ? $return_code : (-1)); } - 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)); + /** + * @brief Builds and transmit messages + * + * @param array $owner the array of the item owner + * @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 bool $spool Should the transmission be spooled or transmitted? + * + * @return int Result of the transmission + */ + private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { - $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']); + $data = array("XML" => array("post" => array($type => $message))); - logger('diaspora_send_status: guid: '.$item['guid'].' result '.$return_code, LOGGER_DEBUG); + $msg = xml::from_array($data, $xml); - if(count($images)) { - diaspora_send_images($item,$owner,$contact,$images,$public_batch); - } - - return $return_code; -} + logger('message: '.$msg, LOGGER_DATA); + logger('send guid '.$guid, LOGGER_DEBUG); -function diaspora_is_reshare($body) { - $body = trim($body); + $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(false); + 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); - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(false); + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - $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); + return $return_code; + } - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; + /** + * @brief Sends a "share" message + * + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * + * @return int The result of the transmission + */ + public static function send_share($owner,$contact) { - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; + $message = array("sender_handle" => self::my_handle($owner), + "recipient_handle" => $contact["addr"]); - 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); - } + return self::build_and_transmit($owner, $contact, "request", $message); } - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; + /** + * @brief sends an "unshare" + * + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * + * @return int The result of the transmission + */ + public static function send_unshare($owner,$contact) { - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; + $message = array("post_guid" => $owner["guid"], + "diaspora_handle" => self::my_handle($owner), + "type" => "Person"); - $ret= array(); + return self::build_and_transmit($owner, $contact, "retraction", $message); + } - $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); - if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) - return(false); + /** + * @brief Checks a message body if it is a reshare + * + * @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 array|bool Reshare details or "false" if no reshare + */ + public static function is_reshare($body, $complete = true) { + $body = trim($body); - $link = ""; - preg_match("/link='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(false); - preg_match('/link="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(false); - $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); - if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) - 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); - return($ret); -} + // If we don't do the complete check we quit here + if (!$complete) + return true; -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')) - )); + $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]; - logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$r[0]['guid'], LOGGER_DEBUG); + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; - $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)); + $ret= array(); - diaspora_transmit($owner,$contact,$slap,$public_batch,false,$r[0]['guid']); - } + $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]; -function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { + preg_match('/link="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); -// $theiraddr = $contact['addr']; + $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); + if (($ret["root_guid"] == $link) OR (trim($ret["root_guid"]) == "")) + return(false); - // 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; + return($ret); } - $text = html_entity_decode(bb2diaspora($item['body'])); + /** + * @brief Sends a post + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * @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) { - // sign it + $myaddr = self::my_handle($owner); - if($like) - $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; - else - $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; + $public = (($item["private"]) ? "false" : "true"); - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); - $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) - )); + // 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"]); - logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); + $type = "reshare"; + } else { + $title = $item["title"]; + $body = $item["body"]; - $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)); + // convert to markdown + $body = html_entity_decode(bb2diaspora($body)); - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); -} + // 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"; + } + } -function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { + $location = array(); + if ($item["location"] != "") + $location["address"] = $item["location"]; - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); -// $theiraddr = $contact['addr']; + if ($item["coord"] != "") { + $coord = explode(" ", $item["coord"]); + $location["lat"] = $coord[0]; + $location["lng"] = $coord[1]; + } - // 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; + $message = array("raw_message" => $body, + "location" => $location, + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); - $like = false; - $relay_retract = false; - $sql_sign_id = 'iid'; - if( $item['deleted']) { - $relay_retract = true; + if (count($location) == 0) + unset($message["location"]); - $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); + $type = "status_message"; + } - $sql_sign_id = 'retract_iid'; - $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - elseif($item['verb'] === ACTIVITY_LIKE) { - $like = true; - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); -// $positive = (($item['deleted']) ? 'false' : 'true'); - $positive = 'true'; + /** + * @brief Creates a "like" object + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * + * @return array The data for a "like" + */ + private function construct_like($item, $owner) { - $tpl = get_markup_template('diaspora_like_relay.tpl'); - } - else { // item is a comment - $tpl = get_markup_template('diaspora_comment_relay.tpl'); - } + $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]; - // fetch the original signature if the relayable was created by a Diaspora - // or DFRN user. Relayables for other networks are not supported. + $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" => "", + "diaspora_handle" => self::my_handle($owner))); + } + + /** + * @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 array The data for a comment + */ + private function construct_comment($item, $owner) { + + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) + ); - $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']; + if (!$p) + return false; - // Split the signed text - $signed_parts = explode(";", $signed_text); + $parent = $p[0]; - // Remove the parent guid - array_shift($signed_parts); + $text = html_entity_decode(bb2diaspora($item["body"])); + + return(array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => "", + "text" => $text, + "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 Target of the communication + * @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) { + $message = self::construct_like($item, $owner); + $type = "like"; + } else { + $message = self::construct_comment($item, $owner); + $type = "comment"; + } - // Remove the comment guid - array_shift($signed_parts); + if (!$message) + return false; - // Remove the handle - array_pop($signed_parts); + $message["author_signature"] = self::signature($owner, $message); - // Glue the parts together - $text = implode(";", $signed_parts); + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - 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; + /** + * @brief Creates a message from a signature record entry + * + * @param array $item The item that will be exported + * @param array $signature The entry of the "sign" record + * + * @return string The message + */ + private function message_from_signature($item, $signature) { - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - } + // 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); - // Sign the relayable with the top-level owner's signature - $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + // Remove the parent guid + $parent_guid = array_shift($signed_parts); - $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) - )); + // Remove the handle + $handle = array_pop($signed_parts); - logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); + // Glue the parts together + $text = implode(";", $signed_parts); - $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)); + $message = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } + return $message; + } + + /** + * @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 + * @param array $contact Target of the communication + * @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"]) + return self::send_retraction($item, $owner, $contact, $public_batch, true); + elseif ($item['verb'] === ACTIVITY_LIKE) + $type = "like"; + else + $type = "comment"; - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); + 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 `iid` = %d LIMIT 1", + intval($item["id"])); + if (!$r) { + logger("Couldn't fetch signatur for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + return false; + } -function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) { + $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_signature($item, $signature); + else {// New way + $msg = json_decode($signature['signed_text'], true); + + $message = array(); + 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"; + } - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); + $message[$field] = $data; + } + } else + logger("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$signature['signed_text'], LOGGER_DEBUG); + } - // Check whether the retraction is for a top-level post or whether it's a relayable - if( $item['uri'] !== $item['parent-uri'] ) { + $message["parent_author_signature"] = self::signature($owner, $message); - $tpl = get_markup_template('diaspora_relay_retraction.tpl'); - $target_type = (($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - } - else { + logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); - $tpl = get_markup_template('diaspora_signed_retract.tpl'); - $target_type = 'StatusMessage'; + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - $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'))) - )); + /** + * @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 Target of the communication + * @param bool $public_batch Is it a public post? + * @param bool $relay Is the retraction transmitted from a relay? + * + * @return int The result of the transmission + */ + public static function send_retraction($item, $owner, $contact, $public_batch = false, $relay = false) { - logger('send guid '.$item['guid'], LOGGER_DEBUG); + $itemaddr = self::handle_from_contact($item["contact-id"], $item["gcontact-id"]); - $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)); + // 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"; + } - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); -} + if ($relay AND ($item["uri"] !== $item["parent-uri"])) + $signature = "parent_author_signature"; + else + $signature = "target_author_signature"; -function diaspora_send_mail($item,$owner,$contact) { + $signed_text = $item["guid"].";".$target_type; - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); + $message = array("target_guid" => $item['guid'], + "target_type" => $target_type, + "sender_handle" => $itemaddr, + $signature => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); - $r = q("select * from conv where id = %d and uid = %d limit 1", - intval($item['convid']), - intval($item['uid']) - ); + logger("Got message ".print_r($message, true), LOGGER_DEBUG); - if(! count($r)) { - logger('diaspora_send_mail: conversation not found.'); - return; + return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); } - $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'])); + /** + * @brief Sends a mail + * + * @param array $item The item that will be exported + * @param array $owner The owner + * @param array $contact Target of the communication + * + * @return int The result of the transmission + */ + public static function send_mail($item, $owner, $contact) { + $myaddr = self::my_handle($owner); -} - -function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false,$guid = "") { - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - return 200; - } + $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($item["convid"]), + intval($item["uid"]) + ); - $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; - } + 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"] + ); - logger('diaspora_transmit: '.$logid.'-'.$guid.' '.$dest_url); + $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( (! $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(); + if ($item["reply"]) { + $message = $msg; + $type = "message"; } else { - logger('diaspora_transmit: test_mode'); - return 200; + $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"]); } - logger('diaspora_transmit: '.$logid.'-'.$guid.' returns: '.$return_code); + /** + * @brief Sends profile data + * + * @param int $uid The user id + */ + public static function send_profile($uid) { - if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) { - logger('diaspora_transmit: queue message'); + if (!$uid) + return; - $r = q("SELECT id from queue where cid = %d and network = '%s' and content = '%s' and batch = %d limit 1", - intval($contact['id']), + $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' + AND `uid` = %d AND `rel` != %d", dbesc(NETWORK_DIASPORA), - dbesc($slap), - intval($public_batch) + 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(count($r)) { - logger('diaspora_transmit: add_to_queue ignored - identical item already in queue'); + + 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 { - // queue message for redelivery - add_to_queue($contact['id'],NETWORK_DIASPORA,$slap,$public_batch); + + $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); + } + + /** + * @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 + */ + public static 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; + } - return(($return_code) ? $return_code : (-1)); -} + $r = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval($contact['uid'])); + if(!$r) + return false; -function diaspora_fetch_relay() { + $contact["uprvkey"] = $r[0]['prvkey']; - $serverdata = get_config("system", "relay_server"); - if ($serverdata == "") - return array(); + $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($post_id)); + if (!$r) + return false; - $relay = array(); + if (!in_array($r[0]["verb"], array(ACTIVITY_LIKE, ACTIVITY_DISLIKE))) + return false; - $servers = explode(",", $serverdata); + $message = self::construct_like($r[0], $contact); + $message["author_signature"] = self::signature($contact, $message); - foreach($servers AS $server) { - $server = trim($server); - $batch = $server."/receive/public"; + // 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) - $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); + $signed_text = $message["positive"].";".$message["guid"].";".$message["target_type"].";". + $message["parent_guid"].";".$message["diaspora_handle"]; - if (!$relais) { - $addr = "relay@".str_replace("http://", "", normalise_link($server)); + 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"]) + ); - $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()) - ); + // 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)) + //); - $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]; + logger('Stored diaspora like signature'); + return true; } - return $relay; + /** + * @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 + */ + public static 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/discover_poco.php b/include/discover_poco.php index a8f670334b..0b468faea1 100644 --- a/include/discover_poco.php +++ b/include/discover_poco.php @@ -20,22 +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'); - $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.'); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") + if (App::maxload_reached()) return; - } - } if(($argc > 2) && ($argv[1] == "dirsearch")) { $search = urldecode($argv[2]); @@ -50,21 +42,10 @@ 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'); - } - exit; - } - } + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") + 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/dsprphotoq.php b/include/dsprphotoq.php deleted file mode 100644 index 0d8088d4bd..0000000000 --- a/include/dsprphotoq.php +++ /dev/null @@ -1,55 +0,0 @@ - 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/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 = '' . $h . ''; diff --git a/include/feed.php b/include/feed.php index eb91f7efd4..293de3cc96 100644 --- a/include/feed.php +++ b/include/feed.php @@ -2,7 +2,18 @@ require_once("include/html2bbcode.php"); require_once("include/items.php"); -function feed_import($xml,$importer,&$contact, &$hub) { +/** + * @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(); @@ -14,18 +25,19 @@ function feed_import($xml,$importer,&$contact, &$hub) { $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(); // 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 +48,29 @@ 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; - - //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; - //} + $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; + + $author["author-id"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue; + + if ($author["author-link"] == "") + $author["author-link"] = $author["author-id"]; + + if ($author["author-link"] == "") { + $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: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,7 +80,13 @@ 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; + $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; @@ -69,9 +97,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 +115,22 @@ function feed_import($xml,$importer,&$contact, &$hub) { $entries = $xpath->query('/rss/channel/item'); } - //if ($author["author-link"] == "") + if (!$simulate) { $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"]; + + // This is no field in the item table. So we have to unset it. + unset($author["author-nick"]); + unset($author["author-id"]); + } $header = array(); $header["uid"] = $importer["uid"]; @@ -120,6 +153,8 @@ function feed_import($xml,$importer,&$contact, &$hub) { if (!is_object($entries)) return; + $items = array(); + $entrylist = array(); foreach ($entries AS $entry) @@ -201,13 +236,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 +307,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); } ?> diff --git a/include/follow.php b/include/follow.php index 22ff079b63..d0411a466a 100644 --- a/include/follow.php +++ b/include/follow.php @@ -1,5 +1,6 @@ user,$contact); - logger('mod_follow: diaspora_share returns: ' . $ret); + $ret = diaspora::send_share($a->user,$contact); + logger('share returns: '.$ret); } } diff --git a/include/group.php b/include/group.php index a1375e00df..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; @@ -215,7 +218,7 @@ function mini_group_select($uid,$gid = 0) { /** * @brief Create group sidebar widget - * + * * @param string $every * @param string $each * @param string $editmode @@ -234,7 +237,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro return ''; $groups = array(); - + $groups[] = array( 'text' => t('Everybody'), 'id' => 0, @@ -255,7 +258,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 +267,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro } else { $groupedit = null; } - + $groups[] = array( 'id' => $rr['id'], 'cid' => $cid, @@ -362,17 +365,41 @@ 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()) ); 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/include/identity.php b/include/identity.php index ec66225d0f..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 = ""; @@ -332,9 +333,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/items.php b/include/items.php index 798ee56958..4627b10ca2 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. @@ -393,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')) @@ -509,6 +499,10 @@ 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'] == "")) + logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG); + if ($arr['plink'] == "") { $a = get_app(); $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']); @@ -713,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); } @@ -768,9 +762,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); @@ -891,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); @@ -1255,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; } @@ -1992,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']); @@ -2127,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..15633fc767 100644 --- a/include/like.php +++ b/include/like.php @@ -1,4 +1,5 @@ 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 - // 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/include/nav.php b/include/nav.php index 6512d35609..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'], ); @@ -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/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" diff --git a/include/notifier.php b/include/notifier.php index 6c42f19c6a..ffbb22e7bf 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -223,13 +223,13 @@ 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'); $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) { @@ -536,7 +550,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(); @@ -628,13 +642,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) diff --git a/include/onepoll.php b/include/onepoll.php index 6fb191f73d..eb1045de14 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'); @@ -60,18 +59,10 @@ 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"); - } - exit; - } - } + // 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)) + return; $d = datetime_convert(); diff --git a/include/ostatus.php b/include/ostatus.php index 00022f8c6c..b798a605f9 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -1,4 +1,8 @@ evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; + $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue; + + $aliaslink = $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; -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 - -function ostatus_check_follow_friends() { - $r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''"); - - if (!$r) - return; - - foreach ($r AS $contact) { - ostatus_follow_friends($contact["uid"], $contact["v"]); - set_pconfig($contact["uid"], "system", "ostatus_legacy_contact", ""); - } -} - -// This function doesn't work reliable by now. -function ostatus_follow_friends($uid, $url) { - $contact = probe_url($url); - - if (!$contact) - return; - - $api = $contact["baseurl"]."/api/"; + $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($aliaslink)), dbesc(NETWORK_STATUSNET)); + if ($r) { + $contact = $r[0]; + $author["contact-id"] = $r[0]["id"]; + } else + $author["contact-id"] = $contact["id"]; - // Fetching friends - $data = z_fetch_url($api."statuses/friends.json?screen_name=".$contact["nick"]); + $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); + } - if (!$data["success"]) - return; + $displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($displayname != "") + $author["author-name"] = $displayname; - $friends = json_decode($data["body"]); + $author["owner-name"] = $author["author-name"]; + $author["owner-link"] = $author["author-link"]; + $author["owner-avatar"] = $author["author-avatar"]; - 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); - } - } -} + // Only update the contacts if it is an OStatus contact + if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { -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); - } + // Update contact data - $displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($displayname != "") - $author["author-name"] = $displayname; + // This query doesn't seem to work + // $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; + // if ($value != "") + // $contact["notify"] = $value; - $author["owner-name"] = $author["author-name"]; - $author["owner-link"] = $author["author-link"]; - $author["owner-avatar"] = $author["author-avatar"]; + // 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; - // Only update the contacts if it is an OStatus contact - if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { - // Update contact data + $value = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["alias"] = $value; - $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; - if ($value != "") - $contact["notify"] = $value; + $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; - $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["alias"] = $value; + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; - $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["name"] = $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:preferredUsername/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["nick"] = $value; + $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; - $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["about"] = html2bbcode($value); + 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"])) { - $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["location"] = $value; + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["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"])); - logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); + poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], + "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); + } - 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"])); + if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) { + logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); - poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], - "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); - } + update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); + } - if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) { - logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); + // 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', + `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(datetime_convert()), dbesc(datetime_convert()), intval($cid)); + + // Update the avatar + update_contact_avatar($author["author-avatar"], 0, $cid); + } - update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); + $contact["generation"] = 2; + $contact["photo"] = $author["author-avatar"]; + update_gcontact($contact); } - - /// @todo Add the "addr" field - $contact["generation"] = 2; - $contact["photo"] = $author["author-avatar"]; - update_gcontact($contact); + return($author); } - return($author); -} - -function ostatus_salmon_author($xml, $importer) { - $a = get_app(); - - if ($xml == "") - return; + /** + * @brief Fetches author data from a given XML string + * + * @param string $xml The XML + * @param array $importer user record of the importing user + * + * @return array Array of author related entries for the item + */ + public static function salmon_author($xml, $importer) { + + if ($xml == "") + return; - $doc = new DOMDocument(); - @$doc->loadXML($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); + $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'); + $entries = $xpath->query('/atom:entry'); - foreach ($entries AS $entry) { - // fetch the author - $author = ostatus_fetchauthor($xpath, $entry, $importer, $contact, true); - return $author; + foreach ($entries AS $entry) { + // fetch the author + $author = self::fetchauthor($xpath, $entry, $importer, $contact, true); + return $author; + } } -} - -function ostatus_import($xml,$importer,&$contact, &$hub) { - $a = get_app(); + /** + * @brief Imports an XML string containing OStatus elements + * + * @param string $xml The XML + * @param array $importer user record of the importing user + * @param $contact + * @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); + logger("Import OStatus message", LOGGER_DEBUG); - if ($xml == "") - return; + if ($xml == "") + return; - $doc = new DOMDocument(); - @$doc->loadXML($xml); + //$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); + } - $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); + $header = array(); + $header["uid"] = $importer["uid"]; + $header["network"] = NETWORK_OSTATUS; + $header["type"] = "remote"; + $header["wall"] = 0; + $header["origin"] = 0; + $header["gravity"] = GRAVITY_PARENT; - $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); - } + // 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; - $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'); + if ($first_child == "feed") + $entries = $xpath->query('/atom:feed/atom:entry'); + else + $entries = $xpath->query('/atom:entry'); - $conversation = ""; - $conversationlist = array(); - $item_id = 0; + $conversation = ""; + $conversationlist = array(); + $item_id = 0; - // Reverse the order of the entries - $entrylist = array(); + // Reverse the order of the entries + $entrylist = array(); - foreach ($entries AS $entry) - $entrylist[] = $entry; + foreach ($entries AS $entry) + $entrylist[] = $entry; - foreach (array_reverse($entrylist) AS $entry) { + foreach (array_reverse($entrylist) AS $entry) { - $mention = false; + $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); + // 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"]; + $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); + $item = array_merge($header, $author); - // Now get the item - $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; + // 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; - } + $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; + $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; + 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; + $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; - } + /// @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_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"] == 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"] == 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)); + 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; - } + $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; - } + 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)); + // 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; + $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 = ""; + $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; + $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]"; - } + $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; + $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; + $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); + // Is it a repeated post? + if ($repeat_of != "") { + $activityobjects = $xpath->query('activity:object', $entry)->item(0); - if (is_object($activityobjects)) { + 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_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; + $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 = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue; - if (!isset($orig_link)) - $orig_link = ostatus_convert_href($orig_uri); + 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_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_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue; - $orig_contact = $contact; - $orig_author = ostatus_fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false); + $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"]; @@ -500,1090 +504,1511 @@ 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; + $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 ($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 (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 (!$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); + 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); + $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"])); + // 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"]; + if ($r) { + $item["type"] = 'remote-comment'; + $item["gravity"] = GRAVITY_COMMENT; + } + } else + $item["parent-uri"] = $item["uri"]; - $item_id = ostatus_completion($conversation, $importer["uid"], $item); + $item_id = self::completion($conversation, $importer["uid"], $item, $self); - if (!$item_id) { - logger("Error storing item", LOGGER_DEBUG); - continue; - } + if (!$item_id) { + logger("Error storing item", LOGGER_DEBUG); + continue; + } - logger("Item was stored with id ".$item_id, LOGGER_DEBUG); + logger("Item was stored with id ".$item_id, LOGGER_DEBUG); + } } -} -function ostatus_convert_href($href) { - $elements = explode(":",$href); + /** + * @brief Create an url out of an uri + * + * @param string $href URI in the format "parameter1:parameter1:..." + * + * @return string URL in the format http(s)://.... + */ + public static function convert_href($href) { + $elements = explode(":",$href); - if ((count($elements) <= 2) OR ($elements[0] != "tag")) - return $href; + if ((count($elements) <= 2) OR ($elements[0] != "tag")) + return $href; - $server = explode(",", $elements[1]); - $conversation = explode("=", $elements[2]); + $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($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]; - 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]; + /** + * @brief Checks if there are entries in conversations that aren't present on our side + * + * @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'); + + $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; - return $href; -} + 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; + } -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'); - if ($last AND !$override) { - $next = $last + ($poll_interval * 60); - if ($next > time()) { - logger('poll interval not reached'); - return; + $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()); } - logger('cron_start'); + /** + * @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) { - $start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60)); + // 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 ($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)); + if (isset($actor->url)) + $contact["url"] = $actor->url; - foreach ($conversations AS $conversation) { - ostatus_completion($conversation['url'], $conversation['uid']); - } + if (isset($actor->displayName)) + $contact["name"] = $actor->displayName; - logger('cron_end'); + if (isset($actor->portablecontacts_net->displayName)) + $contact["name"] = $actor->portablecontacts_net->displayName; - set_config('system','ostatus_last_poll', time()); -} + if (isset($actor->portablecontacts_net->preferredUsername)) + $contact["nick"] = $actor->portablecontacts_net->preferredUsername; -function ostatus_completion($conversation_url, $uid, $item = array()) { + if (isset($actor->id)) + $contact["alias"] = $actor->id; - $a = get_app(); + if (isset($actor->summary)) + $contact["about"] = $actor->summary; - $item_stored = -1; + if (isset($actor->portablecontacts_net->note)) + $contact["about"] = $actor->portablecontacts_net->note; - $conversation_url = ostatus_convert_href($conversation_url); + if (isset($actor->portablecontacts_net->addresses->formatted)) + $contact["location"] = $actor->portablecontacts_net->addresses->formatted; - // 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)) { - //$arr["app"] .= " (OStatus-NoCompletion)"; - $item_stored = item_store($item, true); - return($item_stored); + + 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); } - // 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; + /** + * @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; } - $conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as"; - $pageno = 1; - $items = array(); + /** + * @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) { - logger('fetching conversation url '.$conv.' for user '.$uid); + $details = array(); - do { - $conv_arr = z_fetch_url($conv."?page=".$pageno); + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + $uid, normalise_link($actor), NETWORK_STATUSNET); - // 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"]; + 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); - $conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as); - $conv_as = json_decode($conv_as); + // Adding a global contact + /// @TODO Use this data for the post + $details["global_contact_id"] = get_contact($actor, 0); - $no_of_items = sizeof($items); + logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); - if (@is_array($conv_as->items)) - foreach ($conv_as->items AS $single_item) - $items[$single_item->id] = $single_item; + $details["contact_id"] = $contact_id; + $details["network"] = NETWORK_OSTATUS; - if ($no_of_items == sizeof($items)) - break; + $details["not_following"] = true; + } - $pageno++; + return $details; + } - } while (true); + /** + * @brief Stores an item and completes the thread + * + * @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 integer The item id of the posted item array + */ + private function completion($conversation_url, $uid, $item = array(), $self = "") { - logger('fetching conversation done. Found '.count($items).' items'); + /// @todo This function is totally ugly and has to be rewritten totally - if (!sizeof($items)) { - if (count($item) > 0) { - //$arr["app"] .= " (OStatus-NoConvFetched)"; - $item_stored = item_store($item, true); + $item_stored = -1; - 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); - } + $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); - } else - return(-3); - } + } + + // 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) { - $items = array_reverse($items); + // Update the gcontact table + self::conv_fetch_actor($single_conv->actor); - $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid)); - $importer = $r[0]; + // Test - remove before flight + //$tempfile = tempnam(get_temppath(), "conversation"); + //file_put_contents($tempfile, json_encode($single_conv)); - foreach ($items as $single_conv) { + $mention = false; - // Test - remove before flight - //$tempfile = tempnam(get_temppath(), "conversation"); - //file_put_contents($tempfile, json_encode($single_conv)); + if (isset($single_conv->object->id)) + $single_conv->id = $single_conv->object->id; - $mention = false; + $plink = self::convert_href($single_conv->id); + if (isset($single_conv->object->url)) + $plink = self::convert_href($single_conv->object->url); - if (isset($single_conv->object->id)) - $single_conv->id = $single_conv->object->id; + if (@!$single_conv->id) + continue; - $plink = ostatus_convert_href($single_conv->id); - if (isset($single_conv->object->url)) - $plink = ostatus_convert_href($single_conv->object->url); + logger("Got id ".$single_conv->id, LOGGER_DEBUG); - if (@!$single_conv->id) - continue; + if ($first_id == "") { + $first_id = $single_conv->id; - logger("Got id ".$single_conv->id, LOGGER_DEBUG); + // 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"] != "")) { - if ($first_id == "") { - $first_id = $single_conv->id; + $new_parent = true; - // 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_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"]; + $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 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); + // 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); } - } else { - // Option 1: We hadn't got the real thread starter - // We have to clean up our existing messages. + } elseif ($parent["uri"] == "") { $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"]; + $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; - } + // "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; - } + // 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); + $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]; + 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); + // 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 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"])); + // 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"])); + // 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"])); + // 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"]); + // 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; + // 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; } - continue; - } + if (is_array($single_conv->to)) + foreach($single_conv->to AS $to) + if ($importer["nurl"] == normalise_link($to->id)) + $mention = true; - 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; - $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"]); - $contact = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", - $uid, normalise_link($actor), NETWORK_STATUSNET); + // 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; + } - if (count($contact)) { - logger("Found contact for url ".$actor, LOGGER_DEBUG); - $contact_id = $contact[0]["id"]; - } else { - logger("No contact found for url ".$actor, LOGGER_DEBUG); + $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"; - // 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); + $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); - $contact_id = $parent["contact-id"]; - } + // 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]; - $arr = array(); - $arr["network"] = NETWORK_OSTATUS; - $arr["uri"] = $single_conv->id; - $arr["plink"] = $plink; - $arr["uid"] = $uid; - $arr["contact-id"] = $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"; + logger("Found reshared item ".$single_conv->object->id); - //$arr["app"] .= " (Conversation)"; + // $single_conv->object->context->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); + if (isset($single_conv->object->object->id)) + $arr["uri"] = $single_conv->object->object->id; + else + $arr["uri"] = $single_conv->object->id; - // 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]; + 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); - logger("Found reshared item ".$single_conv->object->id); + 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)); - // $single_conv->object->context->conversation; + $arr["plink"] = $plink; - if (isset($single_conv->object->object->id)) - $arr["uri"] = $single_conv->object->object->id; - else - $arr["uri"] = $single_conv->object->id; + $arr["created"] = $single_conv->object->published; + $arr["edited"] = $single_conv->object->published; - 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); + $arr["author-name"] = $single_conv->object->actor->displayName; + if ($arr["owner-name"] == '') + $arr["author-name"] = $single_conv->object->actor->contact->displayName; - 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["author-link"] = $single_conv->object->actor->url; + $arr["author-avatar"] = $single_conv->object->actor->image->url; - $arr["plink"] = $plink; + $arr["app"] = $single_conv->object->provider->displayName."#"; + //$arr["verb"] = $single_conv->object->verb; - $arr["created"] = $single_conv->object->published; - $arr["edited"] = $single_conv->object->published; + $arr["location"] = $single_conv->object->location->displayName; + $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon); + } - $arr["author-name"] = $single_conv->object->actor->displayName; - if ($arr["owner-name"] == '') - $arr["author-name"] = $single_conv->object->actor->contact->displayName; + if ($arr["location"] == "") + unset($arr["location"]); - $arr["author-link"] = $single_conv->object->actor->url; - $arr["author-avatar"] = $single_conv->object->actor->image->url; + if ($arr["coord"] == "") + unset($arr["coord"]); - $arr["app"] = $single_conv->object->provider->displayName."#"; - //$arr["verb"] = $single_conv->object->verb; + // 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["location"] = $single_conv->object->location->displayName; - $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon); - } + } + + $newitem = item_store($arr); + if (!$newitem) { + logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG); + continue; + } - if ($arr["location"] == "") - unset($arr["location"]); + if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) { + $item = array(); + $item_stored = $newitem; + } - if ($arr["coord"] == "") - unset($arr["coord"]); + logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG); - // 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]; + // Add the conversation entry (but don't fetch the whole conversation) + self::store_conversation($newitem, $conversation_url); - //$arr["app"] .= " (OStatus)"; + // 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]; + } } - $newitem = item_store($arr); - if (!$newitem) { - logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG); - continue; + 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 (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) { - $item = array(); - $item_stored = $newitem; + return($item_stored); + } + + /** + * @brief Stores conversation data into the database + * + * @param integer $itemid The id of the item + * @param string $conversation_url The uri of the conversation + */ + 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); } + } - logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG); + /** + * @brief Checks if the current post is a reshare + * + * @param array $item The item array of thw post + * + * @return string The guid if the post is a reshare + */ + 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; + } + + /** + * @brief Cleans the body of a post if it contains picture links + * + * @param string $body The body + * + * @return string The cleaned body + */ + 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"]; - // Add the conversation entry (but don't fetch the whole conversation) - ostatus_store_conversation($newitem, $conversation_url); + // Is it a remote picture? Then make a smaller preview here + $preview = proxy_url($preview, false, PROXY_SIZE_SMALL); - // 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]; + // 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; + } + + /** + * @brief Adds the header elements to the XML document + * + * @param object $doc XML document + * @param array $owner Contact data of the poster + * + * @return object header root element + */ + 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); + 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)); + + $author = self::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" => App::get_baseurl()."/sup", + /// "rel" => "http://api.friendfeed.com/2008/03#sup", + /// "type" => "application/json"); + /// xml::add_element($doc, $root, "link", "", $attributes); + + self::hublinks($doc, $root); + + $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; } - if (($item_stored < 0) AND (count($item) > 0)) { - //$arr["app"] .= " (OStatus-NoConvFound)"; - $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); + /** + * @brief Add the link to the push hubs to the XML document + * + * @param object $doc XML document + * @param object $root XML root element where the hub links are added + */ + 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")); + } + } } } - return($item_stored); -} + /** + * @brief Adds attachement data to the XML document + * + * @param object $doc XML document + * @param object $root XML root element where the hub links are added + * @param array $item Data of the item that is to be posted + */ + 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"]); + 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; + } -function ostatus_store_conversation($itemid, $conversation_url) { - global $a; + if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) { + $photodata = get_photo_info($siteinfo["image"]); - $conversation_url = ostatus_convert_href($conversation_url); + $attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]); + xml::add_element($doc, $root, "link", "", $attributes); + } - $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)); + $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 (!$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); - } -} + if(intval($matches[2])) + $attributes["length"] = intval($matches[2]); -function get_reshared_guid($item) { - $body = trim($item["body"]); + if(trim($matches[4]) != "") + $attributes["title"] = trim($matches[4]); - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(""); + xml::add_element($doc, $root, "link", "", $attributes); + } + } + } + } - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(""); + /** + * @brief Adds the author element to the XML document + * + * @param object $doc XML document + * @param array $owner Contact data of the poster + * + * @return object author element + */ + 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"); + 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 = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); - // Skip if there is no shared message in there - if ($body == $attributes) - return(false); + $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); + } - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; + 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)); - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; + if (trim($owner["location"]) != "") { + $element = $doc->createElement("poco:address"); + xml::add_element($doc, $element, "poco:formatted", $owner["location"]); + $author->appendChild($element); + } - return $guid; -} + 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 xml_create_element($doc, $element, $value = "", $attributes = array()) { - $element = $doc->createElement($element, xmlify($value)); + 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"])); + } - foreach ($attributes AS $key => $value) { - $attribute = $doc->createAttribute($key); - $attribute->value = xmlify($value); - $element->appendChild($attribute); + return $author; } - return $element; -} -function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) { - $element = xml_create_element($doc, $element, $value, $attributes); - $parent->appendChild($element); -} + /** + * @TODO Picture attachments should look like this: + * https://status.pirati.ca/attachment/572819 + * + */ + + /** + * @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 string activity + */ + function construct_verb($item) { + if ($item['verb']) + return $item['verb']; + return ACTIVITY_POST; + } -function ostatus_format_picture_post($body) { - $siteinfo = get_attached_data($body); + /** + * @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 string Object type + */ + 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 (($siteinfo["type"] == "photo")) { - if (isset($siteinfo["preview"])) - $preview = $siteinfo["preview"]; + /** + * @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 bool $toplevel + * + * @return object Entry element + */ + 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 - $preview = $siteinfo["image"]; + return self::note_entry($doc, $item, $owner, $toplevel); + } - // Is it a remote picture? Then make a smaller preview here - $preview = proxy_url($preview, false, PROXY_SIZE_SMALL); + /** + * @brief Adds a source entry to the XML document + * + * @param object $doc XML document + * @param array $contact Array of the contact that is added + * + * @return object Source element + */ + 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)); + + return $source; + } - // 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); + /** + * @brief Fetches contact data from the contact or the gcontact table + * + * @param string $url URL of the contact + * @param array $owner Contact data of the poster + * + * @return array Contact array + */ + 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 (isset($siteinfo["url"])) - $url = $siteinfo["url"]; - else - $url = $siteinfo["image"]; + 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["poll"] = $data["poll"]; - $body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]"; + if (!$contact["alias"]) + $contact["alias"] = $data["alias"]; + } + + if (!isset($contact["alias"])) + $contact["alias"] = $contact["url"]; + + return $contact; } - return $body; -} + /** + * @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 bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * + * @return object Entry element + */ + 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); + } -function ostatus_add_header($doc, $owner) { - $a = get_app(); + $title = self::entry_header($doc, $entry, $owner, $toplevel); - $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); - $doc->appendChild($root); + $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; - $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); + $contact = self::contact_entry($repeated_item['author-link'], $owner); - $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)); + $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); - $author = ostatus_add_author($doc, $owner); - $root->appendChild($author); + $title = $owner["nick"]." repeated a notice by ".$contact["nick"]; - $attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html"); - xml_add_element($doc, $root, "link", "", $attributes); + self::entry_content($doc, $entry, $item, $owner, $title, ACTIVITY_SHARE, false); - /// @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); + $as_object = $doc->createElement("activity:object"); - ostatus_hublinks($doc, $root); + xml::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity"); - $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon"); - xml_add_element($doc, $root, "link", "", $attributes); + self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false); - $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies"); - xml_add_element($doc, $root, "link", "", $attributes); + $author = self::add_author($doc, $contact); + $as_object->appendChild($author); - $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention"); - xml_add_element($doc, $root, "link", "", $attributes); + $as_object2 = $doc->createElement("activity:object"); - $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); + xml::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item)); - return $root; -} + $title = sprintf("New comment by %s", $contact["nick"]); -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")); - } - } - } -} + self::entry_content($doc, $as_object2, $repeated_item, $owner, $title); -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; - } + $as_object->appendChild($as_object2); - if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) { - $photodata = get_photo_info($siteinfo["image"]); + self::entry_footer($doc, $as_object, $item, $owner, false); - $attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]); - xml_add_element($doc, $root, "link", "", $attributes); - } + $source = self::source_entry($doc, $contact); + $as_object->appendChild($source); - $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]); + $entry->appendChild($as_object); - if(intval($matches[2])) - $attributes["length"] = intval($matches[2]); + self::entry_footer($doc, $entry, $item, $owner); - if(trim($matches[4]) != "") - $attributes["title"] = trim($matches[4]); + return $entry; + } - xml_add_element($doc, $root, "link", "", $attributes); - } + /** + * @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 bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * + * @return object Entry element with "like" + */ + 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); } - } -} -function ostatus_add_author($doc, $owner) { - $a = get_app(); + $title = self::entry_header($doc, $entry, $owner, $toplevel); - $r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"])); - if ($r) - $profile = $r[0]; + $verb = NAMESPACE_ACTIVITY_SCHEMA."favorite"; + self::entry_content($doc, $entry, $item, $owner, "Favorite", $verb, false); - $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"]); + $as_object = $doc->createElement("activity:object"); - $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]); - xml_add_element($doc, $author, "link", "", $attributes); + $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']); - $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); + xml::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0])); - 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); - } + self::entry_content($doc, $as_object, $parent[0], $owner, "New entry"); - 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"]); + $entry->appendChild($as_object); - if (trim($owner["location"]) != "") { - $element = $doc->createElement("poco:address"); - xml_add_element($doc, $element, "poco:formatted", $owner["location"]); - $author->appendChild($element); - } + self::entry_footer($doc, $entry, $item, $owner); - 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); + return $entry; } - 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"])); - } + /** + * @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 bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * + * @return object Entry element + */ + 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); + } - return $author; -} + $title = self::entry_header($doc, $entry, $owner, $toplevel); -/** - * @TODO Picture attachments should look like this: - * https://status.pirati.ca/attachment/572819 - * -*/ + xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); -function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) { - $a = get_app(); + self::entry_content($doc, $entry, $item, $owner, $title); - 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); + self::entry_footer($doc, $entry, $item, $owner); + + return $entry; } - $is_repeat = false; + /** + * @brief Adds a header element to the XML document + * + * @param object $doc XML document + * @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)? + * + * @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"]); + } else { + $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry"); -/* if (!$repeat) { - $repeated_guid = get_reshared_guid($item); + $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 ($repeated_guid != "") { - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($owner["uid"]), dbesc($repeated_guid)); - if ($r) { - $repeated_item = $r[0]; - $is_repeat = true; - } + $author = self::add_author($doc, $owner); + $entry->appendChild($author); + + $title = sprintf("New comment by %s", $owner["nick"]); } - } -*/ - 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); - - $author = ostatus_add_author($doc, $owner); - $entry->appendChild($author); - - $title = sprintf("New comment by %s", $owner["nick"]); + return $title; } - // To use the object-type "bookmark" we have to implement these elements: - // - // http://activitystrea.ms/schema/1.0/bookmark - // Historic Rocket Landing - // Nur ein Testbeitrag. - // - // - // - // 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"]; + /** + * @brief Adds elements to the XML document + * + * @param object $doc XML document + * @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 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) { - $repeated_owner["location"] = $r[0]["location"]; - $repeated_owner["about"] = $r[0]["about"]; - } + if ($verb == "") + $verb = self::construct_verb($item); - $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); - } + xml::add_element($doc, $entry, "id", $item["uri"]); + xml::add_element($doc, $entry, "title", $title); - $mentioned = array(); + $body = self::format_picture_post($item['body']); - 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']); + if ($item['title'] != "") + $body = "[b]".$item['title']."[/b]\n\n".$body; - $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); + $body = bbcode($body, false, false, 7); - $attributes = array( - "rel" => "related", - "href" => $a->get_baseurl()."/display/".$parent[0]["guid"]); - xml_add_element($doc, $entry, "link", "", $attributes); + xml::add_element($doc, $entry, "content", $body, array("type" => "html")); - $mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"]; - $mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"]; + xml::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", + "href" => App::get_baseurl()."/display/".$item["guid"])); - $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"]; - } - } + if ($complete) + xml::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"])); - 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"]); + xml::add_element($doc, $entry, "activity:verb", $verb); - $tags = item_getfeedtags($item); + 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(count($tags)) - foreach($tags as $t) - if ($t[0] == "@") - $mentioned[$t[1]] = $t[1]; + /** + * @brief Adds the elements at the foot of an entry to the XML document + * + * @param object $doc XML document + * @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 + */ + 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"]; + } + } - // 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"]) - 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)); - } + 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"]); - 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")); + $tags = item_getfeedtags($item); - if(count($tags)) - foreach($tags as $t) - if ($t[0] != "@") - xml_add_element($doc, $entry, "category", "", array("term" => $t[2])); + if(count($tags)) + foreach($tags as $t) + if ($t[0] == "@") + $mentioned[$t[1]] = $t[1]; - ostatus_get_attachment($doc, $entry, $item); + // 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; - /// @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); + 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)); + } - //$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); + 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")); + } - $app = $item["app"]; - if ($app == "") - $app = "web"; + 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); - $attributes = array("local_id" => $item["id"], "source" => $app); - if ($is_repeat) - $attributes["repeat_of"] = $repeated_item["id"]; + if ($complete) { + $app = $item["app"]; + if ($app == "") + $app = "web"; - xml_add_element($doc, $entry, "statusnet:notice_info", "", $attributes); + $attributes = array("local_id" => $item["id"], "source" => $app); - return $entry; -} + if (isset($parent["id"])) + $attributes["repeat_of"] = $parent["id"]; -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); + if ($item["coord"] != "") + xml::add_element($doc, $entry, "georss:point", $item["coord"]); + + xml::add_element($doc, $entry, "statusnet:notice_info", "", $attributes); + } } - return(trim($doc->saveXML())); -} + /** + * @brief Creates the XML feed for a given nickname + * + * @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 string XML feed + */ + 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; -function ostatus_salmon($item,$owner) { + $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())); + } - $doc = new DOMDocument('1.0', 'utf-8'); - $doc->formatOutput = true; + /** + * @brief Creates the XML for a salmon message + * + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster + * + * @return string XML for the salmon + */ + public static function salmon($item,$owner) { - $entry = ostatus_entry($doc, $item, $owner, true); + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; - $doc->appendChild($entry); + $entry = self::entry($doc, $item, $owner, true); - return(trim($doc->saveXML())); + $doc->appendChild($entry); + + return(trim($doc->saveXML())); + } } ?> diff --git a/include/plaintext.php b/include/plaintext.php index 05431bee2d..a2b2c56522 100644 --- a/include/plaintext.php +++ b/include/plaintext.php @@ -132,7 +132,19 @@ function shortenmsg($msg, $limit, $twitter = false) { return($msg); } -function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { +/** + * @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"); require_once("include/network.php"); @@ -144,6 +156,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 +169,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')); diff --git a/include/poller.php b/include/poller.php index 190f3fb1ad..7ffd47aa68 100644 --- a/include/poller.php +++ b/include/poller.php @@ -26,17 +26,11 @@ function poller_run(&$argv, &$argc){ unset($db_host, $db_user, $db_pass, $db_data); }; - $load = current_load(); - if($load) { - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; + if (poller_max_connections_reached()) + return; - 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)) { @@ -65,6 +59,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; @@ -117,12 +115,93 @@ 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() { + + // 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) { + // 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); + + $level = $used / $max; + + 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 system connections reached: ".$used."/".$max); + return true; +} + /** * @brief fix the queue entry if the worker process died * */ 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", diff --git a/include/post_update.php b/include/post_update.php new file mode 100644 index 0000000000..2bdfe1f6fd --- /dev/null +++ b/include/post_update.php @@ -0,0 +1,141 @@ += 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 "global" field in the item table + * + * @return bool "true" when the job is done + */ +function post_update_1194() { + + // Was the script completed? + if (get_config("system", "post_update_version") >= 1194) + return true; + + 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_1194_end", $r[0]["id"]); + $end_id = get_config("system", "post_update_1194_end"); + } + } + + logger("End ID: ".$end_id, LOGGER_DEBUG); + + $start_id = get_config("system", "post_update_1194_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 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", 1194); + logger("Update is done", LOGGER_DEBUG); + return true; + } else { + 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 1000,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`.`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/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 @@ 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()); } diff --git a/include/pubsubpublish.php b/include/pubsubpublish.php index 0ac50aaaa7..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", @@ -74,25 +74,14 @@ function pubsubpublish_run(&$argv, &$argc){ }; require_once('include/items.php'); - require_once('include/pidfile.php'); 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") + 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 1525ca3abf..878c149731 100644 --- a/include/queue.php +++ b/include/queue.php @@ -22,26 +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'); - $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") + if (App::is_already_running('queue', 'include/queue.php', 540)) return; - } - } $a->set_baseurl(get_config('system','url')); @@ -204,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']); diff --git a/include/session.php b/include/session.php index 11641d6cea..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; }} diff --git a/include/socgraph.php b/include/socgraph.php index c545343393..33d62dc5b9 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -10,7 +10,8 @@ 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 @@ -139,15 +140,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); @@ -427,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"]); @@ -436,69 +438,47 @@ 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))); - - 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))); - - 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 (is_array($noscrape)) { + $contact = array("url" => $profile, + "network" => $server[0]["network"], + "generation" => $gcontacts[0]["generation"]); - 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))); + $contact["name"] = $noscrape["fn"]; + $contact["community"] = $noscrape["comm"]; - 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"])) - $keywords = implode(" ", $noscrape["tags"]); - else - $keywords = ""; + if (isset($noscrape["tags"])) { + $keywords = implode(" ", $noscrape["tags"]); + if ($keywords != "") + $contact["keywords"] = $keywords; + } - if (($keywords != "") AND ($keywords != $gcontacts[0]["keywords"])) - q("UPDATE `gcontact` SET `keywords` = '%s' WHERE `nurl` = '%s'", - dbesc($keywords), dbesc(normalise_link($profile))); + $location = formatted_location($noscrape); + if ($location) + $contact["location"] = $location; - $location = $noscrape["locality"]; + $contact["notify"] = $noscrape["dfrn-notify"]; - if ($noscrape["region"] != "") { - if ($location != "") - $location .= ", "; + // 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"]); - $location .= $noscrape["region"]; - } + $contact = array_merge($contact, $noscrape); - if ($noscrape["country-name"] != "") { - if ($location != "") - $location .= ", "; + update_gcontact($contact); - $location .= $noscrape["country-name"]; + return $noscrape["updated"]; } - - if (($location != "") AND ($location != $gcontacts[0]["location"])) - q("UPDATE `gcontact` SET `location` = '%s' WHERE `nurl` = '%s'", - dbesc($location), dbesc(normalise_link($profile))); - - // 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))); - - return $noscrape["updated"]; } } } @@ -533,25 +513,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"]); @@ -745,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; @@ -753,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; @@ -880,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'", @@ -920,88 +904,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` @@ -1530,9 +1432,17 @@ 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])) + if (!isset($contact[$field]) OR ($contact[$field] == "")) $contact[$field] = $r[0][$field]; if ($contact["network"] == NETWORK_STATUSNET) @@ -1541,20 +1451,50 @@ 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"]); - 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` = %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' @@ -1567,6 +1507,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; @@ -1580,8 +1542,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/include/text.php b/include/text.php index c7681a4d58..c868499cc6 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'), @@ -1148,41 +1148,41 @@ function smilies($s, $sample = false) { ); $icons = array( - '<3', - '</3', - '<\\3', - ':-)', - ';-)', - ':-(', - ':-P', - ':-p', - ':-\', - ':-\', - ':-x', - ':-X', - ':-D', - '8-|', - '8-O', - ':-O', - '\\o/', - 'o.O', - 'O.o', - 'o_O', - 'O_o', - ':\'(', - ':-!', - ':-/', - ':-[', - '8-)', - ':beer', - ':homebrew', - ':coffee', - ':facepalm', - ':like', - ':dislike', - '~friendica ~friendica', - 'redredmatrix', - 'redredmatrix' + '<3', + '</3', + '<\\3', + ':-)', + ';-)', + ':-(', + ':-P', + ':-p', + ':-\', + ':-\', + ':-x', + ':-X', + ':-D', + '8-|', + '8-O', + ':-O', + '\\o/', + 'o.O', + 'O.o', + 'o_O', + 'O_o', + ':\'(', + ':-!', + ':-/', + ':-[', + '8-)', + ':beer', + ':homebrew', + ':coffee', + ':facepalm', + ':like', + ':dislike', + '~friendica ~friendica', + 'redred#matrix', + 'redred#matrixmatrix' ); $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s); @@ -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/include/update_gcontact.php b/include/update_gcontact.php index b5ea30a0a4..88e1817f0b 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"); @@ -37,18 +36,10 @@ 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"); - } - exit; - } - } + // 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)) + return; $r = q("SELECT * FROM `gcontact` WHERE `id` = %d", intval($contact_id)); diff --git a/include/xml.php b/include/xml.php new file mode 100644 index 0000000000..76ad88cf48 --- /dev/null +++ b/include/xml.php @@ -0,0 +1,131 @@ + $value) { + foreach ($namespaces AS $nskey => $nsvalue) + $key .= " xmlns".($nskey == "" ? "":":").$nskey.'="'.$nsvalue.'"'; + + $root = new SimpleXMLElement("<".$key."/>"); + self::from_array($value, $root, $remove_header, $namespaces, false); + + $dom = dom_import_simplexml($root)->ownerDocument; + $dom->formatOutput = true; + $xml = $dom; + + $xml_text = $dom->saveXML(); + + if ($remove_header) + $xml_text = trim(substr($xml_text, 21)); + + return $xml_text; + } + } + + foreach($array as $key => $value) { + 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); + } + } + } + + /** + * @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 + */ + public static function copy(&$source, &$target, $elementname) { + if (count($source->children()) == 0) + $target->addChild($elementname, xmlify($source)); + else { + $child = $target->addChild($elementname); + foreach ($source->children() AS $childfield => $childentry) + 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); + } +} +?> diff --git a/index.php b/index.php index 2b1053cc1b..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"); @@ -233,16 +234,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"))) { @@ -380,7 +372,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'] .= ''; @@ -416,15 +408,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'] .= '
'; - - /** * * Add the navigation (menu) template @@ -441,10 +424,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/library/HTMLPurifier.auto.php b/library/HTMLPurifier.auto.php deleted file mode 100644 index 1960c399f8..0000000000 --- a/library/HTMLPurifier.auto.php +++ /dev/null @@ -1,11 +0,0 @@ -purify($html, $config); -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier.includes.php b/library/HTMLPurifier.includes.php deleted file mode 100644 index 2ed0f0c17f..0000000000 --- a/library/HTMLPurifier.includes.php +++ /dev/null @@ -1,210 +0,0 @@ - $attributes) { - $allowed_elements[$element] = true; - foreach ($attributes as $attribute => $x) { - $allowed_attributes["$element.$attribute"] = true; - } - } - $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); - } - $purifier = new HTMLPurifier($config); - return $purifier->purify($string); -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier.path.php b/library/HTMLPurifier.path.php deleted file mode 100644 index 39b1b65319..0000000000 --- a/library/HTMLPurifier.path.php +++ /dev/null @@ -1,11 +0,0 @@ -config = HTMLPurifier_Config::create($config); - - $this->strategy = new HTMLPurifier_Strategy_Core(); - - } - - /** - * Adds a filter to process the output. First come first serve - * @param $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); - $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 - * object's construction. The parameter can also be any type - * that HTMLPurifier_Config::create() supports. - * @return Purified HTML - */ - public function purify($html, $config = null) { - - // :TODO: make the config merge in, instead of replace - $config = $config ? HTMLPurifier_Config::create($config) : $this->config; - - // implementation is partially environment dependant, partially - // configuration dependant - $lexer = HTMLPurifier_Lexer::create($config); - - $context = new HTMLPurifier_Context(); - - // setup HTML generator - $this->generator = new HTMLPurifier_Generator($config, $context); - $context->register('Generator', $this->generator); - - // set up global context variables - if ($config->get('Core.CollectErrors')) { - // may get moved out if other facilities use it - $language_factory = HTMLPurifier_LanguageFactory::instance(); - $language = $language_factory->create($config, $context); - $context->register('Locale', $language); - - $error_collector = new HTMLPurifier_ErrorCollector($context); - $context->register('ErrorCollector', $error_collector); - } - - // setup id_accumulator context, necessary due to the fact that - // AttrValidator can be called from many places - $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); - $context->register('IDAccumulator', $id_accumulator); - - $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); - - // setup filters - $filter_flags = $config->getBatch('Filter'); - $custom_filters = $filter_flags['Custom']; - unset($filter_flags['Custom']); - $filters = array(); - foreach ($filter_flags as $filter => $flag) { - if (!$flag) continue; - if (strpos($filter, '.') !== false) continue; - $class = "HTMLPurifier_Filter_$filter"; - $filters[] = new $class; - } - foreach ($custom_filters as $filter) { - // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat - $filters[] = $filter; - } - $filters = array_merge($filters, $this->filters); - // maybe prepare(), but later - - for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { - $html = $filters[$i]->preFilter($html, $config, $context); - } - - // purified HTML - $html = - $this->generator->generateFromTokens( - // list of tokens - $this->strategy->execute( - // list of un-purified tokens - $lexer->tokenizeHTML( - // un-purified HTML - $html, $config, $context - ), - $config, $context - ) - ); - - for ($i = $filter_size - 1; $i >= 0; $i--) { - $html = $filters[$i]->postFilter($html, $config, $context); - } - - $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); - $this->context =& $context; - return $html; - } - - /** - * Filters an array of HTML snippets - * @param $config Optional HTMLPurifier_Config object for this operation. - * See HTMLPurifier::purify() for more details. - * @return Array of purified HTML - */ - 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); - $context_array[$key] = $this->context; - } - $this->context = $context_array; - return $array_of_html; - } - - /** - * 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. - */ - public static function instance($prototype = null) { - if (!self::$instance || $prototype) { - if ($prototype instanceof HTMLPurifier) { - self::$instance = $prototype; - } elseif ($prototype) { - self::$instance = new HTMLPurifier($prototype); - } else { - self::$instance = new HTMLPurifier(); - } - } - return self::$instance; - } - - /** - * @note Backwards compatibility, see instance() - */ - 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/HTMLPurifier.safe-includes.php deleted file mode 100644 index 6402de0458..0000000000 --- a/library/HTMLPurifier.safe-includes.php +++ /dev/null @@ -1,204 +0,0 @@ -attr_collections as $coll_i => $coll) { - if (!isset($this->info[$coll_i])) { - $this->info[$coll_i] = array(); - } - foreach ($coll as $attr_i => $attr) { - 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); - continue; - } - $this->info[$coll_i][$attr_i] = $attr; - } - } - } - // perform internal expansions and inclusions - foreach ($this->info as $name => $attr) { - // merge attribute collections that include others - $this->performInclusions($this->info[$name]); - // replace string identifiers with actual attribute objects - $this->expandIdentifiers($this->info[$name], $attr_types); - } - } - - /** - * Takes a reference to an attribute associative array and performs - * all inclusions specified by the zero index. - * @param &$attr Reference to attribute array - */ - 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; - $seen[$merge[$i]] = true; - // foreach attribute of the inclusion, copy it over - if (!isset($this->info[$merge[$i]])) continue; - foreach ($this->info[$merge[$i]] as $key => $value) { - if (isset($attr[$key])) continue; // also catches more inclusions - $attr[$key] = $value; - } - if (isset($this->info[$merge[$i]][0])) { - // recursion - $merge = array_merge($merge, $this->info[$merge[$i]][0]); - } - } - unset($attr[0]); - } - - /** - * 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 - */ - 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 (isset($processed[$def_i])) continue; - - // determine whether or not attribute is required - if ($required = (strpos($def_i, '*') !== false)) { - // rename the definition - unset($attr[$def_i]); - $def_i = trim($def_i, '*'); - $attr[$def_i] = $def; - } - - $processed[$def_i] = true; - - // if we've already got a literal object, move on - if (is_object($def)) { - // preserve previous required - $attr[$def_i]->required = ($required || $attr[$def_i]->required); - continue; - } - - if ($def === false) { - unset($attr[$def_i]); - continue; - } - - if ($t = $attr_types->get($def)) { - $attr[$def_i] = $t; - $attr[$def_i]->required = $required; - } else { - unset($attr[$def_i]); - } - } - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef.php b/library/HTMLPurifier/AttrDef.php deleted file mode 100644 index b2e4f36c5d..0000000000 --- a/library/HTMLPurifier/AttrDef.php +++ /dev/null @@ -1,123 +0,0 @@ - by removing - * leading and trailing whitespace, ignoring line feeds, and replacing - * carriage returns and tabs with spaces. While most useful for HTML - * attributes specified as CDATA, it can also be applied to most CSS - * values. - * - * @note This method is not entirely standards compliant, as trim() removes - * more types of whitespace than specified in the spec. In practice, - * this is rarely a problem, as those extra characters usually have - * already been removed by HTMLPurifier_Encoder. - * - * @warning This processing is inconsistent with XML's whitespace handling - * as specified by section 3.3.3 and referenced XHTML 1.0 section - * 4.7. However, note that we are NOT necessarily - * parsing XML, thus, this behavior may still be correct. We - * assume that newlines have been normalized. - */ - public function parseCDATA($string) { - $string = trim($string); - $string = str_replace(array("\n", "\t", "\r"), ' ', $string); - return $string; - } - - /** - * Factory method for creating this class from a string. - * @param $string String construction info - * @return Created AttrDef object corresponding to $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 - // to clone or instantiate new copies. (Instantiation is safer.) - return $this; - } - - /** - * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work - * properly. THIS IS A HACK! - */ - 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" - * version of it. - */ - protected function expandCSSEscape($string) { - // flexibly parse it - $ret = ''; - for ($i = 0, $c = strlen($string); $i < $c; $i++) { - if ($string[$i] === '\\') { - $i++; - if ($i >= $c) { - $ret .= '\\'; - break; - } - if (ctype_xdigit($string[$i])) { - $code = $string[$i]; - for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { - 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; - $ret .= $char; - if ($i < $c && trim($string[$i]) !== '') $i--; - 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/HTMLPurifier/AttrDef/CSS.php deleted file mode 100644 index 953e706755..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS.php +++ /dev/null @@ -1,87 +0,0 @@ -parseCDATA($css); - - $definition = $config->getCSSDefinition(); - - // we're going to break the spec and explode by semicolons. - // This is because semicolon rarely appears in escaped form - // Doing this is generally flaky but fast - // IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI - // for details - - $declarations = explode(';', $css); - $propvalues = array(); - - /** - * Name of the current CSS property being validated. - */ - $property = false; - $context->register('CurrentCSSProperty', $property); - - foreach ($declarations as $declaration) { - if (!$declaration) continue; - if (!strpos($declaration, ':')) continue; - list($property, $value) = explode(':', $declaration, 2); - $property = trim($property); - $value = trim($value); - $ok = false; - do { - if (isset($definition->info[$property])) { - $ok = true; - break; - } - if (ctype_lower($property)) break; - $property = strtolower($property); - if (isset($definition->info[$property])) { - $ok = true; - break; - } - } 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 ); - } else { - $result = 'inherit'; - } - if ($result === false) continue; - $propvalues[$property] = $result; - } - - $context->destroy('CurrentCSSProperty'); - - // procedure does not write the new CSS simultaneously, so it's - // slightly inefficient, but it's the only way of getting rid of - // duplicates. Perhaps config to optimize it, but not now. - - $new_declarations = ''; - foreach ($propvalues as $prop => $value) { - $new_declarations .= "$prop:$value;"; - } - - return $new_declarations ? $new_declarations : false; - - } - -} - -// vim: et sw=4 sts=4 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 @@ - 1.0) $result = '1'; - return $result; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Background.php b/library/HTMLPurifier/AttrDef/CSS/Background.php deleted file mode 100644 index 3a3d20cd6a..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Background.php +++ /dev/null @@ -1,87 +0,0 @@ -getCSSDefinition(); - $this->info['background-color'] = $def->info['background-color']; - $this->info['background-image'] = $def->info['background-image']; - $this->info['background-repeat'] = $def->info['background-repeat']; - $this->info['background-attachment'] = $def->info['background-attachment']; - $this->info['background-position'] = $def->info['background-position']; - } - - public function validate($string, $config, $context) { - - // regular pre-processing - $string = $this->parseCDATA($string); - 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 - - $caught = array(); - $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; - foreach ($caught as $key => $status) { - if ($key != 'position') { - if ($status !== false) continue; - $r = $this->info['background-' . $key]->validate($bit, $config, $context); - } else { - $r = $bit; - } - if ($r === false) continue; - if ($key == 'position') { - if ($caught[$key] === false) $caught[$key] = ''; - $caught[$key] .= $r . ' '; - } else { - $caught[$key] = $r; - } - $i++; - break; - } - } - - if (!$i) return false; - if ($caught['position'] !== false) { - $caught['position'] = $this->info['background-position']-> - validate($caught['position'], $config, $context); - } - - $ret = array(); - foreach ($caught as $value) { - if ($value === false) continue; - $ret[] = $value; - } - - 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/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php deleted file mode 100644 index fae82eaec8..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php +++ /dev/null @@ -1,133 +0,0 @@ - | | left | center | right - ] - [ - | | top | center | bottom - ]? - ] | - [ // this signifies that the vertical and horizontal adjectives - // can be arbitrarily ordered, however, there can only be two, - // one of each, or none at all - [ - left | center | right - ] || - [ - top | center | bottom - ] - ] - top, left = 0% - center, (none) = 50% - bottom, right = 100% -*/ - -/* QuirksMode says: - keyword + length/percentage must be ordered correctly, as per W3C - - Internet Explorer and Opera, however, support arbitrary ordering. We - should fix it up. - - Minor issue though, not strictly necessary. -*/ - -// control freaks may appreciate the ability to convert these to -// percentages or something, but it's not necessary - -/** - * Validates the value of background-position. - */ -class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef -{ - - protected $length; - protected $percentage; - - public function __construct() { - $this->length = new HTMLPurifier_AttrDef_CSS_Length(); - $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage(); - } - - public function validate($string, $config, $context) { - $string = $this->parseCDATA($string); - $bits = explode(' ', $string); - - $keywords = array(); - $keywords['h'] = false; // left, right - $keywords['v'] = false; // top, bottom - $keywords['ch'] = false; // center (first word) - $keywords['cv'] = false; // center (second word) - $measures = array(); - - $i = 0; - - $lookup = array( - 'top' => 'v', - 'bottom' => 'v', - 'left' => 'h', - 'right' => 'h', - 'center' => 'c' - ); - - foreach ($bits as $bit) { - if ($bit === '') continue; - - // test for keyword - $lbit = ctype_lower($bit) ? $bit : strtolower($bit); - if (isset($lookup[$lbit])) { - $status = $lookup[$lbit]; - if ($status == 'c') { - if ($i == 0) { - $status = 'ch'; - } else { - $status = 'cv'; - } - } - $keywords[$status] = $lbit; - $i++; - } - - // test for length - $r = $this->length->validate($bit, $config, $context); - if ($r !== false) { - $measures[] = $r; - $i++; - } - - // test for percentage - $r = $this->percentage->validate($bit, $config, $context); - if ($r !== false) { - $measures[] = $r; - $i++; - } - - } - - if (!$i) return false; // no valid values were caught - - $ret = array(); - - // first keyword - 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); - - if ($keywords['v']) $ret[] = $keywords['v']; - elseif ($keywords['cv']) $ret[] = $keywords['cv']; - elseif (count($measures)) $ret[] = array_shift($measures); - - 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/HTMLPurifier/AttrDef/CSS/Border.php deleted file mode 100644 index 42a1d1b4ae..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Border.php +++ /dev/null @@ -1,43 +0,0 @@ -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) { - $string = $this->parseCDATA($string); - $string = $this->mungeRgb($string); - $bits = explode(' ', $string); - $done = array(); // segments we've finished - $ret = ''; // return value - foreach ($bits as $bit) { - foreach ($this->info as $propname => $validator) { - if (isset($done[$propname])) continue; - $r = $validator->validate($bit, $config, $context); - if ($r !== false) { - $ret .= $r . ' '; - $done[$propname] = true; - break; - } - } - } - return rtrim($ret); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Color.php b/library/HTMLPurifier/AttrDef/CSS/Color.php deleted file mode 100644 index 07f95a6719..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Color.php +++ /dev/null @@ -1,78 +0,0 @@ -get('Core.ColorKeywords'); - - $color = trim($color); - if ($color === '') return false; - - $lower = strtolower($color); - 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; - $triad = substr($color, 4, $length - 4 - 1); - $parts = explode(',', $triad); - 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; - $length = strlen($part); - if ($part[$length - 1] === '%') { - // handle percents - if (!$type) { - $type = 'percentage'; - } elseif ($type !== 'percentage') { - return false; - } - $num = (float) substr($part, 0, $length - 1); - if ($num < 0) $num = 0; - if ($num > 100) $num = 100; - $new_parts[] = "$num%"; - } else { - // handle integers - if (!$type) { - $type = 'integer'; - } elseif ($type !== 'integer') { - return false; - } - $num = (int) $part; - if ($num < 0) $num = 0; - if ($num > 255) $num = 255; - $new_parts[] = (string) $num; - } - } - $new_triad = implode(',', $new_parts); - $color = "rgb($new_triad)"; - } else { - // hexadecimal handling - if ($color[0] === '#') { - $hex = substr($color, 1); - } else { - $hex = $color; - $color = '#' . $color; - } - $length = strlen($hex); - 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/HTMLPurifier/AttrDef/CSS/Composite.php deleted file mode 100644 index de1289cba8..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Composite.php +++ /dev/null @@ -1,38 +0,0 @@ -defs = $defs; - } - - 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; - } - return false; - } - -} - -// 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 @@ -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/Filter.php b/library/HTMLPurifier/AttrDef/CSS/Filter.php deleted file mode 100644 index 147894b861..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Filter.php +++ /dev/null @@ -1,54 +0,0 @@ -intValidator = new HTMLPurifier_AttrDef_Integer(); - } - - public function validate($value, $config, $context) { - $value = $this->parseCDATA($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; - $cursor = $function_length + 1; - $parameters_length = strcspn($value, ')', $cursor); - $parameters = substr($value, $cursor, $parameters_length); - $params = explode(',', $parameters); - $ret_params = array(); - $lookup = array(); - foreach ($params as $param) { - list($key, $value) = explode('=', $param); - $key = trim($key); - $value = trim($value); - 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'; - $ret_params[] = "$key=$value"; - $lookup[$key] = true; - } - $ret_parameters = implode(',', $ret_params); - $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/HTMLPurifier/AttrDef/CSS/Font.php deleted file mode 100644 index 699ee0b701..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Font.php +++ /dev/null @@ -1,149 +0,0 @@ -getCSSDefinition(); - $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']; - } - - public function validate($string, $config, $context) { - - static $system_fonts = array( - 'caption' => true, - 'icon' => true, - 'menu' => true, - 'message-box' => true, - 'small-caption' => true, - 'status-bar' => true - ); - - // regular pre-processing - $string = $this->parseCDATA($string); - if ($string === '') return false; - - // check if it's one of the keywords - $lowercase_string = strtolower($string); - if (isset($system_fonts[$lowercase_string])) { - return $lowercase_string; - } - - $bits = explode(' ', $string); // bits to process - $stage = 0; // this indicates what we're looking for - $caught = array(); // which stage 0 properties have we caught? - $stage_1 = array('font-style', 'font-variant', 'font-weight'); - $final = ''; // output - - for ($i = 0, $size = count($bits); $i < $size; $i++) { - if ($bits[$i] === '') continue; - switch ($stage) { - - // attempting to catch font-style, font-variant or font-weight - case 0: - foreach ($stage_1 as $validator_name) { - if (isset($caught[$validator_name])) continue; - $r = $this->info[$validator_name]->validate( - $bits[$i], $config, $context); - if ($r !== false) { - $final .= $r . ' '; - $caught[$validator_name] = true; - break; - } - } - // 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: - $found_slash = false; - if (strpos($bits[$i], '/') !== false) { - list($font_size, $line_height) = - explode('/', $bits[$i]); - if ($line_height === '') { - // ooh, there's a space after the slash! - $line_height = false; - $found_slash = true; - } - } else { - $font_size = $bits[$i]; - $line_height = false; - } - $r = $this->info['font-size']->validate( - $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] === '/') { - if ($found_slash) { - return false; - } else { - $found_slash = true; - continue; - } - } - $line_height = $bits[$j]; - break; - } - } else { - // slash already found - $found_slash = true; - $j = $i; - } - if ($found_slash) { - $i = $j; - $r = $this->info['line-height']->validate( - $line_height, $config, $context); - if ($r !== false) { - $final .= '/' . $r; - } - } - $final .= ' '; - $stage = 2; - break; - } - return false; - - // attempting to catch font-family - case 2: - $font_family = - implode(' ', array_slice($bits, $i, $size - $i)); - $r = $this->info['font-family']->validate( - $font_family, $config, $context); - if ($r !== false) { - $final .= $r . ' '; - // processing completed successfully - return rtrim($final); - } - return false; - } - } - return false; - } - -} - -// 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 @@ - 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/ImportantDecorator.php b/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php deleted file mode 100644 index 4e6b35e5a0..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php +++ /dev/null @@ -1,40 +0,0 @@ -def = $def; - $this->allow = $allow; - } - /** - * Intercepts and removes !important if necessary - */ - public function validate($string, $config, $context) { - // test for ! and important tokens - $string = trim($string); - $is_important = false; - // :TODO: optimization: test directly for !important and ! important - if (strlen($string) >= 9 && substr($string, -9) === 'important') { - $temp = rtrim(substr($string, 0, -9)); - // use a temp, because we might want to restore important - if (strlen($temp) >= 1 && substr($temp, -1) === '!') { - $string = rtrim(substr($temp, 0, -1)); - $is_important = true; - } - } - $string = $this->def->validate($string, $config, $context); - if ($this->allow && $is_important) $string .= ' !important'; - return $string; - } -} - -// 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 @@ -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 @@ -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/Multiple.php b/library/HTMLPurifier/AttrDef/CSS/Multiple.php deleted file mode 100644 index 4d62a40d7f..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Multiple.php +++ /dev/null @@ -1,58 +0,0 @@ -single = $single; - $this->max = $max; - } - - public function validate($string, $config, $context) { - $string = $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; - $result = $this->single->validate($parts[$i], $config, $context); - if ($result !== false) { - $final .= $result . ' '; - $num++; - } - } - if ($final === '') return false; - return rtrim($final); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Number.php b/library/HTMLPurifier/AttrDef/CSS/Number.php deleted file mode 100644 index 3f99e12ec2..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Number.php +++ /dev/null @@ -1,69 +0,0 @@ -non_negative = $non_negative; - } - - /** - * @warning Some contexts do not pass $config, $context. These - * variables should not be used without checking HTMLPurifier_Length - */ - public function validate($number, $config, $context) { - - $number = $this->parseCDATA($number); - - if ($number === '') return false; - if ($number === '0') return '0'; - - $sign = ''; - switch ($number[0]) { - case '-': - if ($this->non_negative) return false; - $sign = '-'; - case '+': - $number = substr($number, 1); - } - - if (ctype_digit($number)) { - $number = ltrim($number, '0'); - return $number ? $sign . $number : '0'; - } - - // Period is the only non-numeric character allowed - if (strpos($number, '.') === false) return false; - - list($left, $right) = explode('.', $number, 2); - - if ($left === '' && $right === '') return false; - if ($left !== '' && !ctype_digit($left)) return false; - - $left = ltrim($left, '0'); - $right = rtrim($right, '0'); - - if ($right === '') { - return $left ? $sign . $left : '0'; - } elseif (!ctype_digit($right)) { - return false; - } - - return $sign . $left . '.' . $right; - - } - -} - -// 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 @@ -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/CSS/TextDecoration.php b/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php deleted file mode 100644 index 772c922d80..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php +++ /dev/null @@ -1,38 +0,0 @@ - true, - 'overline' => true, - 'underline' => true, - ); - - $string = strtolower($this->parseCDATA($string)); - - if ($string === 'none') return $string; - - $parts = explode(' ', $string); - $final = ''; - foreach ($parts as $part) { - if (isset($allowed_values[$part])) { - $final .= $part . ' '; - } - } - $final = rtrim($final); - if ($final === '') return false; - return $final; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/URI.php b/library/HTMLPurifier/AttrDef/CSS/URI.php deleted file mode 100644 index 1df17dc25b..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/URI.php +++ /dev/null @@ -1,52 +0,0 @@ -parseCDATA($uri_string); - 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; - $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; - $uri = substr($uri, 1, $new_length - 1); - } - - $uri = $this->expandCSSEscape($uri); - - $result = parent::validate($uri, $config, $context); - - if ($result === false) return false; - - // extra sanity check; should have been done by URI - $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result); - - return "url(\"$result\")"; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/Enum.php b/library/HTMLPurifier/AttrDef/Enum.php deleted file mode 100644 index 5d603ebcc6..0000000000 --- a/library/HTMLPurifier/AttrDef/Enum.php +++ /dev/null @@ -1,65 +0,0 @@ -valid_values = array_flip($valid_values); - $this->case_sensitive = $case_sensitive; - } - - public function validate($string, $config, $context) { - $string = trim($string); - if (!$this->case_sensitive) { - // we may want to do full case-insensitive libraries - $string = ctype_lower($string) ? $string : strtolower($string); - } - $result = isset($this->valid_values[$string]); - - return $result ? $string : false; - } - - /** - * @param $string In form of comma-delimited list of case-insensitive - * valid values. Example: "foo,bar,baz". Prepend "s:" to make - * case sensitive - */ - public function make($string) { - if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { - $string = substr($string, 2); - $sensitive = true; - } else { - $sensitive = false; - } - $values = explode(',', $string); - return new HTMLPurifier_AttrDef_Enum($values, $sensitive); - } - -} - -// 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 @@ -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/Class.php b/library/HTMLPurifier/AttrDef/HTML/Class.php deleted file mode 100644 index 370068d975..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/Class.php +++ /dev/null @@ -1,34 +0,0 @@ -getDefinition('HTML')->doctype->name; - if ($name == "XHTML 1.1" || $name == "XHTML 2.0") { - return parent::split($string, $config, $context); - } else { - return preg_split('/\s+/', $string); - } - } - 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])) && - !isset($forbidden[$token]) && - // We need this O(n) check because of PHP's array - // implementation that casts -0 to 0. - !in_array($token, $ret, true) - ) { - $ret[] = $token; - } - } - return $ret; - } -} 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 @@ -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 @@ -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 @@ -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 @@ - 100) return '100%'; - - return ((string) $points) . '%'; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php deleted file mode 100644 index 76d25ed088..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php +++ /dev/null @@ -1,53 +0,0 @@ - 'AllowedRel', - 'rev' => 'AllowedRev' - ); - if (!isset($configLookup[$name])) { - trigger_error('Unrecognized attribute name for link '. - 'relationship.', E_USER_ERROR); - return; - } - $this->name = $configLookup[$name]; - } - - public function validate($string, $config, $context) { - - $allowed = $config->get('Attr.' . $this->name); - if (empty($allowed)) return false; - - $string = $this->parseCDATA($string); - $parts = explode(' ', $string); - - // lookup to prevent duplicates - $ret_lookup = array(); - foreach ($parts as $part) { - $part = strtolower(trim($part)); - if (!isset($allowed[$part])) continue; - $ret_lookup[$part] = true; - } - - if (empty($ret_lookup)) return false; - $string = implode(' ', array_keys($ret_lookup)); - - return $string; - - } - -} - -// 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 @@ -split($string, $config, $context); - $tokens = $this->filter($tokens, $config, $context); - if (empty($tokens)) return false; - return implode(' ', $tokens); - - } - - /** - * Splits a space separated list of tokens into its constituent parts. - */ - protected function split($string, $config, $context) { - // OPTIMIZABLE! - // do the preg_match, capture all subpatterns for reformulation - - // we don't support U+00A1 and up codepoints or - // 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 - preg_match_all($pattern, $string, $matches); - return $matches[1]; - } - - /** - * 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. - */ - protected function filter($tokens, $config, $context) { - return $tokens; - } - -} - -// 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 @@ -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 - // 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/Integer.php b/library/HTMLPurifier/AttrDef/Integer.php deleted file mode 100644 index d59738d2a2..0000000000 --- a/library/HTMLPurifier/AttrDef/Integer.php +++ /dev/null @@ -1,73 +0,0 @@ -negative = $negative; - $this->zero = $zero; - $this->positive = $positive; - } - - public function validate($integer, $config, $context) { - - $integer = $this->parseCDATA($integer); - 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] === '-' ) { - $digits = substr($integer, 1); - 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; - - // perform scope tests - 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/HTMLPurifier/AttrDef/Lang.php deleted file mode 100644 index 10e6da56db..0000000000 --- a/library/HTMLPurifier/AttrDef/Lang.php +++ /dev/null @@ -1,73 +0,0 @@ - 8 || !ctype_alnum($subtags[1])) { - return $new_string; - } - if (!ctype_lower($subtags[1])) $subtags[1] = strtolower($subtags[1]); - - $new_string .= '-' . $subtags[1]; - if ($num_subtags == 2) return $new_string; - - // process all other subtags, index 2 and up - for ($i = 2; $i < $num_subtags; $i++) { - $length = strlen($subtags[$i]); - if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) { - return $new_string; - } - if (!ctype_lower($subtags[$i])) { - $subtags[$i] = strtolower($subtags[$i]); - } - $new_string .= '-' . $subtags[$i]; - } - - return $new_string; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/Switch.php b/library/HTMLPurifier/AttrDef/Switch.php deleted file mode 100644 index c9e3ed193e..0000000000 --- a/library/HTMLPurifier/AttrDef/Switch.php +++ /dev/null @@ -1,34 +0,0 @@ -tag = $tag; - $this->withTag = $with_tag; - $this->withoutTag = $without_tag; - } - - public function validate($string, $config, $context) { - $token = $context->get('CurrentToken', true); - if (!$token || $token->name !== $this->tag) { - return $this->withoutTag->validate($string, $config, $context); - } else { - return $this->withTag->validate($string, $config, $context); - } - } - -} - -// 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 @@ -parseCDATA($string); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/URI.php b/library/HTMLPurifier/AttrDef/URI.php deleted file mode 100644 index 01a6d83e95..0000000000 --- a/library/HTMLPurifier/AttrDef/URI.php +++ /dev/null @@ -1,77 +0,0 @@ -parser = new HTMLPurifier_URIParser(); - $this->embedsResource = (bool) $embeds_resource; - } - - public function make($string) { - $embeds = (bool) $string; - return new HTMLPurifier_AttrDef_URI($embeds); - } - - 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; - - // add embedded flag to context for validators - $context->register('EmbeddedURI', $this->embedsResource); - - $ok = false; - do { - - // generic validation - $result = $uri->validate($config, $context); - if (!$result) break; - - // chained filtering - $uri_def = $config->getDefinition('URI'); - $result = $uri_def->filter($uri, $config, $context); - if (!$result) break; - - // scheme-specific validation - $scheme_obj = $uri->getSchemeObj($config, $context); - if (!$scheme_obj) break; - if ($this->embedsResource && !$scheme_obj->browsable) break; - $result = $scheme_obj->validate($uri, $config, $context); - if (!$result) break; - - // Post chained filtering - $result = $uri_def->postFilter($uri, $config, $context); - if (!$result) break; - - // survived gauntlet - $ok = true; - - } while (false); - - $context->destroy('EmbeddedURI'); - 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/HTMLPurifier/AttrDef/URI/Email.php deleted file mode 100644 index bfee9d166c..0000000000 --- a/library/HTMLPurifier/AttrDef/URI/Email.php +++ /dev/null @@ -1,17 +0,0 @@ -" - // that needs more percent encoding to be done - 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/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 @@ -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/IPv4.php b/library/HTMLPurifier/AttrDef/URI/IPv4.php deleted file mode 100644 index ec4cf591b8..0000000000 --- a/library/HTMLPurifier/AttrDef/URI/IPv4.php +++ /dev/null @@ -1,39 +0,0 @@ -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() { - $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/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 @@ -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/HTMLPurifier/AttrTransform.php deleted file mode 100644 index e61d3e01b6..0000000000 --- a/library/HTMLPurifier/AttrTransform.php +++ /dev/null @@ -1,56 +0,0 @@ -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/HTMLPurifier/AttrTransform/BdoDir.php deleted file mode 100644 index 4d1a05665e..0000000000 --- a/library/HTMLPurifier/AttrTransform/BdoDir.php +++ /dev/null @@ -1,19 +0,0 @@ -get('Attr.DefaultTextDir'); - return $attr; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/BgColor.php b/library/HTMLPurifier/AttrTransform/BgColor.php deleted file mode 100644 index ad3916bb96..0000000000 --- a/library/HTMLPurifier/AttrTransform/BgColor.php +++ /dev/null @@ -1,23 +0,0 @@ -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/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 @@ -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/Border.php b/library/HTMLPurifier/AttrTransform/Border.php deleted file mode 100644 index 476b0b079b..0000000000 --- a/library/HTMLPurifier/AttrTransform/Border.php +++ /dev/null @@ -1,18 +0,0 @@ -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/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 @@ -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/ImgRequired.php b/library/HTMLPurifier/AttrTransform/ImgRequired.php deleted file mode 100644 index 7f0e4b7a59..0000000000 --- a/library/HTMLPurifier/AttrTransform/ImgRequired.php +++ /dev/null @@ -1,43 +0,0 @@ -get('Core.RemoveInvalidImg')) return $attr; - $attr['src'] = $config->get('Attr.DefaultInvalidImage'); - $src = false; - } - - if (!isset($attr['alt'])) { - if ($src) { - $alt = $config->get('Attr.DefaultImageAlt'); - if ($alt === null) { - // truncate if the alt is too long - $attr['alt'] = substr(basename($attr['src']),0,40); - } else { - $attr['alt'] = $alt; - } - } else { - $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); - } - } - - return $attr; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/ImgSpace.php b/library/HTMLPurifier/AttrTransform/ImgSpace.php deleted file mode 100644 index fd84c10c36..0000000000 --- a/library/HTMLPurifier/AttrTransform/ImgSpace.php +++ /dev/null @@ -1,44 +0,0 @@ - array('left', 'right'), - 'vspace' => array('top', 'bottom') - ); - - 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; - - $width = $this->confiscateAttr($attr, $this->attr); - // some validation could happen here - - 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/HTMLPurifier/AttrTransform/Input.php deleted file mode 100644 index 16829552d1..0000000000 --- a/library/HTMLPurifier/AttrTransform/Input.php +++ /dev/null @@ -1,40 +0,0 @@ -pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); - } - - 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']); - } - if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') { - unset($attr['maxlength']); - } - 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 (isset($attr['src']) && $t !== 'image') { - unset($attr['src']); - } - if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) { - $attr['value'] = ''; - } - return $attr; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Lang.php b/library/HTMLPurifier/AttrTransform/Lang.php deleted file mode 100644 index 5869e7f820..0000000000 --- a/library/HTMLPurifier/AttrTransform/Lang.php +++ /dev/null @@ -1,28 +0,0 @@ -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 @@ -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 @@ -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/SafeEmbed.php b/library/HTMLPurifier/AttrTransform/SafeEmbed.php deleted file mode 100644 index 4da449981f..0000000000 --- a/library/HTMLPurifier/AttrTransform/SafeEmbed.php +++ /dev/null @@ -1,15 +0,0 @@ -uri = new HTMLPurifier_AttrDef_URI(true); // embedded - } - - public function transform($attr, $config, $context) { - // If we add support for other objects, we'll need to alter the - // transforms. - switch ($attr['name']) { - // application/x-shockwave-flash - // Keep this synchronized with Injector/SafeObject.php - case 'allowScriptAccess': - $attr['value'] = 'never'; - break; - case 'allowNetworking': - $attr['value'] = 'internal'; - break; - case 'wmode': - $attr['value'] = 'window'; - break; - case 'movie': - case 'src': - $attr['name'] = "movie"; - $attr['value'] = $this->uri->validate($attr['value'], $config, $context); - break; - case 'flashvars': - // we're going to allow arbitrary inputs to the SWF, on - // the reasoning that it could only hack the SWF, not us. - break; - // add other cases to support other param name/value pairs - default: - $attr['name'] = $attr['value'] = null; - } - return $attr; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/ScriptRequired.php b/library/HTMLPurifier/AttrTransform/ScriptRequired.php deleted file mode 100644 index 4499050a22..0000000000 --- a/library/HTMLPurifier/AttrTransform/ScriptRequired.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ -class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform -{ - public function transform($attr, $config, $context) { - if (!isset($attr['type'])) { - $attr['type'] = 'text/javascript'; - } - 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 @@ - - */ -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/AttrTypes.php b/library/HTMLPurifier/AttrTypes.php deleted file mode 100644 index fc2ea4e588..0000000000 --- a/library/HTMLPurifier/AttrTypes.php +++ /dev/null @@ -1,77 +0,0 @@ -info['Enum'] = new HTMLPurifier_AttrDef_Enum(); - $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); - - $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text(); - $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID(); - $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length(); - $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength(); - $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens(); - $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels(); - $this->info['Text'] = new HTMLPurifier_AttrDef_Text(); - $this->info['URI'] = new HTMLPurifier_AttrDef_URI(); - $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); - $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); - - // unimplemented aliases - $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); - $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); - $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); - $this->info['Character'] = new HTMLPurifier_AttrDef_Text(); - - // "proprietary" types - $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class(); - - // number is really a positive integer (one or more digits) - // FIXME: ^^ not always, see start and value of list items - $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); - } - - /** - * Retrieves a type - * @param $type String type name - * @return Object AttrDef for 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 (!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 - */ - public function set($type, $impl) { - $this->info[$type] = $impl; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrValidator.php b/library/HTMLPurifier/AttrValidator.php deleted file mode 100644 index 829a0f8f22..0000000000 --- a/library/HTMLPurifier/AttrValidator.php +++ /dev/null @@ -1,162 +0,0 @@ -getHTMLDefinition(); - $e =& $context->get('ErrorCollector', true); - - // initialize IDAccumulator if necessary - $ok =& $context->get('IDAccumulator', true); - if (!$ok) { - $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); - $context->register('IDAccumulator', $id_accumulator); - } - - // initialize CurrentToken if necessary - $current_token =& $context->get('CurrentToken', true); - if (!$current_token) $context->register('CurrentToken', $token); - - if ( - !$token instanceof HTMLPurifier_Token_Start && - !$token instanceof HTMLPurifier_Token_Empty - ) return $token; - - // create alias to global definition array, see also $defs - // DEFINITION CALL - $d_defs = $definition->info_global_attr; - - // don't update token until the very end, to ensure an atomic update - $attr = $token->attr; - - // do global transformations (pre) - // nothing currently utilizes this - 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); - } - } - - // do local transformations only applicable to this element (pre) - // ex.

to

- 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); - } - } - - // create alias to this element's attribute definition array, see - // also $d_defs (global attribute definition array) - // DEFINITION CALL - $defs = $definition->info[$token->name]->attr; - - $attr_key = false; - $context->register('CurrentAttr', $attr_key); - - // iterate through all the attribute keypairs - // Watch out for name collisions: $key has previously been used - foreach ($attr as $attr_key => $value) { - - // call the definition - 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. - // This is usually when there's a global definition - // that must be overridden. - // Theoretically speaking, we could have a - // AttrDef_DenyAll, but this is faster! - $result = false; - } else { - // validate according to the element's definition - $result = $defs[$attr_key]->validate( - $value, $config, $context - ); - } - } 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 - ); - } else { - // system never heard of the attribute? DELETE! - $result = false; - } - - // put the results into effect - 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'); - - // remove the attribute - unset($attr[$attr_key]); - } elseif (is_string($result)) { - // generally, if a substitution is happening, there - // was some sort of implicit correction going on. We'll - // delegate it to the attribute classes to say exactly what. - - // simple substitution - $attr[$attr_key] = $result; - } else { - // nothing happens - } - - // we'd also want slightly more complicated substitution - // involving an array as the return value, - // although we're not sure how colliding attributes would - // resolve (certain ones would be completely overriden, - // others would prepend themselves). - } - - $context->destroy('CurrentAttr'); - - // post transforms - - // global (error reporting untested) - 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); - } - } - - // local (error reporting untested) - 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); - } - } - - $token->attr = $attr; - - // destroy CurrentToken if we made it ourselves - if (!$current_token) $context->destroy('CurrentToken'); - - } - - -} - -// 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 @@ - -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 @@ -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.php b/library/HTMLPurifier/ChildDef.php deleted file mode 100644 index c5d5216dab..0000000000 --- a/library/HTMLPurifier/ChildDef.php +++ /dev/null @@ -1,48 +0,0 @@ -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 - */ - abstract public function validateChildren($tokens_of_children, $config, $context); -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/Chameleon.php b/library/HTMLPurifier/ChildDef/Chameleon.php deleted file mode 100644 index 15c364ee33..0000000000 --- a/library/HTMLPurifier/ChildDef/Chameleon.php +++ /dev/null @@ -1,48 +0,0 @@ -inline = new HTMLPurifier_ChildDef_Optional($inline); - $this->block = new HTMLPurifier_ChildDef_Optional($block); - $this->elements = $this->block->elements; - } - - public function validateChildren($tokens_of_children, $config, $context) { - if ($context->get('IsInline') === false) { - return $this->block->validateChildren( - $tokens_of_children, $config, $context); - } else { - return $this->inline->validateChildren( - $tokens_of_children, $config, $context); - } - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/Custom.php b/library/HTMLPurifier/ChildDef/Custom.php deleted file mode 100644 index b68047b4b5..0000000000 --- a/library/HTMLPurifier/ChildDef/Custom.php +++ /dev/null @@ -1,90 +0,0 @@ -dtd_regex = $dtd_regex; - $this->_compileRegex(); - } - /** - * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex) - */ - protected function _compileRegex() { - $raw = str_replace(' ', '', $this->dtd_regex); - if ($raw{0} != '(') { - $raw = "($raw)"; - } - $el = '[#a-zA-Z0-9_.-]+'; - $reg = $raw; - - // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M - // DOING! Seriously: if there's problems, please report them. - - // collect all elements into the $elements array - preg_match_all("/$el/", $reg, $matches); - foreach ($matches[0] as $match) { - $this->elements[$match] = true; - } - - // setup all elements as parentheticals with leading commas - $reg = preg_replace("/$el/", '(,\\0)', $reg); - - // remove commas when they were not solicited - $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg); - - // remove all non-paranthetical commas: they are handled by first regex - $reg = preg_replace("/,\(/", '(', $reg); - - $this->_pcre_regex = $reg; - } - public function validateChildren($tokens_of_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 . ','; - } - } - // add leading comma to deal with stray comma declarations - $list_of_children = ',' . rtrim($list_of_children, ','); - $okay = - preg_match( - '/^,?'.$this->_pcre_regex.'$/', - $list_of_children - ); - - return (bool) $okay; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/Empty.php b/library/HTMLPurifier/ChildDef/Empty.php deleted file mode 100644 index 13171f6651..0000000000 --- a/library/HTMLPurifier/ChildDef/Empty.php +++ /dev/null @@ -1,20 +0,0 @@ -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 @@ - $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 @@ -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 @@ - 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 @@ -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.php b/library/HTMLPurifier/ConfigSchema.php deleted file mode 100644 index 67be5c71fd..0000000000 --- a/library/HTMLPurifier/ConfigSchema.php +++ /dev/null @@ -1,158 +0,0 @@ - array( - * 'Directive' => new stdclass(), - * ) - * ) - * - * The stdclass may have the following properties: - * - * - If isAlias isn't set: - * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions - * - allow_null: If set, this directive allows null values - * - aliases: If set, an associative array of value aliases to real values - * - allowed: If set, a lookup array of allowed (string) values - * - If isAlias is set: - * - namespace: Namespace this directive aliases to - * - name: Directive name this directive aliases to - * - * In certain degenerate cases, stdclass will actually be an integer. In - * that case, the value is equivalent to an stdclass with the type - * property set to the integer. If the integer is negative, type is - * equal to the absolute value of integer, and allow_null is true. - * - * 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. - */ - public $info = array(); - - /** - * Application-wide singleton - */ - static protected $singleton; - - public function __construct() { - $this->defaultPlist = new HTMLPurifier_PropertyList(); - } - - /** - * Unserializes the default ConfigSchema. - */ - public static function makeFromSerial() { - return unserialize(file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser')); - } - - /** - * Retrieves an instance of the application-wide configuration definition. - */ - public static function instance($prototype = null) { - if ($prototype !== null) { - HTMLPurifier_ConfigSchema::$singleton = $prototype; - } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { - HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial(); - } - return HTMLPurifier_ConfigSchema::$singleton; - } - - /** - * Defines a directive for configuration - * @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 - * HTMLPurifier_DirectiveDef::$type for allowed values - * @param $allow_null Whether or not to allow null values - */ - 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; - $this->info[$key] = $obj; - $this->defaults[$key] = $default; - $this->defaultPlist->set($key, $default); - } - - /** - * Defines a directive value alias. - * - * 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 - */ - public function addValueAliases($key, $aliases) { - if (!isset($this->info[$key]->aliases)) { - $this->info[$key]->aliases = array(); - } - foreach ($aliases as $alias => $real) { - $this->info[$key]->aliases[$alias] = $real; - } - } - - /** - * 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 - */ - 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 - */ - public function addAlias($key, $new_key) { - $obj = new stdclass; - $obj->key = $new_key; - $obj->isAlias = true; - $this->info[$key] = $obj; - } - - /** - * Replaces any stdclass that only has the type property with type integer. - */ - public function postProcess() { - foreach ($this->info as $key => $v) { - if (count((array) $v) == 1) { - $this->info[$key] = $v->type; - } elseif (count((array) $v) == 2 && isset($v->allow_null)) { - $this->info[$key] = -$v->type; - } - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php deleted file mode 100644 index c05668a706..0000000000 --- a/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php +++ /dev/null @@ -1,44 +0,0 @@ -directives as $d) { - $schema->add( - $d->id->key, - $d->default, - $d->type, - $d->typeAllowsNull - ); - if ($d->allowed !== null) { - $schema->addAllowedValues( - $d->id->key, - $d->allowed - ); - } - foreach ($d->aliases as $alias) { - $schema->addAlias( - $alias->key, - $d->id->key - ); - } - if ($d->valueAliases !== null) { - $schema->addValueAliases( - $d->id->key, - $d->valueAliases - ); - } - } - $schema->postProcess(); - return $schema; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/library/HTMLPurifier/ConfigSchema/Builder/Xml.php deleted file mode 100644 index 244561a372..0000000000 --- a/library/HTMLPurifier/ConfigSchema/Builder/Xml.php +++ /dev/null @@ -1,106 +0,0 @@ -startElement('div'); - - $purifier = HTMLPurifier::getInstance(); - $html = $purifier->purify($html); - $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); - $this->writeRaw($html); - - $this->endElement(); // div - } - - protected function export($var) { - if ($var === array()) return 'array()'; - return var_export($var, true); - } - - public function build($interchange) { - // global access, only use as last resort - $this->interchange = $interchange; - - $this->setIndent(true); - $this->startDocument('1.0', 'UTF-8'); - $this->startElement('configdoc'); - $this->writeElement('title', $interchange->name); - - foreach ($interchange->directives as $directive) { - $this->buildDirective($directive); - } - - if ($this->namespace) $this->endElement(); // namespace - - $this->endElement(); // configdoc - $this->flush(); - } - - 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 - $this->namespace = $directive->id->getRootNamespace(); - $this->startElement('namespace'); - $this->writeAttribute('id', $this->namespace); - $this->writeElement('name', $this->namespace); - } - - $this->startElement('directive'); - $this->writeAttribute('id', $directive->id->toString()); - - $this->writeElement('name', $directive->id->getDirective()); - - $this->startElement('aliases'); - 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 - } - $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->endElement(); // deprecated - } - - $this->startElement('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/HTMLPurifier/ConfigSchema/Exception.php deleted file mode 100644 index 2671516c58..0000000000 --- a/library/HTMLPurifier/ConfigSchema/Exception.php +++ /dev/null @@ -1,11 +0,0 @@ - array(directive info) - */ - public $directives = array(); - - /** - * Adds a directive array to $directives - */ - public function addDirective($directive) { - if (isset($this->directives[$i = $directive->id->toString()])) { - throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); - } - $this->directives[$i] = $directive; - } - - /** - * Convenience function to perform standard validation. Throws exception - * on failed validation. - */ - 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/HTMLPurifier/ConfigSchema/Interchange/Directive.php deleted file mode 100644 index ac8be0d970..0000000000 --- a/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php +++ /dev/null @@ -1,77 +0,0 @@ - true). - * Null if all values are allowed. - */ - public $allowed; - - /** - * List of aliases for the directive, - * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). - */ - public $aliases = array(); - - /** - * Hash of value aliases, e.g. array('alt' => 'real'). Null if value - * aliasing is disabled (necessary for non-scalar types). - */ - public $valueAliases; - - /** - * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. - * Null if the directive has always existed. - */ - public $version; - - /** - * ID of directive that supercedes this old directive, is an instance - * of HTMLPurifier_ConfigSchema_Interchange_Id. Null if not deprecated. - */ - public $deprecatedUse; - - /** - * Version of HTML Purifier this directive was deprecated. Null if not - * deprecated. - */ - public $deprecatedVersion; - - /** - * List of external projects this directive depends on, e.g. array('CSSTidy'). - */ - public $external = array(); - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/library/HTMLPurifier/ConfigSchema/Interchange/Id.php deleted file mode 100644 index b9b3c6f5cf..0000000000 --- a/library/HTMLPurifier/ConfigSchema/Interchange/Id.php +++ /dev/null @@ -1,37 +0,0 @@ -key = $key; - } - - /** - * @warning This is NOT magic, to ensure that people don't abuse SPL and - * cause problems for PHP 5.0 support. - */ - public function toString() { - return $this->key; - } - - public function getRootNamespace() { - return substr($this->key, 0, strpos($this->key, ".")); - } - - public function getDirective() { - return substr($this->key, strpos($this->key, ".") + 1); - } - - 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/HTMLPurifier/ConfigSchema/InterchangeBuilder.php deleted file mode 100644 index 785b72ce8e..0000000000 --- a/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php +++ /dev/null @@ -1,180 +0,0 @@ -varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); - } - - 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'; - if (file_exists($dir . '/info.ini')) { - $info = parse_ini_file($dir . '/info.ini'); - $interchange->name = $info['name']; - } - - $files = array(); - $dh = opendir($dir); - while (false !== ($file = readdir($dh))) { - if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') { - continue; - } - $files[] = $file; - } - closedir($dh); - - sort($files); - foreach ($files as $file) { - $this->buildFile($interchange, $dir . '/' . $file); - } - - return $interchange; - } - - public function buildFile($interchange, $file) { - $parser = new HTMLPurifier_StringHashParser(); - $this->build( - $interchange, - 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 - */ - public function build($interchange, $hash) { - if (!$hash instanceof HTMLPurifier_StringHash) { - $hash = new HTMLPurifier_StringHash($hash); - } - if (!isset($hash['ID'])) { - throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID'); - } - if (strpos($hash['ID'], '.') === false) { - if (count($hash) == 2 && isset($hash['DESCRIPTION'])) { - $hash->offsetGet('DESCRIPTION'); // prevent complaining - } else { - throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace'); - } - } else { - $this->buildDirective($interchange, $hash); - } - $this->_findUnused($hash); - } - - public function buildDirective($interchange, $hash) { - $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive(); - - // These are required elements: - $directive->id = $this->id($hash->offsetGet('ID')); - $id = $directive->id->toString(); // convenience - - if (isset($hash['TYPE'])) { - $type = explode('/', $hash->offsetGet('TYPE')); - if (isset($type[1])) $directive->typeAllowsNull = true; - $directive->type = $type[0]; - } else { - throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); - } - - if (isset($hash['DEFAULT'])) { - try { - $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'"); - } - } - - if (isset($hash['DESCRIPTION'])) { - $directive->description = $hash->offsetGet('DESCRIPTION'); - } - - if (isset($hash['ALLOWED'])) { - $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED'))); - } - - if (isset($hash['VALUE-ALIASES'])) { - $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES')); - } - - if (isset($hash['ALIASES'])) { - $raw_aliases = trim($hash->offsetGet('ALIASES')); - $aliases = preg_split('/\s*,\s*/', $raw_aliases); - foreach ($aliases as $alias) { - $directive->aliases[] = $this->id($alias); - } - } - - if (isset($hash['VERSION'])) { - $directive->version = $hash->offsetGet('VERSION'); - } - - if (isset($hash['DEPRECATED-USE'])) { - $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE')); - } - - if (isset($hash['DEPRECATED-VERSION'])) { - $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION'); - } - - if (isset($hash['EXTERNAL'])) { - $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL'))); - } - - $interchange->addDirective($directive); - } - - /** - * Evaluates an array PHP code string without array() wrapper - */ - protected function evalArray($contents) { - return eval('return array('. $contents .');'); - } - - /** - * Converts an array list into a lookup array. - */ - protected function lookup($array) { - $ret = array(); - foreach ($array as $val) $ret[$val] = true; - return $ret; - } - - /** - * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id - * object based on a string 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. - */ - protected function _findUnused($hash) { - $accessed = $hash->getAccessed(); - foreach ($hash as $k => $v) { - if (!isset($accessed[$k])) { - trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE); - } - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Validator.php b/library/HTMLPurifier/ConfigSchema/Validator.php deleted file mode 100644 index f374f6a022..0000000000 --- a/library/HTMLPurifier/ConfigSchema/Validator.php +++ /dev/null @@ -1,206 +0,0 @@ -parser = new HTMLPurifier_VarParser(); - } - - /** - * Validates a fully-formed interchange object. Throws an - * HTMLPurifier_ConfigSchema_Exception if there's a problem. - */ - 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'"); - $this->validateDirective($directive); - } - return true; - } - - /** - * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. - */ - public function validateId($id) { - $id_string = $id->toString(); - $this->context[] = "id '$id_string'"; - if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { - // handled by InterchangeBuilder - $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id'); - } - // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.) - // we probably should check that it has at least one namespace - $this->with($id, 'key') - ->assertNotEmpty() - ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder - array_pop($this->context); - } - - /** - * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. - */ - public function validateDirective($d) { - $id = $d->id->toString(); - $this->context[] = "directive '$id'"; - $this->validateId($d->id); - - $this->with($d, 'description') - ->assertNotEmpty(); - - // BEGIN - handled by InterchangeBuilder - $this->with($d, 'type') - ->assertNotEmpty(); - $this->with($d, 'typeAllowsNull') - ->assertIsBool(); - try { - // This also tests validity of $d->type - $this->parser->parse($d->default, $d->type, $d->typeAllowsNull); - } catch (HTMLPurifier_VarParserException $e) { - $this->error('default', 'had error: ' . $e->getMessage()); - } - // END - handled by InterchangeBuilder - - if (!is_null($d->allowed) || !empty($d->valueAliases)) { - // allowed and valueAliases require that we be dealing with - // strings, so check for that early. - $d_int = HTMLPurifier_VarParser::$types[$d->type]; - if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) { - $this->error('type', 'must be a string type when used with allowed or value aliases'); - } - } - - $this->validateDirectiveAllowed($d); - $this->validateDirectiveValueAliases($d); - $this->validateDirectiveAliases($d); - - array_pop($this->context); - } - - /** - * Extra validation if $allowed member variable of - * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. - */ - public function validateDirectiveAllowed($d) { - if (is_null($d->allowed)) return; - $this->with($d, 'allowed') - ->assertNotEmpty() - ->assertIsLookup(); // handled by InterchangeBuilder - if (is_string($d->default) && !isset($d->allowed[$d->default])) { - $this->error('default', 'must be an allowed value'); - } - $this->context[] = 'allowed'; - foreach ($d->allowed as $val => $x) { - if (!is_string($val)) $this->error("value $val", 'must be a string'); - } - array_pop($this->context); - } - - /** - * Extra validation if $valueAliases member variable of - * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. - */ - 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 ($alias === $real) { - $this->error("alias '$alias'", "must not be an alias to itself"); - } - } - if (!is_null($d->allowed)) { - foreach ($d->valueAliases as $alias => $real) { - if (isset($d->allowed[$alias])) { - $this->error("alias '$alias'", 'must not be an allowed value'); - } elseif (!isset($d->allowed[$real])) { - $this->error("alias '$alias'", 'must be an alias to an allowed value'); - } - } - } - array_pop($this->context); - } - - /** - * Extra validation if $aliases member variable of - * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. - */ - public function validateDirectiveAliases($d) { - $this->with($d, 'aliases') - ->assertIsArray(); // handled by InterchangeBuilder - $this->context[] = 'aliases'; - foreach ($d->aliases as $alias) { - $this->validateId($alias); - $s = $alias->toString(); - if (isset($this->interchange->directives[$s])) { - $this->error("alias '$s'", 'collides with another directive'); - } - if (isset($this->aliases[$s])) { - $other_directive = $this->aliases[$s]; - $this->error("alias '$s'", "collides with alias for directive '$other_directive'"); - } - $this->aliases[$s] = $d->id->toString(); - } - array_pop($this->context); - } - - // protected helper functions - - /** - * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom - * for validating simple member variables of objects. - */ - protected function with($obj, $member) { - return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); - } - - /** - * Emits an error, providing helpful context. - */ - 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. - */ - protected function getFormattedContext() { - return implode(' in ', array_reverse($this->context)); - } - -} - -// 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 @@ -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 22b8d54a59..0000000000 Binary files a/library/HTMLPurifier/ConfigSchema/schema.ser and /dev/null differ diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt deleted file mode 100644 index 0517fed0a1..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt +++ /dev/null @@ -1,8 +0,0 @@ -Attr.AllowedClasses -TYPE: lookup/null -VERSION: 4.0.0 -DEFAULT: null ---DESCRIPTION-- -List of allowed class values in the class attribute. By default, this is null, -which means all classes are allowed. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt deleted file mode 100644 index 249edd647b..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt +++ /dev/null @@ -1,12 +0,0 @@ -Attr.AllowedFrameTargets -TYPE: lookup -DEFAULT: array() ---DESCRIPTION-- -Lookup table of all allowed link frame targets. Some commonly used link -targets include _blank, _self, _parent and _top. Values should be -lowercase, as validation will be done in a case-sensitive manner despite -W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute -so this directive will have no effect in that doctype. XHTML 1.1 does not -enable the Target module by default, you will have to manually enable it -(see the module documentation for more details.) ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt deleted file mode 100644 index 9a8fa6a2e2..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt +++ /dev/null @@ -1,9 +0,0 @@ -Attr.AllowedRel -TYPE: lookup -VERSION: 1.6.0 -DEFAULT: array() ---DESCRIPTION-- -List of allowed forward document relationships in the rel attribute. Common -values may be nofollow or print. By default, this is empty, meaning that no -document relationships are allowed. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt deleted file mode 100644 index b017883485..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt +++ /dev/null @@ -1,9 +0,0 @@ -Attr.AllowedRev -TYPE: lookup -VERSION: 1.6.0 -DEFAULT: array() ---DESCRIPTION-- -List of allowed reverse document relationships in the rev attribute. This -attribute is a bit of an edge-case; if you don't know what it is for, stay -away. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt deleted file mode 100644 index e774b823b1..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt +++ /dev/null @@ -1,19 +0,0 @@ -Attr.ClassUseCDATA -TYPE: bool/null -DEFAULT: null -VERSION: 4.0.0 ---DESCRIPTION-- -If null, class will auto-detect the doctype and, if matching XHTML 1.1 or -XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise, -it will use a relaxed CDATA definition. If true, the relaxed CDATA definition -is forced; if false, the NMTOKENS definition is forced. To get behavior -of HTML Purifier prior to 4.0.0, set this directive to false. - -Some rational behind the auto-detection: -in previous versions of HTML Purifier, it was assumed that the form of -class was NMTOKENS, as specified by the XHTML Modularization (representing -XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however -specify class as CDATA. HTML 5 effectively defines it as CDATA, but -with the additional constraint that each name should be unique (this is not -explicitly outlined in previous specifications). ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt deleted file mode 100644 index 533165e175..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt +++ /dev/null @@ -1,11 +0,0 @@ -Attr.DefaultImageAlt -TYPE: string/null -DEFAULT: null -VERSION: 3.2.0 ---DESCRIPTION-- -This is the content of the alt tag of an image if the user had not -previously specified an alt attribute. This applies to all images without -a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which -only applies to invalid images, and overrides in the case of an invalid image. -Default behavior with null is to use the basename of the src tag for the alt. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt deleted file mode 100644 index 9eb7e38469..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt +++ /dev/null @@ -1,9 +0,0 @@ -Attr.DefaultInvalidImage -TYPE: string -DEFAULT: '' ---DESCRIPTION-- -This is the default image an img tag will be pointed to if it does not have -a valid src attribute. In future versions, we may allow the image tag to -be removed completely, but due to design issues, this is not possible right -now. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt deleted file mode 100644 index 2f17bf477a..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt +++ /dev/null @@ -1,8 +0,0 @@ -Attr.DefaultInvalidImageAlt -TYPE: string -DEFAULT: 'Invalid image' ---DESCRIPTION-- -This is the content of the alt tag of an invalid image if the user had not -previously specified an alt attribute. It has no effect when the image is -valid but there was no alt attribute present. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt deleted file mode 100644 index 52654b53ae..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt +++ /dev/null @@ -1,10 +0,0 @@ -Attr.DefaultTextDir -TYPE: string -DEFAULT: 'ltr' ---DESCRIPTION-- -Defines the default text direction (ltr or rtl) of the document being -parsed. This generally is the same as the value of the dir attribute in -HTML, or ltr if that is not specified. ---ALLOWED-- -'ltr', 'rtl' ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt deleted file mode 100644 index 6440d21032..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt +++ /dev/null @@ -1,16 +0,0 @@ -Attr.EnableID -TYPE: bool -DEFAULT: false -VERSION: 1.2.0 ---DESCRIPTION-- -Allows the ID attribute in HTML. This is disabled by default due to the -fact that without proper configuration user input can easily break the -validation of a webpage by specifying an ID that is already on the -surrounding HTML. If you don't mind throwing caution to the wind, enable -this directive, but I strongly recommend you also consider blacklisting IDs -you use (%Attr.IDBlacklist) or prefixing all user supplied IDs -(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of -pre-1.2.0 versions. ---ALIASES-- -HTML.EnableAttrID ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt deleted file mode 100644 index f31d226f58..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt +++ /dev/null @@ -1,8 +0,0 @@ -Attr.ForbiddenClasses -TYPE: lookup -VERSION: 4.0.0 -DEFAULT: array() ---DESCRIPTION-- -List of forbidden class values in the class attribute. By default, this is -empty, which means that no classes are forbidden. See also %Attr.AllowedClasses. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt deleted file mode 100644 index 5f2b5e3d2c..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt +++ /dev/null @@ -1,5 +0,0 @@ -Attr.IDBlacklist -TYPE: list -DEFAULT: array() -DESCRIPTION: Array of IDs not allowed in the document. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt deleted file mode 100644 index 6f5824586e..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt +++ /dev/null @@ -1,9 +0,0 @@ -Attr.IDBlacklistRegexp -TYPE: string/null -VERSION: 1.6.0 -DEFAULT: NULL ---DESCRIPTION-- -PCRE regular expression to be matched against all IDs. If the expression is -matches, the ID is rejected. Use this with care: may cause significant -degradation. ID matching is done after all other validation. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt deleted file mode 100644 index cc49d43fd0..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt +++ /dev/null @@ -1,12 +0,0 @@ -Attr.IDPrefix -TYPE: string -VERSION: 1.2.0 -DEFAULT: '' ---DESCRIPTION-- -String to prefix to IDs. If you have no idea what IDs your pages may use, -you may opt to simply add a prefix to all user-submitted ID attributes so -that they are still usable, but will not conflict with core page IDs. -Example: setting the directive to 'user_' will result in a user submitted -'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true -before using this. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt deleted file mode 100644 index 2c5924a7ad..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt +++ /dev/null @@ -1,14 +0,0 @@ -Attr.IDPrefixLocal -TYPE: string -VERSION: 1.2.0 -DEFAULT: '' ---DESCRIPTION-- -Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you -need to allow multiple sets of user content on web page, you may need to -have a seperate prefix that changes with each iteration. This way, -seperately submitted user content displayed on the same page doesn't -clobber each other. Ideal values are unique identifiers for the content it -represents (i.e. the id of the row in the database). Be sure to add a -seperator (like an underscore) at the end. Warning: this directive will -not work unless %Attr.IDPrefix is set to a non-empty value! ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt deleted file mode 100644 index d5caa1bb97..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt +++ /dev/null @@ -1,31 +0,0 @@ -AutoFormat.AutoParagraph -TYPE: bool -VERSION: 2.0.1 -DEFAULT: false ---DESCRIPTION-- - -

- This directive turns on auto-paragraphing, where double newlines are - converted in to paragraphs whenever possible. Auto-paragraphing: -

-
    -
  • Always applies to inline elements or text in the root node,
  • -
  • Applies to inline elements or text with double newlines in nodes - that allow paragraph tags,
  • -
  • Applies to double newlines in paragraph tags
  • -
-

- p tags must be allowed for this directive to take effect. - We do not use br tags for paragraphing, as that is - semantically incorrect. -

-

- To prevent auto-paragraphing as a content-producer, refrain from using - double-newlines except to specify a new paragraph or in contexts where - it has special meaning (whitespace usually has no meaning except in - tags like pre, so this should not be difficult.) To prevent - the paragraphing of inline text adjacent to block elements, wrap them - in div tags (the behavior is slightly different outside of - the root node.) -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt deleted file mode 100644 index 2a476481af..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt +++ /dev/null @@ -1,12 +0,0 @@ -AutoFormat.Custom -TYPE: list -VERSION: 2.0.1 -DEFAULT: array() ---DESCRIPTION-- - -

- This directive can be used to add custom auto-format injectors. - Specify an array of injector names (class name minus the prefix) - or concrete implementations. Injector class must exist. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt deleted file mode 100644 index 663064a344..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt +++ /dev/null @@ -1,11 +0,0 @@ -AutoFormat.DisplayLinkURI -TYPE: bool -VERSION: 3.2.0 -DEFAULT: false ---DESCRIPTION-- -

- This directive turns on the in-text display of URIs in <a> tags, and disables - those links. For example, example becomes - example (http://example.com). -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt deleted file mode 100644 index 3a48ba960e..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt +++ /dev/null @@ -1,12 +0,0 @@ -AutoFormat.Linkify -TYPE: bool -VERSION: 2.0.1 -DEFAULT: false ---DESCRIPTION-- - -

- This directive turns on linkification, auto-linking http, ftp and - https URLs. a tags with the href attribute - must be allowed. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt deleted file mode 100644 index db58b13464..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt +++ /dev/null @@ -1,12 +0,0 @@ -AutoFormat.PurifierLinkify.DocURL -TYPE: string -VERSION: 2.0.1 -DEFAULT: '#%s' -ALIASES: AutoFormatParam.PurifierLinkifyDocURL ---DESCRIPTION-- -

- Location of configuration documentation to link to, let %s substitute - into the configuration's namespace and directive names sans the percent - sign. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt deleted file mode 100644 index 7996488be0..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt +++ /dev/null @@ -1,12 +0,0 @@ -AutoFormat.PurifierLinkify -TYPE: bool -VERSION: 2.0.1 -DEFAULT: false ---DESCRIPTION-- - -

- Internal auto-formatter that converts configuration directives in - syntax %Namespace.Directive to links. a tags - with the href attribute must be allowed. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt deleted file mode 100644 index 35c393b4e6..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt +++ /dev/null @@ -1,11 +0,0 @@ -AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions -TYPE: lookup -VERSION: 4.0.0 -DEFAULT: array('td' => true, 'th' => true) ---DESCRIPTION-- -

- When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp - are enabled, this directive defines what HTML elements should not be - removede if they have only a non-breaking space in them. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt deleted file mode 100644 index ca17eb1dc4..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt +++ /dev/null @@ -1,15 +0,0 @@ -AutoFormat.RemoveEmpty.RemoveNbsp -TYPE: bool -VERSION: 4.0.0 -DEFAULT: false ---DESCRIPTION-- -

- When enabled, HTML Purifier will treat any elements that contain only - non-breaking spaces as well as regular whitespace as empty, and remove - them when %AutoForamt.RemoveEmpty is enabled. -

-

- See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements - that don't have this behavior applied to them. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt deleted file mode 100644 index 34657ba47b..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt +++ /dev/null @@ -1,46 +0,0 @@ -AutoFormat.RemoveEmpty -TYPE: bool -VERSION: 3.2.0 -DEFAULT: false ---DESCRIPTION-- -

- When enabled, HTML Purifier will attempt to remove empty elements that - contribute no semantic information to the document. The following types - of nodes will be removed: -

-
  • - Tags with no attributes and no content, and that are not empty - elements (remove <a></a> but not - <br />), and -
  • -
  • - Tags with no content, except for:
      -
    • The colgroup element, or
    • -
    • - Elements with the id or name attribute, - when those attributes are permitted on those elements. -
    • -
  • -
-

- Please be very careful when using this functionality; while it may not - seem that empty elements contain useful information, they can alter the - layout of a document given appropriate styling. This directive is most - useful when you are processing machine-generated HTML, please avoid using - it on regular user HTML. -

-

- Elements that contain only whitespace will be treated as empty. Non-breaking - spaces, however, do not count as whitespace. See - %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior. -

-

- This algorithm is not perfect; you may still notice some empty tags, - particularly if a node had elements, but those elements were later removed - because they were not permitted in that context, or tags that, after - being auto-closed by another tag, where empty. This is for safety reasons - to prevent clever code from breaking validation. The general rule of thumb: - if a tag looked empty on the way in, it will get removed; if HTML Purifier - made it empty, it will stay. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt deleted file mode 100644 index dde990ab26..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt +++ /dev/null @@ -1,11 +0,0 @@ -AutoFormat.RemoveSpansWithoutAttributes -TYPE: bool -VERSION: 4.0.1 -DEFAULT: false ---DESCRIPTION-- -

- This directive causes span tags without any attributes - to be removed. It will also remove spans that had all attributes - removed during processing. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt deleted file mode 100644 index b324608f76..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt +++ /dev/null @@ -1,8 +0,0 @@ -CSS.AllowImportant -TYPE: bool -DEFAULT: false -VERSION: 3.1.0 ---DESCRIPTION-- -This parameter determines whether or not !important cascade modifiers should -be allowed in user CSS. If false, !important will stripped. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt deleted file mode 100644 index 748be0eec8..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt +++ /dev/null @@ -1,11 +0,0 @@ -CSS.AllowTricky -TYPE: bool -DEFAULT: false -VERSION: 3.1.0 ---DESCRIPTION-- -This parameter determines whether or not to allow "tricky" CSS properties and -values. Tricky CSS properties/values can drastically modify page layout or -be used for deceptive practices but do not directly constitute a security risk. -For example, display:none; is considered a tricky property that -will only be allowed if this directive is set to true. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt deleted file mode 100644 index 460112ebe0..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt +++ /dev/null @@ -1,18 +0,0 @@ -CSS.AllowedProperties -TYPE: lookup/null -VERSION: 3.1.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- If HTML Purifier's style attributes 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 an attribute that HTML Purifier never - supported in the first place. -

-

- Warning: If another directive conflicts with the - elements here, that directive will win and override. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt deleted file mode 100644 index 5cb7dda3ba..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt +++ /dev/null @@ -1,11 +0,0 @@ -CSS.DefinitionRev -TYPE: int -VERSION: 2.0.0 -DEFAULT: 1 ---DESCRIPTION-- - -

- Revision identifier for your custom definition. See - %HTML.DefinitionRev for details. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt deleted file mode 100644 index 7a3291470c..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt +++ /dev/null @@ -1,16 +0,0 @@ -CSS.MaxImgLength -TYPE: string/null -DEFAULT: '1200px' -VERSION: 3.1.1 ---DESCRIPTION-- -

- This parameter sets the maximum allowed length on img tags, - effectively the width and height properties. - Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is - in place to prevent imagecrash attacks, disable with null at your own risk. - This directive is similar to %HTML.MaxImgLength, and both should be - concurrently edited, although there are - subtle differences in the input format (the CSS max is a number with - a unit). -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt deleted file mode 100644 index 148eedb8be..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt +++ /dev/null @@ -1,10 +0,0 @@ -CSS.Proprietary -TYPE: bool -VERSION: 3.0.0 -DEFAULT: false ---DESCRIPTION-- - -

- Whether or not to allow safe, proprietary CSS values. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt deleted file mode 100644 index c486724c88..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt +++ /dev/null @@ -1,14 +0,0 @@ -Cache.DefinitionImpl -TYPE: string/null -VERSION: 2.0.0 -DEFAULT: 'Serializer' ---DESCRIPTION-- - -This directive defines which method to use when caching definitions, -the complex data-type that makes HTML Purifier tick. Set to null -to disable caching (not recommended, as you will see a definite -performance degradation). - ---ALIASES-- -Core.DefinitionCache ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt deleted file mode 100644 index 54036507d6..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt +++ /dev/null @@ -1,13 +0,0 @@ -Cache.SerializerPath -TYPE: string/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- Absolute path with no trailing slash to store serialized definitions in. - Default is within the - HTML Purifier library inside DefinitionCache/Serializer. This - path must be writable by the webserver. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt deleted file mode 100644 index 568cbf3b32..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt +++ /dev/null @@ -1,18 +0,0 @@ -Core.AggressivelyFixLt -TYPE: bool -VERSION: 2.1.0 -DEFAULT: true ---DESCRIPTION-- -

- This directive enables aggressive pre-filter fixes HTML Purifier can - perform in order to ensure that open angled-brackets do not get killed - during parsing stage. Enabling this will result in two preg_replace_callback - calls and at least two preg_replace calls for every HTML document parsed; - if your users make very well-formed HTML, you can set this directive false. - This has no effect when DirectLex is used. -

-

- Notice: This directive's default turned from false to true - in HTML Purifier 3.2.0. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt deleted file mode 100644 index d7317911fa..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt +++ /dev/null @@ -1,12 +0,0 @@ -Core.CollectErrors -TYPE: bool -VERSION: 2.0.0 -DEFAULT: false ---DESCRIPTION-- - -Whether or not to collect errors found while filtering the document. This -is a useful way to give feedback to your users. Warning: -Currently this feature is very patchy and experimental, with lots of -possible error messages not yet implemented. It will not cause any -problems, but it may not help your users either. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt deleted file mode 100644 index 08b381d34c..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt +++ /dev/null @@ -1,28 +0,0 @@ -Core.ColorKeywords -TYPE: hash -VERSION: 2.0.0 ---DEFAULT-- -array ( - 'maroon' => '#800000', - 'red' => '#FF0000', - 'orange' => '#FFA500', - 'yellow' => '#FFFF00', - 'olive' => '#808000', - 'purple' => '#800080', - 'fuchsia' => '#FF00FF', - 'white' => '#FFFFFF', - 'lime' => '#00FF00', - 'green' => '#008000', - 'navy' => '#000080', - 'blue' => '#0000FF', - 'aqua' => '#00FFFF', - 'teal' => '#008080', - 'black' => '#000000', - 'silver' => '#C0C0C0', - 'gray' => '#808080', -) ---DESCRIPTION-- - -Lookup array of color names to six digit hexadecimal number corresponding -to color, with preceding hash mark. Used when parsing colors. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt deleted file mode 100644 index 64b114fce2..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt +++ /dev/null @@ -1,14 +0,0 @@ -Core.ConvertDocumentToFragment -TYPE: bool -DEFAULT: true ---DESCRIPTION-- - -This parameter determines whether or not the filter should convert -input that is a full document with html and body tags to a fragment -of just the contents of a body tag. This parameter is simply something -HTML Purifier can do during an edge-case: for most inputs, this -processing is not necessary. - ---ALIASES-- -Core.AcceptFullDocuments ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt deleted file mode 100644 index 36f16e07ea..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt +++ /dev/null @@ -1,17 +0,0 @@ -Core.DirectLexLineNumberSyncInterval -TYPE: int -VERSION: 2.0.0 -DEFAULT: 0 ---DESCRIPTION-- - -

- Specifies the number of tokens the DirectLex line number tracking - implementations should process before attempting to resyncronize the - current line count by manually counting all previous new-lines. When - at 0, this functionality is disabled. Lower values will decrease - performance, and this is only strictly necessary if the counting - algorithm is buggy (in which case you should report it as a bug). - This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is - not being used. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt deleted file mode 100644 index 8bfb47c3ac..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt +++ /dev/null @@ -1,15 +0,0 @@ -Core.Encoding -TYPE: istring -DEFAULT: 'utf-8' ---DESCRIPTION-- -If for some reason you are unable to convert all webpages to UTF-8, you can -use this directive as a stop-gap compatibility change to let HTML Purifier -deal with non UTF-8 input. This technique has notable deficiencies: -absolutely no characters outside of the selected character encoding will be -preserved, not even the ones that have been ampersand escaped (this is due -to a UTF-8 specific feature that automatically resolves all -entities), making it pretty useless for anything except the most I18N-blind -applications, although %Core.EscapeNonASCIICharacters offers fixes this -trouble with another tradeoff. This directive only accepts ISO-8859-1 if -iconv is not enabled. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt deleted file mode 100644 index 4d5b5055cd..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt +++ /dev/null @@ -1,10 +0,0 @@ -Core.EscapeInvalidChildren -TYPE: bool -DEFAULT: false ---DESCRIPTION-- -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. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt deleted file mode 100644 index a7a5b249bb..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt +++ /dev/null @@ -1,7 +0,0 @@ -Core.EscapeInvalidTags -TYPE: bool -DEFAULT: false ---DESCRIPTION-- -When true, invalid tags will be written back to the document as plain text. -Otherwise, they are silently dropped. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt deleted file mode 100644 index abb499948a..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt +++ /dev/null @@ -1,13 +0,0 @@ -Core.EscapeNonASCIICharacters -TYPE: bool -VERSION: 1.4.0 -DEFAULT: false ---DESCRIPTION-- -This directive overcomes a deficiency in %Core.Encoding by blindly -converting all non-ASCII characters into decimal numeric entities before -converting it to its native encoding. This means that even characters that -can be expressed in the non-UTF-8 encoding will be entity-ized, which can -be a real downer for encodings like Big5. It also assumes that the ASCII -repetoire is available, although this is the case for almost all encodings. -Anyway, use UTF-8! ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt deleted file mode 100644 index 915391edb7..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt +++ /dev/null @@ -1,19 +0,0 @@ -Core.HiddenElements -TYPE: lookup ---DEFAULT-- -array ( - 'script' => true, - 'style' => true, -) ---DESCRIPTION-- - -

- This directive is a lookup array of elements which should have their - contents removed when they are not allowed by the HTML definition. - For example, the contents of a script tag are not - normally shown in a document, so if script tags are to be removed, - their contents should be removed to. This is opposed to a b - tag, which defines some presentational changes but does not hide its - contents. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt deleted file mode 100644 index 233fca14f8..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt +++ /dev/null @@ -1,10 +0,0 @@ -Core.Language -TYPE: string -VERSION: 2.0.0 -DEFAULT: 'en' ---DESCRIPTION-- - -ISO 639 language code for localizable things in HTML Purifier to use, -which is mainly error reporting. There is currently only an English (en) -translation, so this directive is currently useless. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt deleted file mode 100644 index 8983e2cca9..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt +++ /dev/null @@ -1,34 +0,0 @@ -Core.LexerImpl -TYPE: mixed/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- This parameter determines what lexer implementation can be used. The - valid values are: -

-
-
null
-
- Recommended, the lexer implementation will be auto-detected based on - your PHP-version and configuration. -
-
string lexer identifier
-
- This is a slim way of manually overridding the implementation. - Currently recognized values are: DOMLex (the default PHP5 -implementation) - and DirectLex (the default PHP4 implementation). Only use this if - you know what you are doing: usually, the auto-detection will - manage things for cases you aren't even aware of. -
-
object lexer instance
-
- Super-advanced: you can specify your own, custom, implementation that - implements the interface defined by HTMLPurifier_Lexer. - I may remove this option simply because I don't expect anyone - to use it. -
-
---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt deleted file mode 100644 index eb841a7597..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt +++ /dev/null @@ -1,16 +0,0 @@ -Core.MaintainLineNumbers -TYPE: bool/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- If true, HTML Purifier will add line number information to all tokens. - This is useful when error reporting is turned on, but can result in - significant performance degradation and should not be used when - unnecessary. This directive must be used with the DirectLex lexer, - as the DOMLex lexer does not (yet) support this functionality. - If the value is null, an appropriate value will be selected based - on other configuration. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt deleted file mode 100644 index 4070c2a0de..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt +++ /dev/null @@ -1,12 +0,0 @@ -Core.RemoveInvalidImg -TYPE: bool -DEFAULT: true -VERSION: 1.3.0 ---DESCRIPTION-- - -

- This directive enables pre-emptive URI checking in img - tags, as the attribute validation strategy is not authorized to - remove elements from the document. Revert to pre-1.3.0 behavior by setting to false. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt deleted file mode 100644 index a4cd966df8..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt +++ /dev/null @@ -1,12 +0,0 @@ -Core.RemoveScriptContents -TYPE: bool/null -DEFAULT: NULL -VERSION: 2.0.0 -DEPRECATED-VERSION: 2.1.0 -DEPRECATED-USE: Core.HiddenElements ---DESCRIPTION-- -

- This directive enables HTML Purifier to remove not only script tags - but all of their contents. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt deleted file mode 100644 index 3db50ef204..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt +++ /dev/null @@ -1,11 +0,0 @@ -Filter.Custom -TYPE: list -VERSION: 3.1.0 -DEFAULT: array() ---DESCRIPTION-- -

- This directive can be used to add custom filters; it is nearly the - equivalent of the now deprecated HTMLPurifier->addFilter() - method. Specify an array of concrete implementations. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt deleted file mode 100644 index 16829bcda0..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt +++ /dev/null @@ -1,14 +0,0 @@ -Filter.ExtractStyleBlocks.Escaping -TYPE: bool -VERSION: 3.0.0 -DEFAULT: true -ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping ---DESCRIPTION-- - -

- Whether or not to escape the dangerous characters <, > and & - as \3C, \3E and \26, respectively. This is can be safely set to false - if the contents of StyleBlocks will be placed in an external stylesheet, - where there is no risk of it being interpreted as HTML. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt deleted file mode 100644 index 7f95f54d12..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt +++ /dev/null @@ -1,29 +0,0 @@ -Filter.ExtractStyleBlocks.Scope -TYPE: string/null -VERSION: 3.0.0 -DEFAULT: NULL -ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope ---DESCRIPTION-- - -

- If you would like users to be able to define external stylesheets, but - only allow them to specify CSS declarations for a specific node and - prevent them from fiddling with other elements, use this directive. - It accepts any valid CSS selector, and will prepend this to any - CSS declaration extracted from the document. For example, if this - directive is set to #user-content and a user uses the - selector a:hover, the final selector will be - #user-content a:hover. -

-

- The comma shorthand may be used; consider the above example, with - #user-content, #user-content2, the final selector will - be #user-content a:hover, #user-content2 a:hover. -

-

- Warning: It is possible for users to bypass this measure - using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML - Purifier, and I am working to get it fixed. Until then, HTML Purifier - performs a basic check to prevent this. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt deleted file mode 100644 index 6c231b2d7f..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt +++ /dev/null @@ -1,16 +0,0 @@ -Filter.ExtractStyleBlocks.TidyImpl -TYPE: mixed/null -VERSION: 3.1.0 -DEFAULT: NULL -ALIASES: FilterParam.ExtractStyleBlocksTidyImpl ---DESCRIPTION-- -

- If left NULL, HTML Purifier will attempt to instantiate a csstidy - class to use for internal cleaning. This will usually be good enough. -

-

- However, for trusted user input, you can set this to false to - disable cleaning. In addition, you can supply your own concrete implementation - of Tidy's interface to use, although I don't know why you'd want to do that. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt deleted file mode 100644 index 078d087417..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt +++ /dev/null @@ -1,74 +0,0 @@ -Filter.ExtractStyleBlocks -TYPE: bool -VERSION: 3.1.0 -DEFAULT: false -EXTERNAL: CSSTidy ---DESCRIPTION-- -

- This directive turns on the style block extraction filter, which removes - style blocks from input HTML, cleans them up with CSSTidy, - and places them in the StyleBlocks context variable, for further - use by you, usually to be placed in an external stylesheet, or a - style block in the head of your document. -

-

- Sample usage: -

-
';
-?>
-
-
-
-  Filter.ExtractStyleBlocks
-body {color:#F00;} Some text';
-
-    $config = HTMLPurifier_Config::createDefault();
-    $config->set('Filter', 'ExtractStyleBlocks', true);
-    $purifier = new HTMLPurifier($config);
-
-    $html = $purifier->purify($dirty);
-
-    // This implementation writes the stylesheets to the styles/ directory.
-    // You can also echo the styles inside the document, but it's a bit
-    // more difficult to make sure they get interpreted properly by
-    // browsers; try the usual CSS armoring techniques.
-    $styles = $purifier->context->get('StyleBlocks');
-    $dir = 'styles/';
-    if (!is_dir($dir)) mkdir($dir);
-    $hash = sha1($_GET['html']);
-    foreach ($styles as $i => $style) {
-        file_put_contents($name = $dir . $hash . "_$i");
-        echo '';
-    }
-?>
-
-
-  
- -
- - -]]>
-

- Warning: It is possible for a user to mount an - imagecrash attack using this CSS. Counter-measures are difficult; - it is not simply enough to limit the range of CSS lengths (using - relative lengths with many nesting levels allows for large values - to be attained without actually specifying them in the stylesheet), - and the flexible nature of selectors makes it difficult to selectively - disable lengths on image tags (HTML Purifier, however, does disable - CSS width and height in inline styling). There are probably two effective - counter measures: an explicit width and height set to auto in all - images in your document (unlikely) or the disabling of width and - height (somewhat reasonable). Whether or not these measures should be - used is left to the reader. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt deleted file mode 100644 index 7fa6536b2c..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt +++ /dev/null @@ -1,11 +0,0 @@ -Filter.YouTube -TYPE: bool -VERSION: 3.1.0 -DEFAULT: false ---DESCRIPTION-- -

- This directive enables YouTube video embedding in HTML Purifier. Check - this document - on embedding videos for more information on what this filter does. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt deleted file mode 100644 index 3e231d2d16..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt +++ /dev/null @@ -1,22 +0,0 @@ -HTML.Allowed -TYPE: itext/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- This is a convenience directive that rolls the functionality of - %HTML.AllowedElements and %HTML.AllowedAttributes into one directive. - Specify elements and attributes that are allowed using: - element1[attr1|attr2],element2.... You can also use - newlines instead of commas to separate elements. -

-

- Warning: - All of the constraints on the component directives are still enforced. - The syntax is a subset of TinyMCE's valid_elements - whitelist: directly copy-pasting it here will probably result in - broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes - are set, this directive has no effect. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt deleted file mode 100644 index fcf093f17d..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt +++ /dev/null @@ -1,19 +0,0 @@ -HTML.AllowedAttributes -TYPE: lookup/null -VERSION: 1.3.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- If HTML Purifier's attribute set is unsatisfactory, overload it! - The syntax is "tag.attr" or "*.attr" for the global attributes - (style, id, class, dir, lang, xml:lang). -

-

- Warning: If another directive conflicts with the - elements here, that directive will win and override. For - example, %HTML.EnableAttrID will take precedence over *.id in this - directive. You must set that directive to true before you can use - IDs at all. -

---# vim: et sw=4 sts=4 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-- -

- 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. -

-

- Warning: If another directive conflicts with the - elements here, that directive will win and override. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt deleted file mode 100644 index 5a59a55c08..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt +++ /dev/null @@ -1,20 +0,0 @@ -HTML.AllowedModules -TYPE: lookup/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- A doctype comes with a set of usual modules to use. Without having - to mucking about with the doctypes, you can quickly activate or - disable these modules by specifying which modules you wish to allow - with this directive. This is most useful for unit testing specific - modules, although end users may find it useful for their own ends. -

-

- If you specify a module that does not exist, the manager will silently - fail to use it, so be careful! User-defined modules are not affected - by this directive. Modules defined in %HTML.CoreModules are not - affected by this directive. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt deleted file mode 100644 index 151fb7b826..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt +++ /dev/null @@ -1,11 +0,0 @@ -HTML.Attr.Name.UseCDATA -TYPE: bool -DEFAULT: false -VERSION: 4.0.0 ---DESCRIPTION-- -The W3C specification DTD defines the name attribute to be CDATA, not ID, due -to limitations of DTD. In certain documents, this relaxed behavior is desired, -whether it is to specify duplicate names, or to specify names that would be -illegal IDs (for example, names that begin with a digit.) Set this configuration -directive to true to use the relaxed parsing rules. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt deleted file mode 100644 index 45ae469ec9..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt +++ /dev/null @@ -1,18 +0,0 @@ -HTML.BlockWrapper -TYPE: string -VERSION: 1.3.0 -DEFAULT: 'p' ---DESCRIPTION-- - -

- String name of element to wrap inline elements that are inside a block - context. This only occurs in the children of blockquote in strict mode. -

-

- Example: by default value, - <blockquote>Foo</blockquote> would become - <blockquote><p>Foo</p></blockquote>. - The <p> tags can be replaced with whatever you desire, - as long as it is a block level element. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt deleted file mode 100644 index 5246188795..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt +++ /dev/null @@ -1,23 +0,0 @@ -HTML.CoreModules -TYPE: lookup -VERSION: 2.0.0 ---DEFAULT-- -array ( - 'Structure' => true, - 'Text' => true, - 'Hypertext' => true, - 'List' => true, - 'NonXMLCommonAttributes' => true, - 'XMLCommonAttributes' => true, - 'CommonAttributes' => true, -) ---DESCRIPTION-- - -

- Certain modularized doctypes (XHTML, namely), have certain modules - that must be included for the doctype to be an conforming document - type: put those modules here. By default, XHTML's core modules - are used. You can set this to a blank array to disable core module - protection, but this is not recommended. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt deleted file mode 100644 index a64e3d7c36..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt +++ /dev/null @@ -1,9 +0,0 @@ -HTML.CustomDoctype -TYPE: string/null -VERSION: 2.0.1 -DEFAULT: NULL ---DESCRIPTION-- - -A custom doctype for power-users who defined there 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/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt deleted file mode 100644 index 103db754a2..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt +++ /dev/null @@ -1,33 +0,0 @@ -HTML.DefinitionID -TYPE: string/null -DEFAULT: NULL -VERSION: 2.0.0 ---DESCRIPTION-- - -

- Unique identifier for a custom-built HTML definition. If you edit - the raw version of the HTMLDefinition, introducing changes that the - configuration object does not reflect, you must specify this variable. - If you change your custom edits, you should change this directive, or - clear your cache. Example: -

-
-$config = HTMLPurifier_Config::createDefault();
-$config->set('HTML', 'DefinitionID', '1');
-$def = $config->getHTMLDefinition();
-$def->addAttribute('a', 'tabindex', 'Number');
-
-

- In the above example, the configuration is still at the defaults, but - using the advanced API, an extra attribute has been added. The - configuration object normally has no way of knowing that this change - has taken place, so it needs an extra directive: %HTML.DefinitionID. - If someone else attempts to use the default configuration, these two - pieces of code will not clobber each other in the cache, since one has - an extra directive attached to it. -

-

- You must specify a value to this directive to use the - advanced API features. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt deleted file mode 100644 index 229ae0267a..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt +++ /dev/null @@ -1,16 +0,0 @@ -HTML.DefinitionRev -TYPE: int -VERSION: 2.0.0 -DEFAULT: 1 ---DESCRIPTION-- - -

- Revision identifier for your custom definition specified in - %HTML.DefinitionID. This serves the same purpose: uniquely identifying - your custom definition, but this one does so in a chronological - context: revision 3 is more up-to-date then revision 2. Thus, when - this gets incremented, the cache handling is smart enough to clean - up any older revisions of your definition as well as flush the - cache. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt deleted file mode 100644 index 9dab497f2f..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt +++ /dev/null @@ -1,11 +0,0 @@ -HTML.Doctype -TYPE: string/null -DEFAULT: NULL ---DESCRIPTION-- -Doctype to use during filtering. Technically speaking this is not actually -a doctype (as it does not identify a corresponding DTD), but we are using -this name for sake of simplicity. When non-blank, this will override any -older directives like %HTML.XHTML or %HTML.Strict. ---ALLOWED-- -'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1' ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt deleted file mode 100644 index 57358f9bad..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt +++ /dev/null @@ -1,21 +0,0 @@ -HTML.ForbiddenAttributes -TYPE: lookup -VERSION: 3.1.0 -DEFAULT: array() ---DESCRIPTION-- -

- While this directive is similar to %HTML.AllowedAttributes, for - forwards-compatibility with XML, this attribute has a different syntax. Instead of - tag.attr, use tag@attr. To disallow href - attributes in a tags, set this directive to - a@href. You can also disallow an attribute globally with - attr or *@attr (either syntax is fine; the latter - is provided for consistency with %HTML.AllowedAttributes). -

-

- Warning: This directive complements %HTML.ForbiddenElements, - accordingly, check - out that directive for a discussion of why you - should think twice before using this directive. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt deleted file mode 100644 index 93a53e14fb..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt +++ /dev/null @@ -1,20 +0,0 @@ -HTML.ForbiddenElements -TYPE: lookup -VERSION: 3.1.0 -DEFAULT: array() ---DESCRIPTION-- -

- This was, perhaps, the most requested feature ever in HTML - Purifier. Please don't abuse it! This is the logical inverse of - %HTML.AllowedElements, and it will override that directive, or any - other directive. -

-

- If possible, %HTML.Allowed is recommended over this directive, because it - can sometimes be difficult to tell whether or not you've forbidden all of - the behavior you would like to disallow. If you forbid img - with the expectation of preventing images on your site, you'll be in for - a nasty surprise when people start using the background-image - CSS property. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt deleted file mode 100644 index e424c386ec..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt +++ /dev/null @@ -1,14 +0,0 @@ -HTML.MaxImgLength -TYPE: int/null -DEFAULT: 1200 -VERSION: 3.1.1 ---DESCRIPTION-- -

- This directive controls the maximum number of pixels in the width and - height attributes in img tags. This is - in place to prevent imagecrash attacks, disable with null at your own risk. - This directive is similar to %CSS.MaxImgLength, and both should be - concurrently edited, although there are - subtle differences in the input format (the HTML max is an integer). -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt deleted file mode 100644 index 62e8e160c7..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt +++ /dev/null @@ -1,12 +0,0 @@ -HTML.Parent -TYPE: string -VERSION: 1.3.0 -DEFAULT: 'div' ---DESCRIPTION-- - -

- String name of element that HTML fragment passed to library will be - inserted in. An interesting variation would be using span as the - parent element, meaning that only inline tags would be allowed. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt deleted file mode 100644 index dfb720496d..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt +++ /dev/null @@ -1,12 +0,0 @@ -HTML.Proprietary -TYPE: bool -VERSION: 3.1.0 -DEFAULT: false ---DESCRIPTION-- -

- Whether or not to allow proprietary elements and attributes in your - documents, as per HTMLPurifier_HTMLModule_Proprietary. - Warning: This can cause your documents to stop - validating! -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt deleted file mode 100644 index cdda09a4c5..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt +++ /dev/null @@ -1,13 +0,0 @@ -HTML.SafeEmbed -TYPE: bool -VERSION: 3.1.1 -DEFAULT: false ---DESCRIPTION-- -

- Whether or not to permit embed tags in documents, with a number of extra - security features added to prevent script execution. This is similar to - what websites like MySpace do to embed tags. Embed is a proprietary - element and will cause your website to stop validating; you should - see if you can use %Output.FlashCompat with %HTML.SafeObject instead - first.

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt deleted file mode 100644 index ceb342e22b..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt +++ /dev/null @@ -1,13 +0,0 @@ -HTML.SafeObject -TYPE: bool -VERSION: 3.1.1 -DEFAULT: false ---DESCRIPTION-- -

- Whether or not to permit object tags in documents, with a number of extra - security features added to prevent script execution. This is similar to - what websites like MySpace do to object tags. You should also enable - %Output.FlashCompat in order to generate Internet Explorer - compatibility code for your object tags. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt deleted file mode 100644 index a8b1de56be..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt +++ /dev/null @@ -1,9 +0,0 @@ -HTML.Strict -TYPE: bool -VERSION: 1.3.0 -DEFAULT: false -DEPRECATED-VERSION: 1.7.0 -DEPRECATED-USE: HTML.Doctype ---DESCRIPTION-- -Determines whether or not to use Transitional (loose) or Strict rulesets. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt deleted file mode 100644 index b4c271b7fa..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt +++ /dev/null @@ -1,8 +0,0 @@ -HTML.TidyAdd -TYPE: lookup -VERSION: 2.0.0 -DEFAULT: array() ---DESCRIPTION-- - -Fixes to add to the default set of Tidy fixes as per your level. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt deleted file mode 100644 index 4186ccd0d1..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt +++ /dev/null @@ -1,24 +0,0 @@ -HTML.TidyLevel -TYPE: string -VERSION: 2.0.0 -DEFAULT: 'medium' ---DESCRIPTION-- - -

General level of cleanliness the Tidy module should enforce. -There are four allowed values:

-
-
none
-
No extra tidying should be done
-
light
-
Only fix elements that would be discarded otherwise due to - lack of support in doctype
-
medium
-
Enforce best practices
-
heavy
-
Transform all deprecated elements and attributes to standards - compliant equivalents
-
- ---ALLOWED-- -'none', 'light', 'medium', 'heavy' ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt deleted file mode 100644 index 996762bd1d..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt +++ /dev/null @@ -1,8 +0,0 @@ -HTML.TidyRemove -TYPE: lookup -VERSION: 2.0.0 -DEFAULT: array() ---DESCRIPTION-- - -Fixes to remove from the default set of Tidy fixes as per your level. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt deleted file mode 100644 index 89133b1a38..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt +++ /dev/null @@ -1,8 +0,0 @@ -HTML.Trusted -TYPE: bool -VERSION: 2.0.0 -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. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt deleted file mode 100644 index 2a47e384f4..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt +++ /dev/null @@ -1,11 +0,0 @@ -HTML.XHTML -TYPE: bool -DEFAULT: true -VERSION: 1.1.0 -DEPRECATED-VERSION: 1.7.0 -DEPRECATED-USE: HTML.Doctype ---DESCRIPTION-- -Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor. ---ALIASES-- -Core.XHTML ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt deleted file mode 100644 index 08921fde70..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt +++ /dev/null @@ -1,10 +0,0 @@ -Output.CommentScriptContents -TYPE: bool -VERSION: 2.0.0 -DEFAULT: true ---DESCRIPTION-- -Determines whether or not HTML Purifier should attempt to fix up the -contents of script tags for legacy browsers with comments. ---ALIASES-- -Core.CommentScriptContents ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt deleted file mode 100644 index 93398e8598..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt +++ /dev/null @@ -1,11 +0,0 @@ -Output.FlashCompat -TYPE: bool -VERSION: 4.1.0 -DEFAULT: false ---DESCRIPTION-- -

- If true, HTML Purifier will generate Internet Explorer compatibility - code for all object code. This is highly recommended if you enable - %HTML.SafeObject. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt deleted file mode 100644 index 79f8ad82cf..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt +++ /dev/null @@ -1,13 +0,0 @@ -Output.Newline -TYPE: string/null -VERSION: 2.0.1 -DEFAULT: NULL ---DESCRIPTION-- - -

- Newline string to format final output with. If left null, HTML Purifier - will auto-detect the default newline type of the system and use that; - you can manually override it here. Remember, \r\n is Windows, \r - is Mac, and \n is Unix. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt deleted file mode 100644 index 232b02362a..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt +++ /dev/null @@ -1,14 +0,0 @@ -Output.SortAttr -TYPE: bool -VERSION: 3.2.0 -DEFAULT: false ---DESCRIPTION-- -

- If true, HTML Purifier will sort attributes by name before writing them back - to the document, converting a tag like: <el b="" a="" c="" /> - to <el a="" b="" c="" />. This is a workaround for - a bug in FCKeditor which causes it to swap attributes order, adding noise - to text diffs. If you're not seeing this bug, chances are, you don't need - this directive. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt deleted file mode 100644 index 06bab00a0a..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt +++ /dev/null @@ -1,25 +0,0 @@ -Output.TidyFormat -TYPE: bool -VERSION: 1.1.1 -DEFAULT: false ---DESCRIPTION-- -

- Determines whether or not to run Tidy on the final output for pretty - formatting reasons, such as indentation and wrap. -

-

- This can greatly improve readability for editors who are hand-editing - the HTML, but is by no means necessary as HTML Purifier has already - fixed all major errors the HTML may have had. Tidy is a non-default - extension, and this directive will silently fail if Tidy is not - available. -

-

- If you are looking to make the overall look of your page's source - better, I recommend running Tidy on the entire page rather than just - user-content (after all, the indentation relative to the containing - blocks will be incorrect). -

---ALIASES-- -Core.TidyFormat ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt deleted file mode 100644 index 071bc0295d..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt +++ /dev/null @@ -1,7 +0,0 @@ -Test.ForceNoIconv -TYPE: bool -DEFAULT: false ---DESCRIPTION-- -When set to true, HTMLPurifier_Encoder will act as if iconv does not exist -and use only pure PHP implementations. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt deleted file mode 100644 index ae3a913f24..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt +++ /dev/null @@ -1,17 +0,0 @@ -URI.AllowedSchemes -TYPE: lookup ---DEFAULT-- -array ( - 'http' => true, - 'https' => true, - 'mailto' => true, - 'ftp' => true, - 'nntp' => true, - 'news' => true, -) ---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 data URI scheme, but it is not -enabled by default. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt deleted file mode 100644 index 876f0680cf..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt +++ /dev/null @@ -1,17 +0,0 @@ -URI.Base -TYPE: string/null -VERSION: 2.1.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- The base URI is the URI of the document this purified HTML will be - inserted into. This information is important if HTML Purifier needs - to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute - is on. You may use a non-absolute URI for this value, but behavior - may vary (%URI.MakeAbsolute deals nicely with both absolute and - relative paths, but forwards-compatibility is not guaranteed). - Warning: If set, the scheme on this URI - overrides the one specified by %URI.DefaultScheme. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt deleted file mode 100644 index 728e378cbe..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt +++ /dev/null @@ -1,10 +0,0 @@ -URI.DefaultScheme -TYPE: string -DEFAULT: 'http' ---DESCRIPTION-- - -

- Defines through what scheme the output will be served, in order to - select the proper object validator when no scheme information is present. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt deleted file mode 100644 index f05312ba86..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt +++ /dev/null @@ -1,11 +0,0 @@ -URI.DefinitionID -TYPE: string/null -VERSION: 2.1.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- Unique identifier for a custom-built URI definition. If you want - to add custom URIFilters, you must specify this value. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt deleted file mode 100644 index 80cfea93f7..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt +++ /dev/null @@ -1,11 +0,0 @@ -URI.DefinitionRev -TYPE: int -VERSION: 2.1.0 -DEFAULT: 1 ---DESCRIPTION-- - -

- Revision identifier for your custom definition. See - %HTML.DefinitionRev for details. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt deleted file mode 100644 index 71ce025a2d..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt +++ /dev/null @@ -1,14 +0,0 @@ -URI.Disable -TYPE: bool -VERSION: 1.3.0 -DEFAULT: false ---DESCRIPTION-- - -

- Disables all URIs in all forms. Not sure why you'd want to do that - (after all, the Internet's founded on the notion of a hyperlink). -

- ---ALIASES-- -Attr.DisableURI ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt deleted file mode 100644 index 13c122c8ce..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt +++ /dev/null @@ -1,11 +0,0 @@ -URI.DisableExternal -TYPE: bool -VERSION: 1.2.0 -DEFAULT: false ---DESCRIPTION-- -Disables links to external websites. This is a highly effective anti-spam -and anti-pagerank-leech measure, but comes at a hefty price: nolinks or -images outside of your domain will be allowed. Non-linkified URIs will -still be preserved. If you want to be able to link to subdomains or use -absolute URIs, specify %URI.Host for your website. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt deleted file mode 100644 index abcc1efd61..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt +++ /dev/null @@ -1,13 +0,0 @@ -URI.DisableExternalResources -TYPE: bool -VERSION: 1.3.0 -DEFAULT: false ---DESCRIPTION-- -Disables the embedding of external resources, preventing users from -embedding things like images from other hosts. This prevents access -tracking (good for email viewers), bandwidth leeching, cross-site request -forging, goatse.cx posting, and other nasties, but also results in a loss -of end-user functionality (they can't directly post a pic they posted from -Flickr anymore). Use it if you don't have a robust user-content moderation -team. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt deleted file mode 100644 index 51e6ea91f7..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt +++ /dev/null @@ -1,12 +0,0 @@ -URI.DisableResources -TYPE: bool -VERSION: 1.3.0 -DEFAULT: false ---DESCRIPTION-- - -

- 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. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt deleted file mode 100644 index ee83b121de..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt +++ /dev/null @@ -1,19 +0,0 @@ -URI.Host -TYPE: string/null -VERSION: 1.2.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- Defines the domain name of the server, so we can determine whether or - an absolute URI is from your website or not. Not strictly necessary, - as users should be using relative URIs to reference resources on your - website. It will, however, let you use absolute URIs to link to - subdomains of the domain you post here: i.e. example.com will allow - sub.example.com. However, higher up domains will still be excluded: - if you set %URI.Host to sub.example.com, example.com will be blocked. - Note: This directive overrides %URI.Base because - a given page may be on a sub-domain, but you wish HTML Purifier to be - more relaxed and allow some of the parent domains too. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt deleted file mode 100644 index 0b6df7625d..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt +++ /dev/null @@ -1,9 +0,0 @@ -URI.HostBlacklist -TYPE: list -VERSION: 1.3.0 -DEFAULT: array() ---DESCRIPTION-- -List of strings that are forbidden in the host of any URI. Use it to kill -domain names of spam, etc. Note that it will catch anything in the domain, -so moo.com will catch moo.com.example.com. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt deleted file mode 100644 index 4214900a59..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt +++ /dev/null @@ -1,13 +0,0 @@ -URI.MakeAbsolute -TYPE: bool -VERSION: 2.1.0 -DEFAULT: false ---DESCRIPTION-- - -

- Converts all URIs into absolute forms. This is useful when the HTML - being filtered assumes a specific base path, but will actually be - viewed in a different context (and setting an alternate base URI is - not possible). %URI.Base must be set for this directive to work. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt deleted file mode 100644 index 58c81dcc44..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt +++ /dev/null @@ -1,83 +0,0 @@ -URI.Munge -TYPE: string/null -VERSION: 1.3.0 -DEFAULT: NULL ---DESCRIPTION-- - -

- Munges all browsable (usually http, https and ftp) - absolute URIs into another URI, usually a URI redirection service. - This directive accepts a URI, formatted with a %s where - the url-encoded original URI should be inserted (sample: - http://www.google.com/url?q=%s). -

-

- Uses for this directive: -

-
    -
  • - Prevent PageRank leaks, while being fairly transparent - to users (you may also want to add some client side JavaScript to - override the text in the statusbar). Notice: - Many security experts believe that this form of protection does not deter spam-bots. -
  • -
  • - Redirect users to a splash page telling them they are leaving your - website. While this is poor usability practice, it is often mandated - in corporate environments. -
  • -
-

- Prior to HTML Purifier 3.1.1, this directive also enabled the munging - of browsable external resources, which could break things if your redirection - script was a splash page or used meta tags. To revert to - previous behavior, please use %URI.MungeResources. -

-

- You may want to also use %URI.MungeSecretKey along with this directive - in order to enforce what URIs your redirector script allows. Open - redirector scripts can be a security risk and negatively affect the - reputation of your domain name. -

-

- Starting with HTML Purifier 3.1.1, there is also these substitutions: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyDescriptionExample <a href="">
%r1 - The URI embeds a resource
(blank) - The URI is merely a link
%nThe name of the tag this URI came froma
%mThe name of the attribute this URI came fromhref
%pThe name of the CSS property this URI came from, or blank if irrelevant
-

- Admittedly, these letters are somewhat arbitrary; the only stipulation - was that they couldn't be a through f. r is for resource (I would have preferred - e, but you take what you can get), n is for name, m - was picked because it came after n (and I couldn't use a), p is for - property. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt deleted file mode 100644 index 6fce0fdc37..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt +++ /dev/null @@ -1,17 +0,0 @@ -URI.MungeResources -TYPE: bool -VERSION: 3.1.1 -DEFAULT: false ---DESCRIPTION-- -

- If true, any URI munging directives like %URI.Munge - will also apply to embedded resources, such as <img src="">. - Be careful enabling this directive if you have a redirector script - that does not use the Location HTTP header; all of your images - and other embedded resources will break. -

-

- Warning: It is strongly advised you use this in conjunction - %URI.MungeSecretKey to mitigate the security risk of an open redirector. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt deleted file mode 100644 index 0d00f62ea8..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt +++ /dev/null @@ -1,30 +0,0 @@ -URI.MungeSecretKey -TYPE: string/null -VERSION: 3.1.1 -DEFAULT: NULL ---DESCRIPTION-- -

- This directive enables secure checksum generation along with %URI.Munge. - It should be set to a secure key that is not shared with anyone else. - The checksum can be placed in the URI using %t. Use of this checksum - affords an additional level of protection by allowing a redirector - to check if a URI has passed through HTML Purifier with this line: -

- -
$checksum === sha1($secret_key . ':' . $url)
- -

- If the output is TRUE, the redirector script should accept the URI. -

- -

- Please note that it would still be possible for an attacker to procure - secure hashes en-mass by abusing your website's Preview feature or the - like, but this service affords an additional level of protection - that should be combined with website blacklisting. -

- -

- Remember this has no effect if %URI.Munge is not on. -

---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt deleted file mode 100644 index 23331a4e79..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt +++ /dev/null @@ -1,9 +0,0 @@ -URI.OverrideAllowedSchemes -TYPE: bool -DEFAULT: true ---DESCRIPTION-- -If this is set to true (which it is by default), you can override -%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the -registry. If false, you will also have to update that directive in order -to add more schemes. ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/info.ini b/library/HTMLPurifier/ConfigSchema/schema/info.ini deleted file mode 100644 index 5de4505e1b..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/info.ini +++ /dev/null @@ -1,3 +0,0 @@ -name = "HTML Purifier" - -; vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ContentSets.php b/library/HTMLPurifier/ContentSets.php deleted file mode 100644 index 3b6e96f5f5..0000000000 --- a/library/HTMLPurifier/ContentSets.php +++ /dev/null @@ -1,155 +0,0 @@ - true) indexed by name. - * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets - */ - public $lookup = array(); - - /** - * Synchronized list of defined content sets (keys of info) - */ - protected $keys = array(); - /** - * Synchronized list of defined content values (values of info) - */ - 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 - */ - 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 ($module->content_sets as $key => $value) { - $temp = $this->convertToLookup($value); - if (isset($this->lookup[$key])) { - // add it into the existing content set - $this->lookup[$key] = array_merge($this->lookup[$key], $temp); - } else { - $this->lookup[$key] = $temp; - } - } - } - $old_lookup = false; - while ($old_lookup !== $this->lookup) { - $old_lookup = $this->lookup; - foreach ($this->lookup as $i => $set) { - $add = array(); - foreach ($set as $element => $x) { - if (isset($this->lookup[$element])) { - $add += $this->lookup[$element]; - unset($this->lookup[$i][$element]); - } - } - $this->lookup[$i] += $add; - } - } - - foreach ($this->lookup as $key => $lookup) { - $this->info[$key] = implode(' | ', array_keys($lookup)); - } - $this->keys = array_keys($this->info); - $this->values = array_values($this->info); - } - - /** - * Accepts a definition; generates and assigns a ChildDef for it - * @param $def HTMLPurifier_ElementDef reference - * @param $module Module that defined the ElementDef - */ - public function generateChildDef(&$def, $module) { - if (!empty($def->child)) return; // already done! - $content_model = $def->content_model; - if (is_string($content_model)) { - // Assume that $this->keys is alphanumeric - $def->content_model = preg_replace_callback( - '/\b(' . implode('|', $this->keys) . ')\b/', - array($this, 'generateChildDefCallback'), - $content_model - ); - //$def->content_model = str_replace( - // $this->keys, $this->values, $content_model); - } - $def->child = $this->getChildDef($def, $module); - } - - public function generateChildDefCallback($matches) { - return $this->info[$matches[0]]; - } - - /** - * Instantiates a ChildDef based on content_model and content_model_type - * 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 - * @return HTMLPurifier_ChildDef corresponding to ElementDef - */ - public function getChildDef($def, $module) { - $value = $def->content_model; - if (is_object($value)) { - trigger_error( - 'Literal object child definitions should be stored in '. - 'ElementDef->child not ElementDef->content_model', - E_USER_NOTICE - ); - return $value; - } - switch ($def->content_model_type) { - case 'required': - return new HTMLPurifier_ChildDef_Required($value); - case 'optional': - return new HTMLPurifier_ChildDef_Optional($value); - case 'empty': - return new HTMLPurifier_ChildDef_Empty(); - case 'custom': - return new HTMLPurifier_ChildDef_Custom($value); - } - // defer to its module - $return = false; - if ($module->defines_child_def) { // save a func call - $return = $module->getChildDef($def); - } - if ($return !== false) return $return; - // error-out - trigger_error( - 'Could not determine which ChildDef class to instantiate', - E_USER_ERROR - ); - return false; - } - - /** - * Converts a string list of elements separated by pipes into - * a lookup array. - * @param $string List of elements - * @return Lookup array of elements - */ - protected function convertToLookup($string) { - $array = explode('|', str_replace(' ', '', $string)); - $ret = array(); - foreach ($array as $i => $k) { - $ret[$k] = true; - } - return $ret; - } - -} - -// 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 @@ -_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 @@ -setup) return; - $this->setup = true; - $this->doSetup($config); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache.php b/library/HTMLPurifier/DefinitionCache.php deleted file mode 100644 index c6e1e388c6..0000000000 --- a/library/HTMLPurifier/DefinitionCache.php +++ /dev/null @@ -1,108 +0,0 @@ -type = $type; - } - - /** - * Generates a unique identifier for a particular configuration - * @param Instance of HTMLPurifier_Config - */ - public function generateKey($config) { - return $config->version . ',' . // possibly replace with function calls - $config->getBatchSerial($this->type) . ',' . - $config->get($this->type . '.DefinitionRev'); - } - - /** - * 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 - */ - 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; - // versions match, ids match, check revision number - 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 - */ - 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; - } - return true; - } - - /** - * Adds a definition object to the cache - */ - abstract public function add($def, $config); - - /** - * Unconditionally saves a definition object to the cache - */ - abstract public function set($def, $config); - - /** - * Replace an object in the cache - */ - abstract public function replace($def, $config); - - /** - * Retrieves a definition object from the cache - */ - abstract public function get($config); - - /** - * Removes a definition object to the cache - */ - abstract public function remove($config); - - /** - * Clears all objects from cache - */ - abstract public function flush($config); - - /** - * Clears all expired (older version or revision) objects from cache - * @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. - */ - abstract public function cleanup($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 @@ -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 @@ -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 @@ -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/DefinitionCache/Serializer/README b/library/HTMLPurifier/DefinitionCache/Serializer/README deleted file mode 100644 index 2e35c1c3d0..0000000000 --- a/library/HTMLPurifier/DefinitionCache/Serializer/README +++ /dev/null @@ -1,3 +0,0 @@ -This is a dummy file to prevent Git from ignoring this empty directory. - - vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCacheFactory.php b/library/HTMLPurifier/DefinitionCacheFactory.php deleted file mode 100644 index a6ead62818..0000000000 --- a/library/HTMLPurifier/DefinitionCacheFactory.php +++ /dev/null @@ -1,91 +0,0 @@ - array()); - protected $implementations = array(); - protected $decorators = array(); - - /** - * Initialize default decorators - */ - public function setup() { - $this->addDecorator('Cleanup'); - } - - /** - * Retrieves an instance of global definition cache factory. - */ - public static function instance($prototype = null) { - static $instance; - if ($prototype !== null) { - $instance = $prototype; - } elseif ($instance === null || $prototype === true) { - $instance = new HTMLPurifier_DefinitionCacheFactory(); - $instance->setup(); - } - return $instance; - } - - /** - * 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 - */ - 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 - */ - public function create($type, $config) { - $method = $config->get('Cache.DefinitionImpl'); - if ($method === null) { - return new HTMLPurifier_DefinitionCache_Null($type); - } - if (!empty($this->caches[$method][$type])) { - return $this->caches[$method][$type]; - } - if ( - isset($this->implementations[$method]) && - class_exists($class = $this->implementations[$method], false) - ) { - $cache = new $class($type); - } else { - if ($method != 'Serializer') { - trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING); - } - $cache = new HTMLPurifier_DefinitionCache_Serializer($type); - } - foreach ($this->decorators as $decorator) { - $new_cache = $decorator->decorate($cache); - // prevent infinite recursion in PHP 4 - unset($cache); - $cache = $new_cache; - } - $this->caches[$method][$type] = $cache; - return $this->caches[$method][$type]; - } - - /** - * Registers a decorator to add to all new cache objects - * @param - */ - 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/HTMLPurifier/Doctype.php deleted file mode 100644 index 1e3c574c06..0000000000 --- a/library/HTMLPurifier/Doctype.php +++ /dev/null @@ -1,60 +0,0 @@ -renderDoctype. - * If structure changes, please update that function. - */ -class HTMLPurifier_Doctype -{ - /** - * Full name of doctype - */ - public $name; - - /** - * List of standard modules (string identifiers or literal objects) - * that this doctype uses - */ - public $modules = array(); - - /** - * List of modules to use for tidying up code - */ - public $tidyModules = array(); - - /** - * Is the language derived from XML (i.e. XHTML)? - */ - public $xml = true; - - /** - * List of aliases for this doctype - */ - public $aliases = array(); - - /** - * Public DTD identifier - */ - public $dtdPublic; - - /** - * System DTD identifier - */ - public $dtdSystem; - - 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; - $this->modules = $modules; - $this->tidyModules = $tidyModules; - $this->aliases = $aliases; - $this->dtdPublic = $dtd_public; - $this->dtdSystem = $dtd_system; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DoctypeRegistry.php b/library/HTMLPurifier/DoctypeRegistry.php deleted file mode 100644 index 86049e9391..0000000000 --- a/library/HTMLPurifier/DoctypeRegistry.php +++ /dev/null @@ -1,103 +0,0 @@ -doctypes[$doctype->name] = $doctype; - $name = $doctype->name; - // hookup aliases - foreach ($doctype->aliases as $alias) { - if (isset($this->doctypes[$alias])) continue; - $this->aliases[$alias] = $name; - } - // remove old aliases - if (isset($this->aliases[$name])) unset($this->aliases[$name]); - return $doctype; - } - - /** - * 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 - */ - 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); - return $anon; - } - return $this->doctypes[$doctype]; - } - - /** - * Creates a doctype based on a configuration object, - * will perform initialization on the doctype - * @note Use this function to get a copy of doctype that config - * can hold on to (this is necessary in order to tell - * Generator whether or not the current document is XML - * based or not). - */ - public function make($config) { - return clone $this->get($this->getDoctypeFromConfig($config)); - } - - /** - * Retrieves the doctype from the configuration object - */ - public function getDoctypeFromConfig($config) { - // recommended test - $doctype = $config->get('HTML.Doctype'); - if (!empty($doctype)) return $doctype; - $doctype = $config->get('HTML.CustomDoctype'); - if (!empty($doctype)) return $doctype; - // backwards-compatibility - if ($config->get('HTML.XHTML')) { - $doctype = 'XHTML 1.0'; - } else { - $doctype = 'HTML 4.01'; - } - if ($config->get('HTML.Strict')) { - $doctype .= ' Strict'; - } else { - $doctype .= ' Transitional'; - } - return $doctype; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ElementDef.php b/library/HTMLPurifier/ElementDef.php deleted file mode 100644 index 5498d95670..0000000000 --- a/library/HTMLPurifier/ElementDef.php +++ /dev/null @@ -1,183 +0,0 @@ -setup(), this array may also - * contain an array at index 0 that indicates which attribute - * collections to load into the full array. It may also - * contain string indentifiers in lieu of HTMLPurifier_AttrDef, - * see HTMLPurifier_AttrTypes on how they are expanded during - * HTMLPurifier_HTMLDefinition->setup() processing. - */ - public $attr = array(); - - /** - * Indexed list of tag's HTMLPurifier_AttrTransform to be done before validation - */ - public $attr_transform_pre = array(); - - /** - * Indexed list of tag's HTMLPurifier_AttrTransform to be done after validation - */ - public $attr_transform_post = array(); - - /** - * HTMLPurifier_ChildDef of this tag. - */ - public $child; - - /** - * 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 - */ - public $content_model; - - /** - * Value of $child->type, used to determine which ChildDef to use, - * used in combination with $content_model. - * @warning This must be lowercase - * @warning This is a temporary variable that is not available after - * being processed by HTMLDefinition - */ - 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. - */ - public $descendants_are_inline = false; - - /** - * List of the names of required attributes this element has. Dynamically - * populated by HTMLPurifier_HTMLDefinition::getElement - */ - public $required_attr = array(); - - /** - * Lookup table of tags excluded from all descendants of this tag. - * @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 - * exclusion for children, but we go the simpler, SGML-style - * route of flat-out exclusions, which correctly apply to - * all descendants and not just children. Note that the XHTML - * Modularization Abstract Modules are blithely unaware of such - * distinctions. - */ - public $excludes = array(); - - /** - * This tag is explicitly auto-closed by the following tags. - */ - public $autoclose = array(); - - /** - * 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. - */ - public $wrap; - - /** - * Whether or not this is a formatting element affected by the - * "Active Formatting Elements" algorithm. - */ - public $formatting; - - /** - * Low-level factory constructor for creating new standalone element defs - */ - 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; - $def->attr = $attr; - return $def; - } - - /** - * Merges the values of another element definition into this one. - * Values from the new element def take precedence if a value is - * not mergeable. - */ - public function mergeIn($def) { - - // later keys takes precedence - foreach($def->attr as $k => $v) { - if ($k === 0) { - // merge in the includes - // sorry, no way to override an include - foreach ($v as $v2) { - $this->attr[0][] = $v2; - } - continue; - } - if ($v === false) { - 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); - - 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)) { - $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; - - } - - /** - * Merges one array into another, removes values which equal false - * @param $a1 Array by reference that is merged into - * @param $a2 Array that merges into $a1 - */ - private function _mergeAssocArray(&$a1, $a2) { - foreach ($a2 as $k => $v) { - if ($v === false) { - 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/HTMLPurifier/Encoder.php deleted file mode 100644 index 2b3140caaf..0000000000 --- a/library/HTMLPurifier/Encoder.php +++ /dev/null @@ -1,426 +0,0 @@ - under the - * LGPL license. Notes on what changed are inside, but in general, - * the original code transformed UTF-8 text into an array of integer - * Unicode codepoints. Understandably, transforming that back to - * a string would be somewhat expensive, so the function was modded to - * directly operate on the string. However, this discourages code - * reuse, and the logic enumerated here would be useful for any - * function that needs to be able to understand UTF-8 characters. - * As of right now, only smart lossless character encoding converters - * 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) { - - // 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)) { - return $str; - } - - $mState = 0; // cached expected number of octets after the current octet - // until the beginning of the next UTF8 character sequence - $mUcs4 = 0; // cached Unicode character - $mBytes = 1; // cached expected number of octets in the current sequence - - // original code involved an $out that was an array of Unicode - // codepoints. Instead of having to convert back into UTF-8, we've - // decided to directly append valid UTF-8 characters onto a string - // $out once they're done. $char accumulates raw bytes, while $mUcs4 - // turns into the Unicode code point, so there's some redundancy. - - $out = ''; - $char = ''; - - $len = strlen($str); - for($i = 0; $i < $len; $i++) { - $in = ord($str{$i}); - $char .= $str[$i]; // append byte to char - if (0 == $mState) { - // When mState is zero we expect either a US-ASCII character - // or a multi-octet sequence. - if (0 == (0x80 & ($in))) { - // US-ASCII, pass straight through. - if (($in <= 31 || $in == 127) && - !($in == 9 || $in == 13 || $in == 10) // save \r\t\n - ) { - // control characters, remove - } else { - $out .= $char; - } - // reset - $char = ''; - $mBytes = 1; - } elseif (0xC0 == (0xE0 & ($in))) { - // First octet of 2 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x1F) << 6; - $mState = 1; - $mBytes = 2; - } elseif (0xE0 == (0xF0 & ($in))) { - // First octet of 3 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x0F) << 12; - $mState = 2; - $mBytes = 3; - } elseif (0xF0 == (0xF8 & ($in))) { - // First octet of 4 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x07) << 18; - $mState = 3; - $mBytes = 4; - } elseif (0xF8 == (0xFC & ($in))) { - // First octet of 5 octet sequence. - // - // This is illegal because the encoded codepoint must be - // either: - // (a) not the shortest form or - // (b) outside the Unicode range of 0-0x10FFFF. - // Rather than trying to resynchronize, we will carry on - // until the end of the sequence and let the later error - // handling code catch it. - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x03) << 24; - $mState = 4; - $mBytes = 5; - } elseif (0xFC == (0xFE & ($in))) { - // First octet of 6 octet sequence, see comments for 5 - // octet sequence. - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 1) << 30; - $mState = 5; - $mBytes = 6; - } else { - // Current octet is neither in the US-ASCII range nor a - // legal first octet of a multi-octet sequence. - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - $char = ''; - } - } else { - // When mState is non-zero, we expect a continuation of the - // multi-octet sequence - if (0x80 == (0xC0 & ($in))) { - // Legal continuation. - $shift = ($mState - 1) * 6; - $tmp = $in; - $tmp = ($tmp & 0x0000003F) << $shift; - $mUcs4 |= $tmp; - - if (0 == --$mState) { - // End of the multi-octet sequence. mUcs4 now contains - // the final Unicode codepoint to be output - - // Check for illegal sequences and codepoints. - - // From Unicode 3.1, non-shortest form is illegal - if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || - ((3 == $mBytes) && ($mUcs4 < 0x0800)) || - ((4 == $mBytes) && ($mUcs4 < 0x10000)) || - (4 < $mBytes) || - // From Unicode 3.2, surrogate characters = illegal - (($mUcs4 & 0xFFFFF800) == 0xD800) || - // Codepoints outside the Unicode range are illegal - ($mUcs4 > 0x10FFFF) - ) { - - } elseif (0xFEFF != $mUcs4 && // omit BOM - // check for valid Char unicode codepoints - ( - 0x9 == $mUcs4 || - 0xA == $mUcs4 || - 0xD == $mUcs4 || - (0x20 <= $mUcs4 && 0x7E >= $mUcs4) || - // 7F-9F is not strictly prohibited by XML, - // but it is non-SGML, and thus we don't allow it - (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || - (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) - ) - ) { - $out .= $char; - } - // initialize UTF8 cache (reset) - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - $char = ''; - } - } else { - // ((0xC0 & (*in) != 0x80) && (mState != 0)) - // Incomplete multi-octet sequence. - // used to result in complete fail, but we'll reset - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - $char =''; - } - } - } - return $out; - } - - /** - * Translates a Unicode codepoint into its corresponding UTF-8 character. - * @note Based on Feyd's function at - * , - * which is in public domain. - * @note While we're going to do code point parsing anyway, a good - * optimization would be to refuse to translate code points that - * are non-SGML characters. However, this could lead to duplication. - * @note This is very similar to the unichr function in - * maintenance/generate-entity-file.php (although this is superior, - * due to its sanity checks). - */ - - // +----------+----------+----------+----------+ - // | 33222222 | 22221111 | 111111 | | - // | 10987654 | 32109876 | 54321098 | 76543210 | bit - // +----------+----------+----------+----------+ - // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F - // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF - // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF - // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF - // +----------+----------+----------+----------+ - // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF) - // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes - // +----------+----------+----------+----------+ - - 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 - return ''; - } - - $x = $y = $z = $w = 0; - if ($code < 128) { - // regular ASCII character - $x = $code; - } else { - // set up bits for UTF-8 - $x = ($code & 63) | 128; - if ($code < 2048) { - $y = (($code & 2047) >> 6) | 192; - } else { - $y = (($code & 4032) >> 6) | 128; - if($code < 65536) { - $z = (($code >> 12) & 15) | 224; - } else { - $z = (($code >> 12) & 63) | 128; - $w = (($code >> 18) & 7) | 240; - } - } - } - // set up the actual character - $ret = ''; - 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. - */ - 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 = function_exists('iconv'); - set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); - if ($iconv && !$config->get('Test.ForceNoIconv')) { - $str = iconv($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(); - 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); - } - - /** - * Converts a string from UTF-8 based on configuration. - * @note Currently, this is a lossy conversion, with unexpressable - * characters being omitted. - */ - 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); - } - 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); - if (!$escape && !empty($ascii_fix)) { - $clear_fix = array(); - 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(); - return $str; - } elseif ($encoding === 'iso-8859-1') { - $str = utf8_decode($str); - restore_error_handler(); - return $str; - } - trigger_error('Encoding not supported', E_USER_ERROR); - } - - /** - * 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 - * @warning Adapted from MediaWiki, claiming fair use: this is a common - * algorithm. If you disagree with this license fudgery, - * implement it yourself. - * @note Uses decimal numeric entities since they are best supported. - * @note This is a DUMB function: it has no concept of keeping - * character entities that the projected character encoding - * can allow. We could possibly implement a smart version - * but that would require it to also know which Unicode - * codepoints the charset supported (not an easy task). - * @note Sort of with cleanUTF8() but it assumes that $str is - * well-formed UTF-8 - */ - 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 ); - $bytesleft = 0; - } elseif( $bytevalue <= 0xBF ) { //10xx xxxx - $working = $working << 6; - $working += ($bytevalue & 0x3F); - $bytesleft--; - if( $bytesleft <= 0 ) { - $result .= "&#" . $working . ";"; - } - } elseif( $bytevalue <= 0xDF ) { //110x xxxx - $working = $bytevalue & 0x1F; - $bytesleft = 1; - } elseif( $bytevalue <= 0xEF ) { //1110 xxxx - $working = $bytevalue & 0x0F; - $bytesleft = 2; - } else { //1111 0xxx - $working = $bytevalue & 0x07; - $bytesleft = 3; - } - } - return $result; - } - - /** - * This expensive function tests whether or not a given character - * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will - * fail this test, and require special processing. Variable width - * encodings shouldn't ever fail. - * - * @param string $encoding Encoding name to test, as per iconv format - * @param bool $bypass Whether or not to bypass the precompiled arrays. - * @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) { - static $encodings = array(); - if (!$bypass) { - if (isset($encodings[$encoding])) return $encodings[$encoding]; - $lenc = strtolower($encoding); - switch ($lenc) { - case 'shift_jis': - return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~'); - case 'johab': - return array("\xE2\x82\xA9" => '\\'); - } - 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; - 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 === '' || - // 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) - ) { - // 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; - } - } - restore_error_handler(); - $encodings[$encoding] = $ret; - return $ret; - } - - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/EntityLookup.php b/library/HTMLPurifier/EntityLookup.php deleted file mode 100644 index b4dfce94c3..0000000000 --- a/library/HTMLPurifier/EntityLookup.php +++ /dev/null @@ -1,44 +0,0 @@ -table = unserialize(file_get_contents($file)); - } - - /** - * Retrieves sole instance of the object. - * @param Optional prototype of custom lookup table to overload with. - */ - public static function instance($prototype = false) { - // no references, since PHP doesn't copy unless modified - static $instance = null; - if ($prototype) { - $instance = $prototype; - } elseif (!$instance) { - $instance = new HTMLPurifier_EntityLookup(); - $instance->setup(); - } - return $instance; - } - -} - -// 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/EntityParser.php b/library/HTMLPurifier/EntityParser.php deleted file mode 100644 index 8c384472dc..0000000000 --- a/library/HTMLPurifier/EntityParser.php +++ /dev/null @@ -1,144 +0,0 @@ - '"', - 38 => '&', - 39 => "'", - 60 => '<', - 62 => '>' - ); - - /** - * Stripped entity names to decimal conversion table for special entities. - */ - protected $_special_ent2dec = - array( - 'quot' => 34, - 'amp' => 38, - 'lt' => 60, - 'gt' => 62 - ); - - /** - * Substitutes non-special entities with their parsed equivalents. Since - * 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. - */ - 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 - * either index 1, 2 or 3 set with a hex value, dec value, - * or string (respectively). - * @returns Replacement string. - */ - - 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; - - return HTMLPurifier_Encoder::unichr($code); - } else { - if (isset($this->_special_ent2dec[$matches[3]])) return $entity; - if (!$this->_entity_lookup) { - $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); - } - if (isset($this->_entity_lookup->table[$matches[3]])) { - return $this->_entity_lookup->table[$matches[3]]; - } else { - return $entity; - } - } - } - - /** - * Substitutes only special entities with their parsed equivalents. - * - * @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. - */ - public function substituteSpecialEntities($string) { - return preg_replace_callback( - $this->_substituteEntitiesRegex, - array($this, 'specialEntityCallback'), - $string); - } - - /** - * Callback function for substituteSpecialEntities() that does the work. - * - * This callback has same syntax as nonSpecialEntityCallback(). - * - * @param $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. - */ - protected function specialEntityCallback($matches) { - $entity = $matches[0]; - $is_num = (@$matches[0][1] === '#'); - if ($is_num) { - $is_hex = (@$entity[2] === 'x'); - $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; - return isset($this->_special_dec2str[$int]) ? - $this->_special_dec2str[$int] : - $entity; - } else { - return isset($this->_special_ent2dec[$matches[3]]) ? - $this->_special_ent2dec[$matches[3]] : - $entity; - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ErrorCollector.php b/library/HTMLPurifier/ErrorCollector.php deleted file mode 100644 index 6713eaf773..0000000000 --- a/library/HTMLPurifier/ErrorCollector.php +++ /dev/null @@ -1,209 +0,0 @@ -locale =& $context->get('Locale'); - $this->context = $context; - $this->_current =& $this->_stacks[0]; - $this->errors =& $this->_stacks[0]; - } - - /** - * 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 ... - */ - public function send($severity, $msg) { - - $args = array(); - if (func_num_args() > 2) { - $args = func_get_args(); - array_shift($args); - unset($args[0]); - } - - $token = $this->context->get('CurrentToken', true); - $line = $token ? $token->line : $this->context->get('CurrentLine', true); - $col = $token ? $token->col : $this->context->get('CurrentCol', true); - $attr = $this->context->get('CurrentAttr', true); - - // perform special substitutions, also add custom parameters - $subst = array(); - if (!is_null($token)) { - $args['CurrentToken'] = $token; - } - if (!is_null($attr)) { - $subst['$CurrentAttr.Name'] = $attr; - if (isset($token->attr[$attr])) $subst['$CurrentAttr.Value'] = $token->attr[$attr]; - } - - if (empty($args)) { - $msg = $this->locale->getMessage($msg); - } else { - $msg = $this->locale->formatMessage($msg, $args); - } - - if (!empty($subst)) $msg = strtr($msg, $subst); - - // (numerically indexed) - $error = array( - self::LINENO => $line, - self::SEVERITY => $severity, - self::MESSAGE => $msg, - self::CHILDREN => array() - ); - $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 (is_int($line) && is_int($col)) { - if (isset($this->lines[$line][$col])) { - $struct = $this->lines[$line][$col]; - } else { - $struct = $this->lines[$line][$col] = $new_struct; - } - // These ksorts may present a performance problem - ksort($this->lines[$line], SORT_NUMERIC); - } else { - if (isset($this->lines[-1])) { - $struct = $this->lines[-1]; - } else { - $struct = $this->lines[-1] = $new_struct; - } - } - ksort($this->lines, SORT_NUMERIC); - - // Now, check if we need to operate on a lower structure - if (!empty($attr)) { - $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr); - if (!$struct->value) { - $struct->value = array($attr, 'PUT VALUE HERE'); - } - } - if (!empty($cssprop)) { - $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop); - if (!$struct->value) { - // if we tokenize CSS this might be a little more difficult to do - $struct->value = array($cssprop, 'PUT VALUE HERE'); - } - } - - // Ok, structs are all setup, now time to register the error - $struct->addError($severity, $msg); - } - - /** - * 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() { - 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. - */ - public function getHTMLFormatted($config, $errors = null) { - $ret = array(); - - $this->generator = new HTMLPurifier_Generator($config, $this->context); - 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; - foreach ($col_array as $col => $struct) { - $this->_renderStruct($ret, $struct, $line, $col); - } - } - if (isset($this->lines[-1])) { - $this->_renderStruct($ret, $this->lines[-1]); - } - - if (empty($errors)) { - return '

' . $this->locale->getMessage('ErrorCollector: No errors') . '

'; - } else { - return '
  • ' . implode('
  • ', $ret) . '
'; - } - - } - - private function _renderStruct(&$ret, $struct, $line = null, $col = null) { - $stack = array($struct); - $context_stack = array(array()); - while ($current = array_pop($stack)) { - $context = array_pop($context_stack); - foreach ($current->errors as $error) { - list($severity, $msg) = $error; - $string = ''; - $string .= '
'; - // W3C uses an icon to indicate the severity of the error. - $error = $this->locale->getErrorName($severity); - $string .= "$error "; - if (!is_null($line) && !is_null($col)) { - $string .= "Line $line, Column $col: "; - } else { - $string .= 'End of Document: '; - } - $string .= '' . $this->generator->escape($msg) . ' '; - $string .= '
'; - // Here, have a marker for the character on the column appropriate. - // Be sure to clip extremely long lines. - //$string .= '
';
-                //$string .= '';
-                //$string .= '
'; - $ret[] = $string; - } - foreach ($current->children as $type => $array) { - $context[] = $current; - $stack = array_merge($stack, array_reverse($array, true)); - for ($i = count($array); $i > 0; $i--) { - $context_stack[] = $context; - } - } - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ErrorStruct.php b/library/HTMLPurifier/ErrorStruct.php deleted file mode 100644 index 9bc8996ec1..0000000000 --- a/library/HTMLPurifier/ErrorStruct.php +++ /dev/null @@ -1,60 +0,0 @@ -children[$type][$id])) { - $this->children[$type][$id] = new HTMLPurifier_ErrorStruct(); - $this->children[$type][$id]->type = $type; - } - return $this->children[$type][$id]; - } - - 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/HTMLPurifier/Exception.php deleted file mode 100644 index be85b4c560..0000000000 --- a/library/HTMLPurifier/Exception.php +++ /dev/null @@ -1,12 +0,0 @@ -preFilter, - * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter, - * 1->postFilter. - * - * @note Methods are not declared abstract as it is perfectly legitimate - * for an implementation not to want anything to happen on a step - */ - -class HTMLPurifier_Filter -{ - - /** - * Name of the filter for identification purposes - */ - public $name; - - /** - * Pre-processor function, handles HTML before HTML Purifier - */ - public function preFilter($html, $config, $context) { - return $html; - } - - /** - * Post-processor function, handles HTML after HTML Purifier - */ - public function postFilter($html, $config, $context) { - return $html; - } - -} - -// vim: et sw=4 sts=4 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 @@ - 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 #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 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 @@ -]+>.+?'. - 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s'; - $pre_replace = '\1'; - return preg_replace($pre_regex, $pre_replace, $html); - } - - public function postFilter($html, $config, $context) { - $post_regex = '#((?:v|cp)/[A-Za-z0-9\-_=]+)#'; - 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 ''. - ''. - ''. - ''; - - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Generator.php b/library/HTMLPurifier/Generator.php deleted file mode 100644 index 4a62417271..0000000000 --- a/library/HTMLPurifier/Generator.php +++ /dev/null @@ -1,224 +0,0 @@ - tags - */ - private $_scriptFix = false; - - /** - * Cache of HTMLDefinition during HTML output to determine whether or - * not attributes should be minimized. - */ - private $_def; - - /** - * Cache of %Output.SortAttr - */ - private $_sortAttr; - - /** - * Cache of %Output.FlashCompat - */ - private $_flashCompat; - - /** - * Stack for keeping track of object information when outputting IE - * compatibility code. - */ - private $_flashStack = array(); - - /** - * Configuration for the generator - */ - protected $config; - - /** - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - */ - public function __construct($config, $context) { - $this->config = $config; - $this->_scriptFix = $config->get('Output.CommentScriptContents'); - $this->_sortAttr = $config->get('Output.SortAttr'); - $this->_flashCompat = $config->get('Output.FlashCompat'); - $this->_def = $config->getHTMLDefinition(); - $this->_xhtml = $this->_def->doctype->xml; - } - - /** - * Generates HTML from an array of tokens. - * @param $tokens Array of HTMLPurifier_Token - * @param $config HTMLPurifier_Config object - * @return Generated HTML - */ - public function generateFromTokens($tokens) { - if (!$tokens) return ''; - - // Basic algorithm - $html = ''; - for ($i = 0, $size = count($tokens); $i < $size; $i++) { - if ($this->_scriptFix && $tokens[$i]->name === 'script' - && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) { - // script special case - // the contents of the script block must be ONE token - // for this to work. - $html .= $this->generateFromToken($tokens[$i++]); - $html .= $this->generateScriptFromToken($tokens[$i++]); - } - $html .= $this->generateFromToken($tokens[$i]); - } - - // 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->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); - return $html; - } - - /** - * Generates HTML from a single token. - * @param $token HTMLPurifier_Token object. - * @return Generated HTML - */ - public function generateFromToken($token) { - if (!$token instanceof HTMLPurifier_Token) { - trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING); - return ''; - - } elseif ($token instanceof HTMLPurifier_Token_Start) { - $attr = $this->generateAttributes($token->attr, $token->name); - if ($this->_flashCompat) { - if ($token->name == "object") { - $flash = new stdclass(); - $flash->attr = $token->attr; - $flash->param = array(); - $this->_flashStack[] = $flash; - } - } - return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>'; - - } elseif ($token instanceof HTMLPurifier_Token_End) { - $_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 = ""; - } - } - return $_extra . 'name . '>'; - - } elseif ($token instanceof HTMLPurifier_Token_Empty) { - if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) { - $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value']; - } - $attr = $this->generateAttributes($token->attr, $token->name); - return '<' . $token->name . ($attr ? ' ' : '') . $attr . - ( $this->_xhtml ? ' /': '' ) //
v.
- . '>'; - - } elseif ($token instanceof HTMLPurifier_Token_Text) { - return $this->escape($token->data, ENT_NOQUOTES); - - } elseif ($token instanceof HTMLPurifier_Token_Comment) { - return ''; - } else { - return ''; - - } - } - - /** - * Special case processor for the contents of script tags - * @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); - // Thanks - $data = preg_replace('#//\s*$#', '', $token->data); - return ''; - } - - /** - * 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 - * attribute minimization. - * @return Generate HTML fragment for insertion. - */ - public function generateAttributes($assoc_array_of_attributes, $element = false) { - $html = ''; - 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; - // Check if we should minimize the attribute: val="val" -> val - if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) { - $html .= $key . ' '; - continue; - } - } - $html .= $key.'="'.$this->escape($value).'" '; - } - return rtrim($html); - } - - /** - * Escapes raw text data. - * @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 - * permissible for non-attribute output. - * @return String escaped data. - */ - public function escape($string, $quote = ENT_COMPAT) { - return htmlspecialchars($string, $quote, 'UTF-8'); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLDefinition.php b/library/HTMLPurifier/HTMLDefinition.php deleted file mode 100644 index c99ac11eb2..0000000000 --- a/library/HTMLPurifier/HTMLDefinition.php +++ /dev/null @@ -1,420 +0,0 @@ -getAnonymousModule(); - if (!isset($module->info[$element_name])) { - $element = $module->addBlankElement($element_name); - } else { - $element = $module->info[$element_name]; - } - $element->attr[$attr_name] = $def; - } - - /** - * Adds a custom element to your HTML definition - * @note See HTMLPurifier_HTMLModule::addElement for detailed - * parameter and return value descriptions. - */ - 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 - $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes); - return $element; - } - - /** - * Adds a blank element to your HTML definition, for overriding - * existing behavior - * @note See HTMLPurifier_HTMLModule::addBlankElement for detailed - * parameter and return value descriptions. - */ - public function addBlankElement($element_name) { - $module = $this->getAnonymousModule(); - $element = $module->addBlankElement($element_name); - return $element; - } - - /** - * Retrieves a reference to the anonymous module, so you can - * bust out advanced features without having to make your own - * module. - */ - public function getAnonymousModule() { - if (!$this->_anonModule) { - $this->_anonModule = new HTMLPurifier_HTMLModule(); - $this->_anonModule->name = 'Anonymous'; - } - return $this->_anonModule; - } - - private $_anonModule; - - - // PUBLIC BUT INTERNAL VARIABLES -------------------------------------- - - public $type = 'HTML'; - public $manager; /**< Instance of HTMLPurifier_HTMLModuleManager */ - - /** - * Performs low-cost, preliminary initialization. - */ - public function __construct() { - $this->manager = new HTMLPurifier_HTMLModuleManager(); - } - - protected function doSetup($config) { - $this->processModules($config); - $this->setupConfigStuff($config); - unset($this->manager); - - // cleanup some of the element definitions - foreach ($this->info as $k => $v) { - unset($this->info[$k]->content_model); - unset($this->info[$k]->content_model_type); - } - } - - /** - * Extract out the information from the manager - */ - protected function processModules($config) { - - if ($this->_anonModule) { - // for user specific changes - // this is late-loaded so we don't have to deal with PHP4 - // reference wonky-ness - $this->manager->addModule($this->_anonModule); - unset($this->_anonModule); - } - - $this->manager->setup($config); - $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_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_injector as $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. - */ - 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); - } - - $parent = $config->get('HTML.Parent'); - $def = $this->manager->getElement($parent, true); - if ($def) { - $this->info_parent = $parent; - $this->info_parent_def = $def; - } else { - 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) "; - - // setup allowed elements ----------------------------------------- - - $allowed_elements = $config->get('HTML.AllowedElements'); - $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early - - if (!is_array($allowed_elements) && !is_array($allowed_attributes)) { - $allowed = $config->get('HTML.Allowed'); - if (is_string($allowed)) { - list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed); - } - } - - if (is_array($allowed_elements)) { - foreach ($this->info as $name => $d) { - if(!isset($allowed_elements[$name])) unset($this->info[$name]); - unset($allowed_elements[$name]); - } - // emit errors - foreach ($allowed_elements as $element => $d) { - $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful! - trigger_error("Element '$element' is not supported $support", E_USER_WARNING); - } - } - - // setup allowed attributes --------------------------------------- - - $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! - foreach ($this->info_global_attr as $attr => $x) { - $keys = array($attr, "*@$attr", "*.$attr"); - $delete = true; - foreach ($keys as $key) { - if ($delete && isset($allowed_attributes[$key])) { - $delete = false; - } - if (isset($allowed_attributes_mutable[$key])) { - unset($allowed_attributes_mutable[$key]); - } - } - if ($delete) unset($this->info_global_attr[$attr]); - } - - foreach ($this->info as $tag => $info) { - foreach ($info->attr as $attr => $x) { - $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr"); - $delete = true; - foreach ($keys as $key) { - if ($delete && isset($allowed_attributes[$key])) { - $delete = false; - } - if (isset($allowed_attributes_mutable[$key])) { - unset($allowed_attributes_mutable[$key]); - } - } - if ($delete) unset($this->info[$tag]->attr[$attr]); - } - } - // emit errors - foreach ($allowed_attributes_mutable as $elattr => $d) { - $bits = preg_split('/[.@]/', $elattr, 2); - $c = count($bits); - switch ($c) { - case 2: - if ($bits[0] !== '*') { - $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"); - } else { - 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 ". - "supported in any elements $support", - E_USER_WARNING); - break; - } - } - - } - - // setup forbidden elements --------------------------------------- - - $forbidden_elements = $config->get('HTML.ForbiddenElements'); - $forbidden_attributes = $config->get('HTML.ForbiddenAttributes'); - - foreach ($this->info as $tag => $info) { - if (isset($forbidden_elements[$tag])) { - unset($this->info[$tag]); - continue; - } - foreach ($info->attr as $attr => $x) { - 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"])) { - // $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); - } - } - } - foreach ($forbidden_attributes as $key => $v) { - 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); - } - } - - // setup injectors ----------------------------------------------------- - foreach ($this->info_injector as $i => $injector) { - if ($injector->checkNeeded($config) !== false) { - // remove injector that does not have it's required - // elements/attributes present, and is thus not needed. - unset($this->info_injector[$i]); - } - } - } - - /** - * Parses a TinyMCE-flavored Allowed Elements and Attributes list into - * 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) - * @todo Give this its own class, probably static interface - */ - public function parseTinyMCEAllowedList($list) { - - $list = str_replace(array(' ', "\t"), '', $list); - - $elements = array(); - $attributes = array(); - - $chunks = preg_split('/(,|[\n\r]+)/', $list); - foreach ($chunks as $chunk) { - if (empty($chunk)) continue; - // remove TinyMCE element control characters - if (!strpos($chunk, '[')) { - $element = $chunk; - $attr = false; - } else { - list($element, $attr) = explode('[', $chunk); - } - 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/HTMLPurifier/HTMLModule.php deleted file mode 100644 index 072cf68084..0000000000 --- a/library/HTMLPurifier/HTMLModule.php +++ /dev/null @@ -1,244 +0,0 @@ -info, since the object's data is only info, - * with extra behavior associated with it. - */ - public $attr_collections = array(); - - /** - * Associative array of deprecated tag name to HTMLPurifier_TagTransform - */ - public $info_tag_transform = array(); - - /** - * List of HTMLPurifier_AttrTransform to be performed before validation. - */ - public $info_attr_transform_pre = array(); - - /** - * List of HTMLPurifier_AttrTransform to be performed after validation. - */ - public $info_attr_transform_post = array(); - - /** - * List of HTMLPurifier_Injector to be performed during well-formedness fixing. - * 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. - */ - public $info_injector = array(); - - /** - * Boolean flag that indicates whether or not getChildDef is implemented. - * 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! - */ - public $defines_child_def = false; - - /** - * Boolean flag whether or not this module is safe. If it is not safe, all - * of its members are unsafe. Modules are safe by default (this might be - * slightly dangerous, but it doesn't make much sense to force HTML Purifier, - * which is based off of safe HTML, to explicitly say, "This is safe," even - * though there are modules which are "unsafe") - * - * @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 - * must be used. - */ - public $safe = true; - - /** - * Retrieves a proper HTMLPurifier_ChildDef subclass based on - * 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 - * @return HTMLPurifier_ChildDef subclass - */ - 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? - * Set as false to skip this step. - * @param $contents Allowed children in form of: - * "$content_model_type: $content_model" - * @param $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 - * can set advanced parameters - */ - 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); - // create element - $this->info[$element] = HTMLPurifier_ElementDef::create( - $content_model, $content_model_type, $attr - ); - // literal object $contents means direct child manipulation - 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 - */ - public function addBlankElement($element) { - if (!isset($this->info[$element])) { - $this->elements[] = $element; - $this->info[$element] = new HTMLPurifier_ElementDef(); - $this->info[$element]->standalone = false; - } else { - trigger_error("Definition for $element already exists in module, cannot redefine"); - } - return $this->info[$element]; - } - - /** - * Convenience function that registers an element to a content set - * @param Element to register - * @param 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] .= ' | '; - $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: - * "$content_model_type: $content_model" - * @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 - switch ($contents) { - // check for shorthand content model forms - case 'Empty': - return array('empty', ''); - case 'Inline': - return array('optional', 'Inline | #PCDATA'); - case 'Flow': - return array('optional', 'Flow | #PCDATA'); - } - list($content_model_type, $content_model) = explode(':', $contents); - $content_model_type = strtolower(trim($content_model_type)); - $content_model = trim($content_model); - return array($content_model_type, $content_model); - } - - /** - * 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 - */ - public function mergeInAttrIncludes(&$attr, $attr_includes) { - if (!is_array($attr_includes)) { - if (empty($attr_includes)) $attr_includes = array(); - else $attr_includes = array($attr_includes); - } - $attr[0] = $attr_includes; - } - - /** - * Convenience function that generates a lookup table with boolean - * true as value. - * @param $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 - */ - public function makeLookup($list) { - if (is_string($list)) $list = func_get_args(); - $ret = array(); - foreach ($list as $value) { - if (is_null($value)) continue; - $ret[$value] = true; - } - return $ret; - } - - /** - * 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 - */ - public function setup($config) {} - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Bdo.php b/library/HTMLPurifier/HTMLModule/Bdo.php deleted file mode 100644 index 3d66f1b4e1..0000000000 --- a/library/HTMLPurifier/HTMLModule/Bdo.php +++ /dev/null @@ -1,31 +0,0 @@ - array('dir' => false) - ); - - public function setup($config) { - $bdo = $this->addElement( - '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(); - - $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl'; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/library/HTMLPurifier/HTMLModule/CommonAttributes.php deleted file mode 100644 index 7c15da84fc..0000000000 --- a/library/HTMLPurifier/HTMLModule/CommonAttributes.php +++ /dev/null @@ -1,26 +0,0 @@ - array( - 0 => array('Style'), - // 'xml:space' => false, - 'class' => 'Class', - 'id' => 'ID', - 'title' => 'CDATA', - ), - 'Lang' => array(), - 'I18N' => array( - 0 => array('Lang'), // proprietary, for xml:lang/lang - ), - 'Common' => array( - 0 => array('Core', 'I18N') - ) - ); - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Edit.php b/library/HTMLPurifier/HTMLModule/Edit.php deleted file mode 100644 index ff93690555..0000000000 --- a/library/HTMLPurifier/HTMLModule/Edit.php +++ /dev/null @@ -1,38 +0,0 @@ - 'URI', - // 'datetime' => 'Datetime', // not implemented - ); - $this->addElement('del', 'Inline', $contents, 'Common', $attr); - $this->addElement('ins', 'Inline', $contents, 'Common', $attr); - } - - // HTML 4.01 specifies that ins/del must not contain block - // elements when used in an inline context, chameleon is - // a complicated workaround to acheive this effect - - // Inline context ! Block context (exclamation mark is - // separator, see getChildDef for parsing) - - public $defines_child_def = true; - 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/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 @@ - '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 . 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/HTMLPurifier/HTMLModule/Hypertext.php deleted file mode 100644 index d7e9bdd27e..0000000000 --- a/library/HTMLPurifier/HTMLModule/Hypertext.php +++ /dev/null @@ -1,31 +0,0 @@ -addElement( - 'a', 'Inline', 'Inline', 'Common', - array( - // 'accesskey' => 'Character', - // 'charset' => 'Charset', - 'href' => 'URI', - // 'hreflang' => 'LanguageCode', - 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'), - 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'), - // 'tabindex' => 'Number', - // 'type' => 'ContentType', - ) - ); - $a->formatting = true; - $a->excludes = array('a' => true); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Image.php b/library/HTMLPurifier/HTMLModule/Image.php deleted file mode 100644 index 948d435bcd..0000000000 --- a/library/HTMLPurifier/HTMLModule/Image.php +++ /dev/null @@ -1,40 +0,0 @@ -get('HTML.MaxImgLength'); - $img = $this->addElement( - '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, - 'longdesc' => 'URI', - 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded - ) - ); - if ($max === null || $config->get('HTML.Trusted')) { - $img->attr['height'] = - $img->attr['width'] = 'Length'; - } - - // kind of strange, but splitting things up would be inefficient - $img->attr_transform_pre[] = - $img->attr_transform_post[] = - new HTMLPurifier_AttrTransform_ImgRequired(); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Legacy.php b/library/HTMLPurifier/HTMLModule/Legacy.php deleted file mode 100644 index df33927ba6..0000000000 --- a/library/HTMLPurifier/HTMLModule/Legacy.php +++ /dev/null @@ -1,143 +0,0 @@ -addElement('basefont', 'Inline', 'Empty', false, 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' - )); - - $s = $this->addElement('s', 'Inline', 'Inline', 'Common'); - $s->formatting = true; - - $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common'); - $strike->formatting = true; - - $u = $this->addElement('u', 'Inline', 'Inline', 'Common'); - $u->formatting = true; - - // setup modifications to old elements - - $align = 'Enum#left,right,center,justify'; - - $address = $this->addBlankElement('address'); - $address->content_model = 'Inline | #PCDATA | p'; - $address->content_model_type = 'optional'; - $address->child = false; - - $blockquote = $this->addBlankElement('blockquote'); - $blockquote->content_model = 'Flow | #PCDATA'; - $blockquote->content_model_type = 'optional'; - $blockquote->child = false; - - $br = $this->addBlankElement('br'); - $br->attr['clear'] = 'Enum#left,all,right,none'; - - $caption = $this->addBlankElement('caption'); - $caption->attr['align'] = 'Enum#top,bottom,left,right'; - - $div = $this->addBlankElement('div'); - $div->attr['align'] = $align; - - $dl = $this->addBlankElement('dl'); - $dl->attr['compact'] = 'Bool#compact'; - - for ($i = 1; $i <= 6; $i++) { - $h = $this->addBlankElement("h$i"); - $h->attr['align'] = $align; - } - - $hr = $this->addBlankElement('hr'); - $hr->attr['align'] = $align; - $hr->attr['noshade'] = 'Bool#noshade'; - $hr->attr['size'] = 'Pixels'; - $hr->attr['width'] = 'Length'; - - $img = $this->addBlankElement('img'); - $img->attr['align'] = 'Enum#top,middle,bottom,left,right'; - $img->attr['border'] = 'Pixels'; - $img->attr['hspace'] = 'Pixels'; - $img->attr['vspace'] = 'Pixels'; - - // figure out this integer business - - $li = $this->addBlankElement('li'); - $li->attr['value'] = new HTMLPurifier_AttrDef_Integer(); - $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle'; - - $ol = $this->addBlankElement('ol'); - $ol->attr['compact'] = 'Bool#compact'; - $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer(); - $ol->attr['type'] = 'Enum#s:1,i,I,a,A'; - - $p = $this->addBlankElement('p'); - $p->attr['align'] = $align; - - $pre = $this->addBlankElement('pre'); - $pre->attr['width'] = 'Number'; - - // script omitted - - $table = $this->addBlankElement('table'); - $table->attr['align'] = 'Enum#left,center,right'; - $table->attr['bgcolor'] = 'Color'; - - $tr = $this->addBlankElement('tr'); - $tr->attr['bgcolor'] = 'Color'; - - $th = $this->addBlankElement('th'); - $th->attr['bgcolor'] = 'Color'; - $th->attr['height'] = 'Length'; - $th->attr['nowrap'] = 'Bool#nowrap'; - $th->attr['width'] = 'Length'; - - $td = $this->addBlankElement('td'); - $td->attr['bgcolor'] = 'Color'; - $td->attr['height'] = 'Length'; - $td->attr['nowrap'] = 'Bool#nowrap'; - $td->attr['width'] = 'Length'; - - $ul = $this->addBlankElement('ul'); - $ul->attr['compact'] = 'Bool#compact'; - $ul->attr['type'] = 'Enum#square,disc,circle'; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/List.php b/library/HTMLPurifier/HTMLModule/List.php deleted file mode 100644 index 74d4522f4e..0000000000 --- a/library/HTMLPurifier/HTMLModule/List.php +++ /dev/null @@ -1,37 +0,0 @@ - '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"; - $this->addElement('dl', 'List', 'Required: dt | dd', 'Common'); - - $this->addElement('li', false, 'Flow', 'Common'); - - $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/HTMLPurifier/HTMLModule/Name.php deleted file mode 100644 index 05694b4504..0000000000 --- a/library/HTMLPurifier/HTMLModule/Name.php +++ /dev/null @@ -1,21 +0,0 @@ -addBlankElement($name); - $element->attr['name'] = 'CDATA'; - if (!$config->get('HTML.Attr.Name.UseCDATA')) { - $element->attr_transform_post['NameSync'] = new HTMLPurifier_AttrTransform_NameSync(); - } - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php deleted file mode 100644 index 5f1b14abb8..0000000000 --- a/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php +++ /dev/null @@ -1,14 +0,0 @@ - array( - 'lang' => 'LanguageCode', - ) - ); -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Object.php b/library/HTMLPurifier/HTMLModule/Object.php deleted file mode 100644 index 193c1011f8..0000000000 --- a/library/HTMLPurifier/HTMLModule/Object.php +++ /dev/null @@ -1,47 +0,0 @@ - to cater to legacy browsers: this - * module does not allow this sort of behavior - */ -class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule -{ - - public $name = 'Object'; - public $safe = false; - - public function setup($config) { - - $this->addElement('object', 'Inline', 'Optional: #PCDATA | Flow | param', 'Common', - array( - 'archive' => 'URI', - 'classid' => 'URI', - 'codebase' => 'URI', - 'codetype' => 'Text', - 'data' => 'URI', - 'declare' => 'Bool#declare', - 'height' => 'Length', - 'name' => 'CDATA', - 'standby' => 'Text', - 'tabindex' => 'Number', - 'type' => 'ContentType', - 'width' => 'Length' - ) - ); - - $this->addElement('param', false, 'Empty', false, - 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/HTMLPurifier/HTMLModule/Presentation.php deleted file mode 100644 index 8ff0b5ed78..0000000000 --- a/library/HTMLPurifier/HTMLModule/Presentation.php +++ /dev/null @@ -1,36 +0,0 @@ -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->formatting = true; - $i = $this->addElement('i', 'Inline', 'Inline', 'Common'); - $i->formatting = true; - $small = $this->addElement('small', 'Inline', 'Inline', 'Common'); - $small->formatting = true; - $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/HTMLPurifier/HTMLModule/Proprietary.php deleted file mode 100644 index dd36a3de0e..0000000000 --- a/library/HTMLPurifier/HTMLModule/Proprietary.php +++ /dev/null @@ -1,33 +0,0 @@ -addElement('marquee', 'Inline', 'Flow', 'Common', - array( - 'direction' => 'Enum#left,right,up,down', - 'behavior' => 'Enum#alternate', - 'width' => 'Length', - 'height' => 'Length', - 'scrolldelay' => 'Number', - 'scrollamount' => 'Number', - 'loop' => 'Number', - 'bgcolor' => 'Color', - 'hspace' => 'Pixels', - 'vspace' => 'Pixels', - ) - ); - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Ruby.php b/library/HTMLPurifier/HTMLModule/Ruby.php deleted file mode 100644 index b26a0a30a0..0000000000 --- a/library/HTMLPurifier/HTMLModule/Ruby.php +++ /dev/null @@ -1,27 +0,0 @@ -addElement('ruby', 'Inline', - 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))', - 'Common'); - $this->addElement('rbc', false, 'Required: rb', 'Common'); - $this->addElement('rtc', false, 'Required: rt', 'Common'); - $rb = $this->addElement('rb', false, 'Inline', 'Common'); - $rb->excludes = array('ruby' => true); - $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number')); - $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/HTMLPurifier/HTMLModule/SafeEmbed.php deleted file mode 100644 index ea256716bb..0000000000 --- a/library/HTMLPurifier/HTMLModule/SafeEmbed.php +++ /dev/null @@ -1,34 +0,0 @@ -get('HTML.MaxImgLength'); - $embed = $this->addElement( - 'embed', 'Inline', 'Empty', 'Common', - array( - 'src*' => 'URI#embedded', - 'type' => 'Enum#application/x-shockwave-flash', - 'width' => 'Pixels#' . $max, - 'height' => 'Pixels#' . $max, - 'allowscriptaccess' => 'Enum#never', - 'allownetworking' => 'Enum#internal', - 'flashvars' => 'Text', - 'wmode' => 'Enum#window', - '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/HTMLPurifier/HTMLModule/SafeObject.php deleted file mode 100644 index 64ab8c0703..0000000000 --- a/library/HTMLPurifier/HTMLModule/SafeObject.php +++ /dev/null @@ -1,53 +0,0 @@ -get('HTML.MaxImgLength'); - $object = $this->addElement( - 'object', - 'Inline', - 'Optional: param | Flow | #PCDATA', - 'Common', - array( - // While technically not required by the spec, we're forcing - // it to this value. - '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')), - ) - ); - $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject(); - - $param = $this->addElement('param', false, 'Empty', false, - array( - 'id' => 'ID', - 'name*' => 'Text', - 'value' => 'Text' - ) - ); - $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam(); - $this->info_injector[] = 'SafeObject'; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Scripting.php b/library/HTMLPurifier/HTMLModule/Scripting.php deleted file mode 100644 index cecdea6c30..0000000000 --- a/library/HTMLPurifier/HTMLModule/Scripting.php +++ /dev/null @@ -1,54 +0,0 @@ - 'script | noscript', 'Inline' => 'script | noscript'); - public $safe = false; - - 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 - // blockquote's contents are optional while noscript's contents - // are required) - - // TODO: convert this to new syntax, main problem is getting - // both content sets working - - // 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']->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')) - ); - $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'] = - new HTMLPurifier_AttrTransform_ScriptRequired(); - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/StyleAttribute.php b/library/HTMLPurifier/HTMLModule/StyleAttribute.php deleted file mode 100644 index eb78464cc0..0000000000 --- a/library/HTMLPurifier/HTMLModule/StyleAttribute.php +++ /dev/null @@ -1,24 +0,0 @@ - array('style' => false), // see constructor - 'Core' => array(0 => array('Style')) - ); - - 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/HTMLPurifier/HTMLModule/Tables.php deleted file mode 100644 index f314ced3f8..0000000000 --- a/library/HTMLPurifier/HTMLModule/Tables.php +++ /dev/null @@ -1,66 +0,0 @@ -addElement('caption', false, 'Inline', 'Common'); - - $this->addElement('table', 'Block', - new HTMLPurifier_ChildDef_Table(), 'Common', - array( - 'border' => 'Pixels', - 'cellpadding' => 'Length', - 'cellspacing' => 'Length', - 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border', - 'rules' => 'Enum#none,groups,rows,cols,all', - 'summary' => 'Text', - 'width' => 'Length' - ) - ); - - // common attributes - $cell_align = array( - 'align' => 'Enum#left,center,right,justify,char', - 'charoff' => 'Length', - 'valign' => 'Enum#top,middle,bottom,baseline', - ); - - $cell_t = array_merge( - array( - 'abbr' => 'Text', - 'colspan' => 'Number', - 'rowspan' => 'Number', - ), - $cell_align - ); - $this->addElement('td', false, 'Flow', 'Common', $cell_t); - $this->addElement('th', false, 'Flow', 'Common', $cell_t); - - $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align); - - $cell_col = array_merge( - array( - 'span' => 'Number', - 'width' => 'MultiLength', - ), - $cell_align - ); - $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/HTMLPurifier/HTMLModule/Target.php deleted file mode 100644 index 2b844ecc45..0000000000 --- a/library/HTMLPurifier/HTMLModule/Target.php +++ /dev/null @@ -1,23 +0,0 @@ -addBlankElement($name); - $e->attr = array( - 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget() - ); - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Text.php b/library/HTMLPurifier/HTMLModule/Text.php deleted file mode 100644 index ae77c71886..0000000000 --- a/library/HTMLPurifier/HTMLModule/Text.php +++ /dev/null @@ -1,71 +0,0 @@ - 'Heading | Block | Inline' - ); - - public function setup($config) { - - // Inline Phrasal ------------------------------------------------- - $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'); - - $em = $this->addElement('em', 'Inline', 'Inline', 'Common'); - $em->formatting = true; - - $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common'); - $strong->formatting = true; - - $code = $this->addElement('code', 'Inline', 'Inline', 'Common'); - $code->formatting = true; - - // Inline Structural ---------------------------------------------- - $this->addElement('span', 'Inline', 'Inline', 'Common'); - $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') ); - $pre = $this->addElement('pre', 'Block', 'Inline', 'Common'); - $pre->excludes = $this->makeLookup( - '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'); - $this->addElement('h4', 'Heading', 'Inline', 'Common'); - $this->addElement('h5', 'Heading', 'Inline', 'Common'); - $this->addElement('h6', 'Heading', 'Inline', 'Common'); - - // Block Structural ----------------------------------------------- - $p = $this->addElement('p', 'Block', 'Inline', 'Common'); - $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/HTMLPurifier/HTMLModule/Tidy.php deleted file mode 100644 index 21783f18eb..0000000000 --- a/library/HTMLPurifier/HTMLModule/Tidy.php +++ /dev/null @@ -1,207 +0,0 @@ - 'none', 'light', 'medium', 'heavy'); - - /** - * Default level to place all fixes in. Disabled by default - */ - public $defaultLevel = null; - - /** - * Lists of fixes used by getFixesForLevel(). Format is: - * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2'); - */ - public $fixesForLevel = array( - 'light' => array(), - 'medium' => array(), - 'heavy' => array() - ); - - /** - * Lazy load constructs the module by determining the necessary - * fixes to create and then delegating to the populate() function. - * @todo Wildcard matching and error reporting when an added or - * subtracted fix has no effect. - */ - public function setup($config) { - - // create fixes, initialize fixesForLevel - $fixes = $this->makeFixes(); - $this->makeFixesForLevel($fixes); - - // figure out which fixes to use - $level = $config->get('HTML.TidyLevel'); - $fixes_lookup = $this->getFixesForLevel($level); - - // get custom fix declarations: these need namespace processing - $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])) - ) { - 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 - */ - 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 ($i == $c) { - trigger_error( - 'Tidy level ' . htmlspecialchars($level) . ' not recognized', - E_USER_WARNING - ); - return array(); - } - $ret = array(); - foreach ($activated_levels as $level) { - foreach ($this->fixesForLevel[$level] as $fix) { - $ret[$fix] = true; - } - } - return $ret; - } - - /** - * 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. - */ - public function makeFixesForLevel($fixes) { - if (!isset($this->defaultLevel)) return; - if (!isset($this->fixesForLevel[$this->defaultLevel])) { - trigger_error( - 'Default level ' . $this->defaultLevel . ' does not exist', - E_USER_ERROR - ); - return; - } - $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes); - } - - /** - * 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 - */ - public function populate($fixes) { - foreach ($fixes as $name => $fix) { - // determine what the fix is for - list($type, $params) = $this->getFixType($name); - switch ($type) { - case 'attr_transform_pre': - case 'attr_transform_post': - $attr = $params['attr']; - if (isset($params['element'])) { - $element = $params['element']; - if (empty($this->info[$element])) { - $e = $this->addBlankElement($element); - } else { - $e = $this->info[$element]; - } - } else { - $type = "info_$type"; - $e = $this; - } - // PHP does some weird parsing when I do - // $e->$type[$attr], so I have to assign a ref. - $f =& $e->$type; - $f[$attr] = $fix; - break; - case 'tag_transform': - $this->info_tag_transform[$params['element']] = $fix; - break; - case 'child': - case 'content_model_type': - $element = $params['element']; - if (empty($this->info[$element])) { - $e = $this->addBlankElement($element); - } else { - $e = $this->info[$element]; - } - $e->$type = $fix; - break; - default: - trigger_error("Fix type $type not supported", E_USER_ERROR); - break; - } - } - } - - /** - * Parses a fix name and determines what kind of fix it is, as well - * as other information defined by the fix - * @param $name String name of fix - * @return array(string $fix_type, array $fix_parameters) - * @note $fix_parameters is type dependant, see populate() for usage - * of these parameters - */ - 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); - - // figure out the parameters - $params = array(); - 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'; - $type = 'attr_transform_' . $property; - return array($type, $params); - } - - // special case: tag transform - if (is_null($property)) { - return array('tag_transform', $params); - } - - return array($property, $params); - - } - - /** - * Defines all fixes the module will perform in a compact - * associative array of fix name to fix implementation. - */ - public function makeFixes() {} - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Tidy/Name.php b/library/HTMLPurifier/HTMLModule/Tidy/Name.php deleted file mode 100644 index 61ff85ce2f..0000000000 --- a/library/HTMLPurifier/HTMLModule/Tidy/Name.php +++ /dev/null @@ -1,24 +0,0 @@ -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/HTMLPurifier/HTMLModule/Tidy/Transitional.php deleted file mode 100644 index 9960b1dd10..0000000000 --- a/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php +++ /dev/null @@ -1,9 +0,0 @@ - '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;', - )); - - // @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;' - )); - - // @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;' - )); - - // @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;"; - // }}} - $r['h1@align'] = - $r['h2@align'] = - $r['h3@align'] = - $r['h4@align'] = - $r['h5@align'] = - $r['h6@align'] = - $r['p@align'] = - $r['div@align'] = - new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup); - - // @bgcolor for table, tr, td, th --------------------------------- - $r['table@bgcolor'] = - $r['td@bgcolor'] = - $r['th@bgcolor'] = - new HTMLPurifier_AttrTransform_BgColor(); - - // @border for img ------------------------------------------------ - $r['img@border'] = new HTMLPurifier_AttrTransform_Border(); - - // @clear for br -------------------------------------------------- - $r['br@clear'] = - 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'] = - $r['th@height'] = - new HTMLPurifier_AttrTransform_Length('height'); - - // @hspace for img ------------------------------------------------ - $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace'); - - // @noshade for hr ------------------------------------------------ - // this transformation is not precise but often good enough. - // different browsers use different styles to designate noshade - $r['hr@noshade'] = - new HTMLPurifier_AttrTransform_BoolToCSS( - 'noshade', - 'color:#808080;background-color:#808080;border:0;' - ); - - // @nowrap for td, th --------------------------------------------- - $r['td@nowrap'] = - $r['th@nowrap'] = - new HTMLPurifier_AttrTransform_BoolToCSS( - 'nowrap', - 'white-space:nowrap;' - ); - - // @size for hr -------------------------------------------------- - $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height'); - - // @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; - // }}} - - $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types); - $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true); - $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true); - - // @vspace for img ------------------------------------------------ - $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace'); - - // @width for hr, td, th ------------------------------------------ - $r['td@width'] = - $r['th@width'] = - $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/HTMLPurifier/HTMLModule/XMLCommonAttributes.php deleted file mode 100644 index 9c0e031984..0000000000 --- a/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php +++ /dev/null @@ -1,14 +0,0 @@ - array( - 'xml:lang' => 'LanguageCode', - ) - ); -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModuleManager.php b/library/HTMLPurifier/HTMLModuleManager.php deleted file mode 100644 index f5c4a1d2cb..0000000000 --- a/library/HTMLPurifier/HTMLModuleManager.php +++ /dev/null @@ -1,403 +0,0 @@ -attrTypes = new HTMLPurifier_AttrTypes(); - $this->doctypes = new HTMLPurifier_DoctypeRegistry(); - - // setup basic modules - $common = array( - 'CommonAttributes', 'Text', 'Hypertext', 'List', - 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image', - 'StyleAttribute', - // Unsafe: - 'Scripting', 'Object', 'Forms', - // Sorta legacy, but present in strict: - 'Name', - ); - $transitional = array('Legacy', 'Target'); - $xml = array('XMLCommonAttributes'); - $non_xml = array('NonXMLCommonAttributes'); - - // setup basic doctypes - $this->doctypes->register( - 'HTML 4.01 Transitional', false, - array_merge($common, $transitional, $non_xml), - array('Tidy_Transitional', 'Tidy_Proprietary'), - array(), - '-//W3C//DTD HTML 4.01 Transitional//EN', - 'http://www.w3.org/TR/html4/loose.dtd' - ); - - $this->doctypes->register( - 'HTML 4.01 Strict', false, - array_merge($common, $non_xml), - array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'), - array(), - '-//W3C//DTD HTML 4.01//EN', - 'http://www.w3.org/TR/html4/strict.dtd' - ); - - $this->doctypes->register( - 'XHTML 1.0 Transitional', true, - array_merge($common, $transitional, $xml, $non_xml), - array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'), - array(), - '-//W3C//DTD XHTML 1.0 Transitional//EN', - 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd' - ); - - $this->doctypes->register( - 'XHTML 1.0 Strict', true, - array_merge($common, $xml, $non_xml), - array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'), - array(), - '-//W3C//DTD XHTML 1.0 Strict//EN', - 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd' - ); - - $this->doctypes->register( - 'XHTML 1.1', true, - array_merge($common, $xml, array('Ruby')), - array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1 - array(), - '-//W3C//DTD XHTML 1.1//EN', - 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd' - ); - - } - - /** - * Registers a module to the recognized module list, useful for - * overloading pre-existing modules. - * @param $module Mixed: string module name, with or without - * HTMLPurifier_HTMLModule prefix, or instance of - * subclass of HTMLPurifier_HTMLModule. - * @param $overload Boolean whether or not to overload previous modules. - * If this is not set, and you do overload a module, - * HTML Purifier will complain with a warning. - * @note This function will not call autoload, you must instantiate - * (and thus invoke) autoload outside the method. - * @note If a string is passed as a module name, different variants - * will be tested in this order: - * - Check for HTMLPurifier_HTMLModule_$name - * - Check all prefixes with $name in order they were added - * - Check for literal object name - * - Throw fatal error - * If your object name collides with an internal class, specify - * your module manually. All modules must have been included - * externally: registerModule will not perform inclusions for you! - */ - public function registerModule($module, $overload = false) { - if (is_string($module)) { - // attempt to load the module - $original_module = $module; - $ok = false; - foreach ($this->prefixes as $prefix) { - $module = $prefix . $original_module; - if (class_exists($module)) { - $ok = true; - break; - } - } - if (!$ok) { - $module = $original_module; - if (!class_exists($module)) { - trigger_error($original_module . ' module does not exist', - E_USER_ERROR); - return; - } - } - $module = new $module(); - } - if (empty($module->name)) { - trigger_error('Module instance of ' . get_class($module) . ' must have name'); - return; - } - if (!$overload && isset($this->registeredModules[$module->name])) { - trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING); - } - $this->registeredModules[$module->name] = $module; - } - - /** - * Adds a module to the current doctype by first registering it, - * and then tacking it on to the active doctype - */ - public function addModule($module) { - $this->registerModule($module); - if (is_object($module)) $module = $module->name; - $this->userModules[] = $module; - } - - /** - * Adds a class prefix that registerModule() will use to resolve a - * string name to a concrete class - */ - 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 - */ - public function setup($config) { - - $this->trusted = $config->get('HTML.Trusted'); - - // generate - $this->doctype = $this->doctypes->make($config); - $modules = $this->doctype->modules; - - // take out the default modules that aren't allowed - $lookup = $config->get('HTML.AllowedModules'); - $special_cases = $config->get('HTML.CoreModules'); - - if (is_array($lookup)) { - foreach ($modules as $k => $m) { - 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.) - 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'; - } - - // merge in custom modules - $modules = array_merge($modules, $this->userModules); - - foreach ($modules as $module) { - $this->processModule($module); - $this->modules[$module]->setup($config); - } - - foreach ($this->doctype->tidyModules as $module) { - $this->processModule($module); - $this->modules[$module]->setup($config); - } - - // prepare any injectors - foreach ($this->modules as $module) { - $n = array(); - foreach ($module->info_injector as $i => $injector) { - if (!is_object($injector)) { - $class = "HTMLPurifier_Injector_$injector"; - $injector = new $class; - } - $n[$injector->name] = $injector; - } - $module->info_injector = $n; - } - - // setup lookup table based on all valid modules - foreach ($this->modules as $module) { - foreach ($module->info as $name => $def) { - if (!isset($this->elementLookup[$name])) { - $this->elementLookup[$name] = array(); - } - $this->elementLookup[$name][] = $module->name; - } - } - - // note the different choice - $this->contentSets = new HTMLPurifier_ContentSets( - // content set assembly deals with all possible modules, - // not just ones deemed to be "safe" - $this->modules - ); - $this->attrCollections = new HTMLPurifier_AttrCollections( - $this->attrTypes, - // there is no way to directly disable a global attribute, - // but using AllowedAttributes or simply not including - // the module in your custom doctype should be sufficient - $this->modules - ); - } - - /** - * Takes a module and adds it to the active module collection, - * registering it if necessary. - */ - public function processModule($module) { - if (!isset($this->registeredModules[$module]) || is_object($module)) { - $this->registerModule($module); - } - $this->modules[$module] = $this->registeredModules[$module]; - } - - /** - * Retrieves merged element definitions. - * @return Array of HTMLPurifier_ElementDef - */ - public function getElements() { - - $elements = array(); - foreach ($this->modules as $module) { - if (!$this->trusted && !$module->safe) continue; - foreach ($module->info as $name => $v) { - if (isset($elements[$name])) continue; - $elements[$name] = $this->getElement($name); - } - } - - // 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]); - } - - return $elements; - - } - - /** - * Retrieves a single merged element definition - * @param $name Name of element - * @param $trusted Boolean trusted overriding parameter: set to true - * if you want the full version of an element - * @return 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) { - - if (!isset($this->elementLookup[$name])) { - return false; - } - - // setup global state variables - $def = false; - if ($trusted === null) $trusted = $this->trusted; - - // iterate through each module that has registered itself to this - // element - foreach($this->elementLookup[$name] as $module_name) { - - $module = $this->modules[$module_name]; - - // refuse to create/merge from a module that is deemed unsafe-- - // pretend the module doesn't exist--when trusted mode is not on. - if (!$trusted && !$module->safe) { - continue; - } - - // clone is used because, ideally speaking, the original - // definition should not be modified. Usually, this will - // make no difference, but for consistency's sake - $new_def = clone $module->info[$name]; - - if (!$def && $new_def->standalone) { - $def = $new_def; - } elseif ($def) { - // This will occur even if $new_def is standalone. In practice, - // this will usually result in a full replacement. - $def->mergeIn($new_def); - } else { - // :TODO: - // non-standalone definitions that don't have a standalone - // to merge into could be deferred to the end - continue; - } - - // attribute value expansions - $this->attrCollections->performInclusions($def->attr); - $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes); - - // descendants_are_inline, for ChildDef_Chameleon - if (is_string($def->content_model) && - strpos($def->content_model, 'Inline') !== false) { - if ($name != 'del' && $name != 'ins') { - // this is for you, ins/del - $def->descendants_are_inline = true; - } - } - - $this->contentSets->generateChildDef($def, $module); - } - - // This can occur if there is a blank definition, but no base to - // mix it in with - if (!$def) return false; - - // add information on required attributes - foreach ($def->attr as $attr_name => $attr_def) { - if ($attr_def->required) { - $def->required_attr[] = $attr_name; - } - } - - return $def; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/IDAccumulator.php b/library/HTMLPurifier/IDAccumulator.php deleted file mode 100644 index 73215295a5..0000000000 --- a/library/HTMLPurifier/IDAccumulator.php +++ /dev/null @@ -1,53 +0,0 @@ -load($config->get('Attr.IDBlacklist')); - return $id_accumulator; - } - - /** - * 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 - */ - public function add($id) { - if (isset($this->ids[$id])) return false; - return $this->ids[$id] = true; - } - - /** - * Load a list of IDs into the lookup table - * @param $array_of_ids Array of IDs to load - * @note This function doesn't care about duplicates - */ - 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/HTMLPurifier/Injector.php deleted file mode 100644 index 5922f81305..0000000000 --- a/library/HTMLPurifier/Injector.php +++ /dev/null @@ -1,239 +0,0 @@ -processToken() - * documentation. - * - * @todo Allow injectors to request a re-run on their output. This - * would help if an operation is recursive. - */ -abstract class HTMLPurifier_Injector -{ - - /** - * Advisory name of injector, this is for friendly error messages - */ - public $name; - - /** - * Instance of HTMLPurifier_HTMLDefinition - */ - protected $htmlDefinition; - - /** - * Reference to CurrentNesting variable in Context. This is an array - * list of tokens that we are currently "inside" - */ - protected $currentNesting; - - /** - * Reference to InputTokens variable in Context. This is an array - * list of the input tokens that are being processed. - */ - protected $inputTokens; - - /** - * Reference to InputIndex variable in Context. This is an integer - * array index for $this->inputTokens that indicates what token - * is currently being processed. - */ - protected $inputIndex; - - /** - * 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') - */ - public $needed = array(); - - /** - * Index of inputTokens to rewind to. - */ - protected $rewind = 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. - * @warning HTML Purifier will prevent you from fast-forwarding with this - * function. - */ - public function rewind($index) { - $this->rewind = $index; - } - - /** - * Retrieves rewind, and then unsets it. - */ - public function getRewind() { - $r = $this->rewind; - $this->rewind = false; - return $r; - } - - /** - * Prepares the injector by giving it the config and context objects: - * 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 - */ - 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; - $this->currentNesting =& $context->get('CurrentNesting'); - $this->inputTokens =& $context->get('InputTokens'); - $this->inputIndex =& $context->get('InputIndex'); - return false; - } - - /** - * 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 - */ - 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; - foreach ($attributes as $name) { - if (!isset($def->info[$element]->attr[$name])) return "$element.$name"; - } - } - return false; - } - - /** - * 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 - */ - public function allowsElement($name) { - if (!empty($this->currentNesting)) { - $parent_token = array_pop($this->currentNesting); - $this->currentNesting[] = $parent_token; - $parent = $this->htmlDefinition->info[$parent_token->name]; - } else { - $parent = $this->htmlDefinition->info_parent_def; - } - if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) { - return false; - } - // check for exclusion - 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; - } - return true; - } - - /** - * Iterator function, which starts with the next token and continues until - * 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 - */ - 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]; - return true; - } - - /** - * 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. - */ - 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; - $nesting--; - } - return true; - } - - /** - * Iterator function, starts with the previous token and continues until - * 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 - */ - protected function backward(&$i, &$current) { - if ($i === null) $i = $this->inputIndex - 1; - else $i--; - if ($i < 0) return false; - $current = $this->inputTokens[$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) {} - - /** - * Handler that is called when a start or empty token is processed - */ - public function handleElement(&$token) {} - - /** - * Handler that is called when an end token is processed - */ - public function handleEnd(&$token) { - $this->notifyEnd($token); - } - - /** - * Notifier that is called when an end token is processed - * @note This differs from handlers in that the token is read-only - * @deprecated - */ - public function notifyEnd($token) {} - - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Injector/AutoParagraph.php b/library/HTMLPurifier/Injector/AutoParagraph.php deleted file mode 100644 index afa7608924..0000000000 --- a/library/HTMLPurifier/Injector/AutoParagraph.php +++ /dev/null @@ -1,345 +0,0 @@ -armor['MakeWellFormed_TagClosedError'] = true; - return $par; - } - - public function handleText(&$token) { - $text = $token->data; - // Does the current parent allow

tags? - if ($this->allowsElement('p')) { - if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) { - // Note that we have differing behavior when dealing with text - // in the anonymous root node, or a node inside the document. - // If the text as a double-newline, the treatment is the same; - // if it doesn't, see the next if-block if you're in the document. - - $i = $nesting = null; - if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) { - // State 1.1: ... ^ (whitespace, then document end) - // ---- - // This is a degenerate case - } else { - if (!$token->is_whitespace || $this->_isInline($current)) { - // State 1.2: PAR1 - // ---- - - // State 1.3: PAR1\n\nPAR2 - // ------------ - - // State 1.4:

PAR1\n\nPAR2 (see State 2) - // ------------ - $token = array($this->_pStart()); - $this->_splitText($text, $token); - } else { - // State 1.5: \n
- // -- - } - } - } else { - // State 2:
PAR1... (similar to 1.4) - // ---- - - // We're in an element that allows paragraph tags, but we're not - // sure if we're going to need them. - if ($this->_pLookAhead()) { - // State 2.1:
PAR1PAR1\n\nPAR2 - // ---- - // Note: This will always be the first child, since any - // previous inline element would have triggered this very - // same routine, and found the double newline. One possible - // exception would be a comment. - $token = array($this->_pStart(), $token); - } else { - // State 2.2.1:
PAR1
- // ---- - - // State 2.2.2:
PAR1PAR1
- // ---- - } - } - // Is the current parent a

tag? - } elseif ( - !empty($this->currentNesting) && - $this->currentNesting[count($this->currentNesting)-1]->name == 'p' - ) { - // State 3.1: ...

PAR1 - // ---- - - // State 3.2: ...

PAR1\n\nPAR2 - // ------------ - $token = array(); - $this->_splitText($text, $token); - // Abort! - } else { - // State 4.1: ...PAR1 - // ---- - - // State 4.2: ...PAR1\n\nPAR2 - // ------------ - } - } - - public function handleElement(&$token) { - // We don't have to check if we're already in a

tag for block - // tokens, because the tag would have been autoclosed by MakeWellFormed. - if ($this->allowsElement('p')) { - if (!empty($this->currentNesting)) { - if ($this->_isInline($token)) { - // State 1:

... - // --- - - // Check if this token is adjacent to the parent token - // (seek backwards until token isn't whitespace) - $i = null; - $this->backward($i, $prev); - - if (!$prev instanceof HTMLPurifier_Token_Start) { - // Token wasn't adjacent - - if ( - $prev instanceof HTMLPurifier_Token_Text && - substr($prev->data, -2) === "\n\n" - ) { - // State 1.1.4:

PAR1

\n\n - // --- - - // Quite frankly, this should be handled by splitText - $token = array($this->_pStart(), $token); - } else { - // State 1.1.1:

PAR1

- // --- - - // State 1.1.2:

- // --- - - // State 1.1.3:
PAR - // --- - } - - } else { - // State 1.2.1:
- // --- - - // Lookahead to see if

is needed. - if ($this->_pLookAhead()) { - // State 1.3.1:

PAR1\n\nPAR2 - // --- - $token = array($this->_pStart(), $token); - } else { - // State 1.3.2:
PAR1
- // --- - - // State 1.3.3:
PAR1
\n\n
- // --- - } - } - } else { - // State 2.3: ...
- // ----- - } - } else { - if ($this->_isInline($token)) { - // State 3.1: - // --- - // This is where the {p} tag is inserted, not reflected in - // inputTokens yet, however. - $token = array($this->_pStart(), $token); - } else { - // State 3.2:
- // ----- - } - - $i = null; - if ($this->backward($i, $prev)) { - if ( - !$prev instanceof HTMLPurifier_Token_Text - ) { - // State 3.1.1: ...

{p} - // --- - - // State 3.2.1: ...

- // ----- - - if (!is_array($token)) $token = array($token); - array_unshift($token, new HTMLPurifier_Token_Text("\n\n")); - } else { - // State 3.1.2: ...

\n\n{p} - // --- - - // State 3.2.2: ...

\n\n
- // ----- - - // Note: PAR cannot occur because PAR would have been - // wrapped in

tags. - } - } - } - } else { - // State 2.2: