Merge remote-tracking branch 'upstream/develop' into 1601-api-statuses-lookup
This commit is contained in:
commit
92007a1438
615 changed files with 37187 additions and 22266 deletions
|
|
@ -4,42 +4,32 @@ Friendica translations
|
|||
Translation Process
|
||||
-------------------
|
||||
|
||||
The strings used in the UI of Friendica is translated at [Transifex] [1] and then
|
||||
included in the git repository at github. If you want to help with translation
|
||||
for any language, be it correcting terms or translating friendica to a
|
||||
currently not supported language, please register an account at transifex.com
|
||||
and contact the friendica translation team there.
|
||||
The strings used in the UI of Friendica is translated at [Transifex] [1] and then included in the git repository at github.
|
||||
If you want to help with translation for any language, be it correcting terms or translating friendica to a currently not supported language, please register an account at transifex.com and contact the friendica translation team there.
|
||||
|
||||
Translating friendica is simple. Just use the online tool at transifex. If you
|
||||
don't want to deal with git & co. that is fine, we check the status of the
|
||||
translations regularly and import them into the source tree at github so that
|
||||
others can use them.
|
||||
Translating friendica is simple.
|
||||
Just use the online tool at transifex.
|
||||
If you don't want to deal with git & co. that is fine, we check the status of the translations regularly and import them into the source tree at github so that others can use them.
|
||||
|
||||
We do not include every translation from transifex in the source tree to avoid
|
||||
a scattered and disturbed overall experience. As an uneducated guess we have a
|
||||
lower limit of 50% translated strings before we include the language (for the
|
||||
core message.po file, addon translation will be included once all strings of
|
||||
an addon are translated. This limit is judging only by the amount of translated
|
||||
strings under the assumption that the most prominent strings for the UI will be
|
||||
translated first by a translation team. If you feel your translation useable
|
||||
before this limit, please contact us and we will probably include your teams
|
||||
work in the source tree.
|
||||
We do not include every translation from transifex in the source tree to avoid a scattered and disturbed overall experience.
|
||||
As an uneducated guess we have a lower limit of 50% translated strings before we include the language (for the core messages.po file, addont translation will be included once all strings of an addon are translated.
|
||||
This limit is judging only by the amount of translated strings under the assumption that the most prominent strings for the UI will be translated first by a translation team.
|
||||
If you feel your translation useable before this limit, please contact us and we will probably include your teams work in the source tree.
|
||||
|
||||
If you want to get your work into the source tree yourself, feel free to do so
|
||||
and contact us with and question that arises. The process is simple and
|
||||
friendica ships with all the tools necessary.
|
||||
If you want to help translating, please concentrate on the core messages.po file first.
|
||||
We will only include translations with a sufficient translated messages.po file.
|
||||
Translations of addons will only be included, when the core file is included as well.
|
||||
|
||||
If you want to get your work into the source tree yourself, feel free to do so and contact us with and question that arises.
|
||||
The process is simple and friendica ships with all the tools necessary.
|
||||
|
||||
The location of the translated files in the source tree is
|
||||
/view/LNG-CODE/
|
||||
where LNG-CODE is the language code used, e.g. de for German or fr for French.
|
||||
For the email templates (the *.tpl files) just place them into the directory
|
||||
and you are done. The translated strings come as a "message.po" file from
|
||||
transifex which needs to be translated into the PHP file friendica uses. To do
|
||||
so, place the file in the directory mentioned above and use the "po2php"
|
||||
utility from the util directory of your friendica installation.
|
||||
The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses.
|
||||
To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation.
|
||||
|
||||
Assuming you want to convert the German localization which is placed in
|
||||
view/de/message.po you would do the following.
|
||||
Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following.
|
||||
|
||||
1. Navigate at the command prompt to the base directory of your
|
||||
friendica installation
|
||||
|
|
@ -51,11 +41,11 @@ view/de/message.po you would do the following.
|
|||
|
||||
The output of the script will be placed at view/de/strings.php where
|
||||
friendica is expecting it, so you can test your translation immediately.
|
||||
|
||||
|
||||
3. Visit your friendica page to check if it still works in the language you
|
||||
just translated. If not try to find the error, most likely PHP will give
|
||||
you a hint in the log/warnings.about the error.
|
||||
|
||||
|
||||
For debugging you can also try to "run" the file with PHP. This should
|
||||
not give any output if the file is ok but might give a hint for
|
||||
searching the bug in the file.
|
||||
|
|
@ -69,30 +59,10 @@ view/de/message.po you would do the following.
|
|||
Utilities
|
||||
---------
|
||||
|
||||
Additional to the po2php script there are some more utilities for translation
|
||||
in the "util" directory of the friendica source tree. If you only want to
|
||||
translate friendica into another language you won't need any of these tools most
|
||||
likely but it gives you an idea how the translation process of friendica
|
||||
works.
|
||||
Additional to the po2php script there are some more utilities for translation in the "util" directory of the friendica source tree.
|
||||
If you only want to translate friendica into another language you wont need any of these tools most likely but it gives you an idea how the translation process of friendica works.
|
||||
|
||||
For further information see the utils/README file.
|
||||
|
||||
Known Problems
|
||||
--------------
|
||||
|
||||
Friendica uses the language setting of the visitors browser to determain the
|
||||
language for the UI. Most of the time this works, but there are some known
|
||||
quirks.
|
||||
|
||||
One is that some browsers, like Safari, do the setting to "de-de" but friendica
|
||||
only has a "de" localisation. A workaround would be to add a symbolic link
|
||||
from
|
||||
$friendica/view/de-de
|
||||
pointing to
|
||||
$friendica/view/de
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
[1]: https://www.transifex.com/projects/p/friendica/
|
||||
|
||||
|
|
|
|||
38
boot.php
38
boot.php
|
|
@ -17,6 +17,8 @@
|
|||
* easily as email does today.
|
||||
*/
|
||||
|
||||
require_once('include/autoloader.php');
|
||||
|
||||
require_once('include/config.php');
|
||||
require_once('include/network.php');
|
||||
require_once('include/plugin.php');
|
||||
|
|
@ -36,7 +38,7 @@ define ( 'FRIENDICA_PLATFORM', 'Friendica');
|
|||
define ( 'FRIENDICA_CODENAME', 'Asparagus');
|
||||
define ( 'FRIENDICA_VERSION', '3.5-dev' );
|
||||
define ( 'DFRN_PROTOCOL_VERSION', '2.23' );
|
||||
define ( 'DB_UPDATE_VERSION', 1191 );
|
||||
define ( 'DB_UPDATE_VERSION', 1194 );
|
||||
|
||||
/**
|
||||
* @brief Constant with a HTML line break.
|
||||
|
|
@ -467,6 +469,7 @@ class App {
|
|||
public $is_tablet;
|
||||
public $is_friendica_app;
|
||||
public $performance = array();
|
||||
public $callstack = array();
|
||||
|
||||
public $nav_sel;
|
||||
|
||||
|
|
@ -529,6 +532,8 @@ class App {
|
|||
private $cached_profile_image;
|
||||
private $cached_profile_picdate;
|
||||
|
||||
private static $a;
|
||||
|
||||
/**
|
||||
* @brief App constructor.
|
||||
*/
|
||||
|
|
@ -554,6 +559,12 @@ class App {
|
|||
$this->performance["marktime"] = 0;
|
||||
$this->performance["markstart"] = microtime(true);
|
||||
|
||||
$this->callstack["database"] = array();
|
||||
$this->callstack["network"] = array();
|
||||
$this->callstack["file"] = array();
|
||||
$this->callstack["rendering"] = array();
|
||||
$this->callstack["parser"] = array();
|
||||
|
||||
$this->config = array();
|
||||
$this->page = array();
|
||||
$this->pager= array();
|
||||
|
|
@ -703,6 +714,8 @@ class App {
|
|||
}
|
||||
}
|
||||
|
||||
self::$a = $this;
|
||||
|
||||
}
|
||||
|
||||
function get_basepath() {
|
||||
|
|
@ -727,6 +740,10 @@ class App {
|
|||
|
||||
function get_baseurl($ssl = false) {
|
||||
|
||||
// Is the function called statically?
|
||||
if (!is_object($this))
|
||||
return(self::$a->get_baseurl($ssl));
|
||||
|
||||
$scheme = $this->scheme;
|
||||
|
||||
if((x($this->config,'system')) && (x($this->config['system'],'ssl_policy'))) {
|
||||
|
|
@ -903,6 +920,10 @@ class App {
|
|||
}
|
||||
|
||||
function get_cached_avatar_image($avatar_image){
|
||||
return $avatar_image;
|
||||
|
||||
// The following code is deactivated. It doesn't seem to make any sense and it slows down the system.
|
||||
/*
|
||||
if($this->cached_profile_image[$avatar_image])
|
||||
return $this->cached_profile_image[$avatar_image];
|
||||
|
||||
|
|
@ -922,6 +943,7 @@ class App {
|
|||
}
|
||||
}
|
||||
return $this->cached_profile_image[$avatar_image];
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1016,6 +1038,20 @@ class App {
|
|||
|
||||
$this->performance[$value] += (float)$duration;
|
||||
$this->performance["marktime"] += (float)$duration;
|
||||
|
||||
// Trace the different functions with their timestamps
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
|
||||
|
||||
array_shift($trace);
|
||||
|
||||
$function = array();
|
||||
foreach ($trace AS $func)
|
||||
$function[] = $func["function"];
|
||||
|
||||
$function = implode(", ", $function);
|
||||
|
||||
$this->callstack[$value][$function] += (float)$duration;
|
||||
|
||||
}
|
||||
|
||||
function mark_timestamp($mark) {
|
||||
|
|
|
|||
240
database.sql
240
database.sql
|
|
@ -1,6 +1,6 @@
|
|||
-- ------------------------------------------
|
||||
-- Friendica 3.4.2 (Lily of the valley)
|
||||
-- DB_UPDATE_VERSION 1190
|
||||
-- Friendica 3.5-dev (Asparagus)
|
||||
-- DB_UPDATE_VERSION 1193
|
||||
-- ------------------------------------------
|
||||
|
||||
|
||||
|
|
@ -8,20 +8,21 @@
|
|||
-- TABLE addon
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `addon` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`version` varchar(255) NOT NULL DEFAULT '',
|
||||
`installed` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`hidden` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`timestamp` bigint(20) NOT NULL DEFAULT 0,
|
||||
`plugin_admin` tinyint(1) NOT NULL DEFAULT 0
|
||||
`plugin_admin` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE attach
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `attach` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`hash` varchar(64) NOT NULL DEFAULT '',
|
||||
`filename` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -33,28 +34,31 @@ CREATE TABLE IF NOT EXISTS `attach` (
|
|||
`allow_cid` mediumtext NOT NULL,
|
||||
`allow_gid` mediumtext NOT NULL,
|
||||
`deny_cid` mediumtext NOT NULL,
|
||||
`deny_gid` mediumtext NOT NULL
|
||||
`deny_gid` mediumtext NOT NULL,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE auth_codes
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `auth_codes` (
|
||||
`id` varchar(40) NOT NULL PRIMARY KEY,
|
||||
`id` varchar(40) NOT NULL,
|
||||
`client_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`redirect_uri` varchar(200) NOT NULL DEFAULT '',
|
||||
`expires` int(11) NOT NULL DEFAULT 0,
|
||||
`scope` varchar(250) NOT NULL DEFAULT ''
|
||||
`scope` varchar(250) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE cache
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `cache` (
|
||||
`k` varchar(255) NOT NULL PRIMARY KEY,
|
||||
`k` varchar(255) NOT NULL,
|
||||
`v` text NOT NULL,
|
||||
`expire_mode` int(11) NOT NULL DEFAULT 0,
|
||||
`updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`k`),
|
||||
INDEX `updated` (`updated`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -62,34 +66,37 @@ CREATE TABLE IF NOT EXISTS `cache` (
|
|||
-- TABLE challenge
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `challenge` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`challenge` varchar(255) NOT NULL DEFAULT '',
|
||||
`dfrn-id` varchar(255) NOT NULL DEFAULT '',
|
||||
`expire` int(11) NOT NULL DEFAULT 0,
|
||||
`type` varchar(255) NOT NULL DEFAULT '',
|
||||
`last_update` varchar(255) NOT NULL DEFAULT ''
|
||||
`last_update` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE clients
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `clients` (
|
||||
`client_id` varchar(20) NOT NULL PRIMARY KEY,
|
||||
`client_id` varchar(20) NOT NULL,
|
||||
`pw` varchar(20) NOT NULL DEFAULT '',
|
||||
`redirect_uri` varchar(200) NOT NULL DEFAULT '',
|
||||
`name` text,
|
||||
`icon` text,
|
||||
`uid` int(11) NOT NULL DEFAULT 0
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`client_id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE config
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `config` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`cat` varchar(255) NOT NULL DEFAULT '',
|
||||
`k` varchar(255) NOT NULL DEFAULT '',
|
||||
`v` text NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `cat_k` (`cat`(30),`k`(30))
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -97,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `config` (
|
|||
-- TABLE contact
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `contact` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`self` tinyint(1) NOT NULL DEFAULT 0,
|
||||
|
|
@ -162,6 +169,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
|
|||
`notify_new_posts` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`fetch_further_information` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`ffi_keyword_blacklist` mediumtext NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -169,7 +177,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
|
|||
-- TABLE conv
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `conv` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`guid` varchar(64) NOT NULL DEFAULT '',
|
||||
`recips` mediumtext NOT NULL,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
|
|
@ -177,6 +185,7 @@ CREATE TABLE IF NOT EXISTS `conv` (
|
|||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`subject` mediumtext NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -184,27 +193,29 @@ CREATE TABLE IF NOT EXISTS `conv` (
|
|||
-- TABLE deliverq
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `deliverq` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`cmd` varchar(32) NOT NULL DEFAULT '',
|
||||
`item` int(11) NOT NULL DEFAULT 0,
|
||||
`contact` int(11) NOT NULL DEFAULT 0
|
||||
`contact` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE dsprphotoq
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `dsprphotoq` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`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
|
||||
`attempt` tinyint(4) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE event
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `event` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`cid` int(11) NOT NULL DEFAULT 0,
|
||||
`uri` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -223,6 +234,7 @@ CREATE TABLE IF NOT EXISTS `event` (
|
|||
`allow_gid` mediumtext NOT NULL,
|
||||
`deny_cid` mediumtext NOT NULL,
|
||||
`deny_gid` mediumtext NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -230,7 +242,7 @@ CREATE TABLE IF NOT EXISTS `event` (
|
|||
-- TABLE fcontact
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `fcontact` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`url` varchar(255) NOT NULL DEFAULT '',
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`photo` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -246,6 +258,7 @@ CREATE TABLE IF NOT EXISTS `fcontact` (
|
|||
`alias` varchar(255) NOT NULL DEFAULT '',
|
||||
`pubkey` text NOT NULL,
|
||||
`updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `addr` (`addr`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -253,20 +266,22 @@ CREATE TABLE IF NOT EXISTS `fcontact` (
|
|||
-- TABLE ffinder
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `ffinder` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`cid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fid` int(10) unsigned NOT NULL DEFAULT 0
|
||||
`fid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE fserver
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `fserver` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`server` varchar(255) NOT NULL DEFAULT '',
|
||||
`posturl` varchar(255) NOT NULL DEFAULT '',
|
||||
`key` text NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `server` (`server`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -274,7 +289,7 @@ CREATE TABLE IF NOT EXISTS `fserver` (
|
|||
-- TABLE fsuggest
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `fsuggest` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`cid` int(11) NOT NULL DEFAULT 0,
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -282,16 +297,18 @@ CREATE TABLE IF NOT EXISTS `fsuggest` (
|
|||
`request` varchar(255) NOT NULL DEFAULT '',
|
||||
`photo` varchar(255) NOT NULL DEFAULT '',
|
||||
`note` text NOT NULL,
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE gcign
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `gcign` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`gcid` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`),
|
||||
INDEX `gcid` (`gcid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
|
@ -300,7 +317,7 @@ CREATE TABLE IF NOT EXISTS `gcign` (
|
|||
-- TABLE gcontact
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `gcontact` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`nick` varchar(255) NOT NULL DEFAULT '',
|
||||
`url` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -315,11 +332,17 @@ CREATE TABLE IF NOT EXISTS `gcontact` (
|
|||
`about` text NOT NULL,
|
||||
`keywords` text NOT NULL,
|
||||
`gender` varchar(32) NOT NULL DEFAULT '',
|
||||
`birthday` varchar(32) NOT NULL DEFAULT '0000-00-00',
|
||||
`community` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`hide` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`nsfw` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`network` varchar(255) NOT NULL DEFAULT '',
|
||||
`addr` varchar(255) NOT NULL DEFAULT '',
|
||||
`notify` text NOT NULL,
|
||||
`alias` varchar(255) NOT NULL DEFAULT '',
|
||||
`generation` tinyint(3) NOT NULL DEFAULT 0,
|
||||
`server_url` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `nurl` (`nurl`),
|
||||
INDEX `updated` (`updated`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
|
@ -328,12 +351,13 @@ CREATE TABLE IF NOT EXISTS `gcontact` (
|
|||
-- TABLE glink
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `glink` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`cid` int(11) NOT NULL DEFAULT 0,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`gcid` int(11) NOT NULL DEFAULT 0,
|
||||
`zcid` int(11) NOT NULL DEFAULT 0,
|
||||
`updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `cid_uid_gcid_zcid` (`cid`,`uid`,`gcid`,`zcid`),
|
||||
INDEX `gcid` (`gcid`),
|
||||
INDEX `zcid` (`zcid`)
|
||||
|
|
@ -343,11 +367,12 @@ CREATE TABLE IF NOT EXISTS `glink` (
|
|||
-- TABLE group
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `group` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`visible` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -355,10 +380,11 @@ CREATE TABLE IF NOT EXISTS `group` (
|
|||
-- TABLE group_member
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `group_member` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`gid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`contact-id` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid_gid_contactid` (`uid`,`gid`,`contact-id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -366,7 +392,7 @@ CREATE TABLE IF NOT EXISTS `group_member` (
|
|||
-- TABLE gserver
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `gserver` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`url` varchar(255) NOT NULL DEFAULT '',
|
||||
`nurl` varchar(255) NOT NULL DEFAULT '',
|
||||
`version` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -381,6 +407,7 @@ CREATE TABLE IF NOT EXISTS `gserver` (
|
|||
`last_poco_query` datetime DEFAULT '0000-00-00 00:00:00',
|
||||
`last_contact` datetime DEFAULT '0000-00-00 00:00:00',
|
||||
`last_failure` datetime DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `nurl` (`nurl`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -388,11 +415,12 @@ CREATE TABLE IF NOT EXISTS `gserver` (
|
|||
-- TABLE guid
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `guid` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`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`)
|
||||
|
|
@ -402,11 +430,12 @@ CREATE TABLE IF NOT EXISTS `guid` (
|
|||
-- TABLE hook
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `hook` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`hook` varchar(255) NOT NULL DEFAULT '',
|
||||
`file` varchar(255) NOT NULL DEFAULT '',
|
||||
`function` varchar(255) NOT NULL DEFAULT '',
|
||||
`priority` int(11) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `hook_file_function` (`hook`(30),`file`(60),`function`(30))
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -414,7 +443,7 @@ CREATE TABLE IF NOT EXISTS `hook` (
|
|||
-- TABLE intro
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `intro` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fid` int(11) NOT NULL DEFAULT 0,
|
||||
`contact-id` int(11) NOT NULL DEFAULT 0,
|
||||
|
|
@ -424,18 +453,20 @@ CREATE TABLE IF NOT EXISTS `intro` (
|
|||
`hash` varchar(255) NOT NULL DEFAULT '',
|
||||
`datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`blocked` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`ignore` tinyint(1) NOT NULL DEFAULT 0
|
||||
`ignore` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE item
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `item` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`guid` varchar(255) NOT NULL DEFAULT '',
|
||||
`uri` varchar(255) NOT NULL DEFAULT '',
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`contact-id` int(11) NOT NULL DEFAULT 0,
|
||||
`gcontact-id` int(11) unsigned NOT NULL DEFAULT 0,
|
||||
`type` varchar(255) NOT NULL DEFAULT '',
|
||||
`wall` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`gravity` tinyint(1) NOT NULL DEFAULT 0,
|
||||
|
|
@ -493,6 +524,7 @@ CREATE TABLE IF NOT EXISTS `item` (
|
|||
`rendered-hash` varchar(32) NOT NULL DEFAULT '',
|
||||
`rendered-html` mediumtext NOT NULL,
|
||||
`global` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `guid` (`guid`),
|
||||
INDEX `uri` (`uri`),
|
||||
INDEX `parent` (`parent`),
|
||||
|
|
@ -509,6 +541,7 @@ CREATE TABLE IF NOT EXISTS `item` (
|
|||
INDEX `uid_thrparent` (`uid`,`thr-parent`),
|
||||
INDEX `uid_parenturi` (`uid`,`parent-uri`),
|
||||
INDEX `uid_contactid_created` (`uid`,`contact-id`,`created`),
|
||||
INDEX `gcontactid_uid_created` (`gcontact-id`,`uid`,`created`),
|
||||
INDEX `wall_body` (`wall`,`body`(6)),
|
||||
INDEX `uid_visible_moderated_created` (`uid`,`visible`,`moderated`,`created`),
|
||||
INDEX `uid_uri` (`uid`,`uri`),
|
||||
|
|
@ -531,11 +564,12 @@ CREATE TABLE IF NOT EXISTS `item` (
|
|||
-- TABLE item_id
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `item_id` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`iid` int(11) NOT NULL DEFAULT 0,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`sid` varchar(255) NOT NULL DEFAULT '',
|
||||
`service` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`),
|
||||
INDEX `sid` (`sid`),
|
||||
INDEX `service` (`service`),
|
||||
|
|
@ -546,17 +580,18 @@ CREATE TABLE IF NOT EXISTS `item_id` (
|
|||
-- TABLE locks
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `locks` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`name` varchar(128) NOT NULL DEFAULT '',
|
||||
`locked` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`created` datetime DEFAULT '0000-00-00 00:00:00'
|
||||
`created` datetime DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE mail
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `mail` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`guid` varchar(64) NOT NULL DEFAULT '',
|
||||
`from-name` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -573,6 +608,7 @@ CREATE TABLE IF NOT EXISTS `mail` (
|
|||
`uri` varchar(255) NOT NULL DEFAULT '',
|
||||
`parent-uri` varchar(255) NOT NULL DEFAULT '',
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`),
|
||||
INDEX `guid` (`guid`),
|
||||
INDEX `convid` (`convid`),
|
||||
|
|
@ -585,7 +621,7 @@ CREATE TABLE IF NOT EXISTS `mail` (
|
|||
-- TABLE mailacct
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `mailacct` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`server` varchar(255) NOT NULL DEFAULT '',
|
||||
`port` int(11) NOT NULL DEFAULT 0,
|
||||
|
|
@ -597,16 +633,18 @@ CREATE TABLE IF NOT EXISTS `mailacct` (
|
|||
`action` int(11) NOT NULL DEFAULT 0,
|
||||
`movetofolder` varchar(255) NOT NULL DEFAULT '',
|
||||
`pubmail` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'
|
||||
`last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE manage
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `manage` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`mid` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid_mid` (`uid`,`mid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -614,7 +652,7 @@ CREATE TABLE IF NOT EXISTS `manage` (
|
|||
-- TABLE notify
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `notify` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`hash` varchar(64) NOT NULL DEFAULT '',
|
||||
`type` int(11) NOT NULL DEFAULT 0,
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -629,6 +667,7 @@ CREATE TABLE IF NOT EXISTS `notify` (
|
|||
`seen` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`verb` varchar(255) NOT NULL DEFAULT '',
|
||||
`otype` varchar(16) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -636,24 +675,50 @@ CREATE TABLE IF NOT EXISTS `notify` (
|
|||
-- TABLE notify-threads
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `notify-threads` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`notify-id` int(11) NOT NULL DEFAULT 0,
|
||||
`master-parent-item` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`parent-item` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`receiver-uid` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `master-parent-item` (`master-parent-item`),
|
||||
INDEX `receiver-uid` (`receiver-uid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE oembed
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `oembed` (
|
||||
`url` varchar(255) NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`url`),
|
||||
INDEX `created` (`created`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE parsed_url
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `parsed_url` (
|
||||
`url` varchar(255) NOT NULL,
|
||||
`guessing` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`oembed` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`content` text NOT NULL,
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`url`,`guessing`,`oembed`),
|
||||
INDEX `created` (`created`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE pconfig
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `pconfig` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`cat` varchar(255) NOT NULL DEFAULT '',
|
||||
`k` varchar(255) NOT NULL DEFAULT '',
|
||||
`v` mediumtext NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid_cat_k` (`uid`,`cat`(30),`k`(30))
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -661,7 +726,7 @@ CREATE TABLE IF NOT EXISTS `pconfig` (
|
|||
-- TABLE photo
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `photo` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`contact-id` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`guid` varchar(64) NOT NULL DEFAULT '',
|
||||
|
|
@ -683,6 +748,7 @@ CREATE TABLE IF NOT EXISTS `photo` (
|
|||
`allow_gid` mediumtext NOT NULL,
|
||||
`deny_cid` mediumtext NOT NULL,
|
||||
`deny_gid` mediumtext NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`),
|
||||
INDEX `resource-id` (`resource-id`),
|
||||
INDEX `guid` (`guid`)
|
||||
|
|
@ -692,7 +758,7 @@ CREATE TABLE IF NOT EXISTS `photo` (
|
|||
-- TABLE poll
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `poll` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`q0` mediumtext NOT NULL,
|
||||
`q1` mediumtext NOT NULL,
|
||||
|
|
@ -704,6 +770,7 @@ CREATE TABLE IF NOT EXISTS `poll` (
|
|||
`q7` mediumtext NOT NULL,
|
||||
`q8` mediumtext NOT NULL,
|
||||
`q9` mediumtext NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -711,9 +778,10 @@ CREATE TABLE IF NOT EXISTS `poll` (
|
|||
-- TABLE poll_result
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `poll_result` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`poll_id` int(11) NOT NULL DEFAULT 0,
|
||||
`choice` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `poll_id` (`poll_id`),
|
||||
INDEX `choice` (`choice`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
|
@ -722,7 +790,7 @@ CREATE TABLE IF NOT EXISTS `poll_result` (
|
|||
-- TABLE profile
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `profile` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`profile-name` varchar(255) NOT NULL DEFAULT '',
|
||||
`is-default` tinyint(1) NOT NULL DEFAULT 0,
|
||||
|
|
@ -763,6 +831,7 @@ CREATE TABLE IF NOT EXISTS `profile` (
|
|||
`thumb` varchar(255) NOT NULL DEFAULT '',
|
||||
`publish` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`net-publish` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `hometown` (`hometown`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -770,39 +839,42 @@ CREATE TABLE IF NOT EXISTS `profile` (
|
|||
-- TABLE profile_check
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `profile_check` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(10) unsigned NOT NULL auto_increment,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`cid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`dfrn_id` varchar(255) NOT NULL DEFAULT '',
|
||||
`sec` varchar(255) NOT NULL DEFAULT '',
|
||||
`expire` int(11) NOT NULL DEFAULT 0
|
||||
`expire` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE push_subscriber
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `push_subscriber` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`callback_url` varchar(255) NOT NULL DEFAULT '',
|
||||
`topic` varchar(255) NOT NULL DEFAULT '',
|
||||
`nickname` varchar(255) NOT NULL DEFAULT '',
|
||||
`push` int(11) NOT NULL DEFAULT 0,
|
||||
`last_update` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`secret` varchar(255) NOT NULL DEFAULT ''
|
||||
`secret` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE queue
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `queue` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`cid` int(11) NOT NULL DEFAULT 0,
|
||||
`network` varchar(32) NOT NULL DEFAULT '',
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`last` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`content` mediumtext NOT NULL,
|
||||
`batch` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `cid` (`cid`),
|
||||
INDEX `created` (`created`),
|
||||
INDEX `last` (`last`),
|
||||
|
|
@ -814,21 +886,23 @@ CREATE TABLE IF NOT EXISTS `queue` (
|
|||
-- TABLE register
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `register` (
|
||||
`id` int(11) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) unsigned NOT NULL auto_increment,
|
||||
`hash` varchar(255) NOT NULL DEFAULT '',
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`uid` int(11) unsigned NOT NULL DEFAULT 0,
|
||||
`password` varchar(255) NOT NULL DEFAULT '',
|
||||
`language` varchar(16) NOT NULL DEFAULT ''
|
||||
`language` varchar(16) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE search
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `search` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`term` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`),
|
||||
INDEX `term` (`term`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
|
@ -837,10 +911,11 @@ CREATE TABLE IF NOT EXISTS `search` (
|
|||
-- TABLE session
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `session` (
|
||||
`id` bigint(20) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` bigint(20) unsigned NOT NULL auto_increment,
|
||||
`sid` varchar(255) NOT NULL DEFAULT '',
|
||||
`data` text NOT NULL,
|
||||
`expire` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `sid` (`sid`),
|
||||
INDEX `expire` (`expire`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
|
@ -849,12 +924,13 @@ CREATE TABLE IF NOT EXISTS `session` (
|
|||
-- TABLE sign
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `sign` (
|
||||
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`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`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
|
@ -863,12 +939,13 @@ CREATE TABLE IF NOT EXISTS `sign` (
|
|||
-- TABLE spam
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `spam` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`spam` int(11) NOT NULL DEFAULT 0,
|
||||
`ham` int(11) NOT NULL DEFAULT 0,
|
||||
`term` varchar(255) NOT NULL DEFAULT '',
|
||||
`date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `uid` (`uid`),
|
||||
INDEX `spam` (`spam`),
|
||||
INDEX `ham` (`ham`),
|
||||
|
|
@ -879,7 +956,7 @@ CREATE TABLE IF NOT EXISTS `spam` (
|
|||
-- TABLE term
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `term` (
|
||||
`tid` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
|
||||
`tid` int(10) unsigned NOT NULL auto_increment,
|
||||
`oid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`otype` tinyint(3) unsigned NOT NULL DEFAULT 0,
|
||||
`type` tinyint(3) unsigned NOT NULL DEFAULT 0,
|
||||
|
|
@ -891,6 +968,7 @@ CREATE TABLE IF NOT EXISTS `term` (
|
|||
`global` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`aid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`tid`),
|
||||
INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`),
|
||||
INDEX `uid_term_tid` (`uid`,`term`,`tid`),
|
||||
INDEX `type_term` (`type`,`term`),
|
||||
|
|
@ -903,9 +981,10 @@ CREATE TABLE IF NOT EXISTS `term` (
|
|||
-- TABLE thread
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `thread` (
|
||||
`iid` int(10) unsigned NOT NULL DEFAULT 0 PRIMARY KEY,
|
||||
`iid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`uid` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`contact-id` int(11) unsigned NOT NULL DEFAULT 0,
|
||||
`gcontact-id` int(11) unsigned NOT NULL DEFAULT 0,
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`edited` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`commented` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
|
|
@ -926,12 +1005,15 @@ CREATE TABLE IF NOT EXISTS `thread` (
|
|||
`forum_mode` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`mention` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`network` varchar(32) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY(`iid`),
|
||||
INDEX `created` (`created`),
|
||||
INDEX `commented` (`commented`),
|
||||
INDEX `uid_network_commented` (`uid`,`network`,`commented`),
|
||||
INDEX `uid_network_created` (`uid`,`network`,`created`),
|
||||
INDEX `uid_contactid_commented` (`uid`,`contact-id`,`commented`),
|
||||
INDEX `uid_contactid_created` (`uid`,`contact-id`,`created`),
|
||||
INDEX `uid_gcontactid_commented` (`uid`,`gcontact-id`,`commented`),
|
||||
INDEX `uid_gcontactid_created` (`uid`,`gcontact-id`,`created`),
|
||||
INDEX `wall_private_received` (`wall`,`private`,`received`),
|
||||
INDEX `uid_created` (`uid`,`created`),
|
||||
INDEX `uid_commented` (`uid`,`commented`)
|
||||
|
|
@ -941,33 +1023,20 @@ CREATE TABLE IF NOT EXISTS `thread` (
|
|||
-- TABLE tokens
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `tokens` (
|
||||
`id` varchar(40) NOT NULL PRIMARY KEY,
|
||||
`id` varchar(40) NOT NULL,
|
||||
`secret` text NOT NULL,
|
||||
`client_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`expires` int(11) NOT NULL DEFAULT 0,
|
||||
`scope` varchar(200) NOT NULL DEFAULT '',
|
||||
`uid` int(11) NOT NULL DEFAULT 0
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE unique_contacts
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `unique_contacts` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`url` varchar(255) NOT NULL DEFAULT '',
|
||||
`nick` varchar(255) NOT NULL DEFAULT '',
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`avatar` varchar(255) NOT NULL DEFAULT '',
|
||||
`location` varchar(255) NOT NULL DEFAULT '',
|
||||
`about` text NOT NULL,
|
||||
INDEX `url` (`url`)
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- TABLE user
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`uid` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`uid` int(11) NOT NULL auto_increment,
|
||||
`guid` varchar(64) NOT NULL DEFAULT '',
|
||||
`username` varchar(255) NOT NULL DEFAULT '',
|
||||
`password` varchar(255) NOT NULL DEFAULT '',
|
||||
|
|
@ -1009,6 +1078,7 @@ CREATE TABLE IF NOT EXISTS `user` (
|
|||
`deny_cid` mediumtext NOT NULL,
|
||||
`deny_gid` mediumtext NOT NULL,
|
||||
`openidserver` text NOT NULL,
|
||||
PRIMARY KEY(`uid`),
|
||||
INDEX `nickname` (`nickname`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -1016,8 +1086,9 @@ CREATE TABLE IF NOT EXISTS `user` (
|
|||
-- TABLE userd
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `userd` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`username` varchar(255) NOT NULL,
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `username` (`username`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
@ -1025,12 +1096,13 @@ CREATE TABLE IF NOT EXISTS `userd` (
|
|||
-- TABLE workerqueue
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `workerqueue` (
|
||||
`id` int(11) NOT NULL auto_increment PRIMARY KEY,
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`parameter` text NOT NULL,
|
||||
`priority` tinyint(3) unsigned NOT NULL DEFAULT 0,
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`pid` int(11) NOT NULL DEFAULT 0,
|
||||
`executed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY(`id`),
|
||||
INDEX `created` (`created`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,12 @@ Posting to Community forums
|
|||
|
||||
If you are a member of a community forum, you may post to the forum by including an @-tag in the post mentioning the forum.
|
||||
For example @bicycle would send my post to all members of the group "bicycle" in addition to the normal recipients.
|
||||
If your post is private you must also explicitly include the group in the post permissions (to allow the forum "contact" to see the post) **and** mention it in a tag (which redistributes the post to the forum members).
|
||||
If you mention a forum (you are a member of) in a new posting, the posting will be distributed to all members of the forum, regardless of your privacy settings for the posting.
|
||||
Also, if the forum is a public forum, your posting will be public for the all internet users.
|
||||
If your post is private you must also explicitly include the group in the post permissions (to allow the forum "contact" to see the post) **and** mention it in a tag (which redistributes the post to the forum members).
|
||||
Posting privately to a public forum, will result in your posting being displayed on the forum wall, but not on yours.
|
||||
|
||||
You may also post to a community forum by posting a "wall-to-wall" post using secure cross-site authentication.
|
||||
You may also post to a community forum by posting a "wall-to-wall" post using secure cross-site authentication.
|
||||
|
||||
Comments which are relayed to community forums will be relayed back to the original post creator.
|
||||
Mentioning the forum with an @-tag in a comment does not relay the message, as distribution is controlled entirely by the original post creator.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
101
doc/Plugins.md
101
doc/Plugins.md
|
|
@ -1,5 +1,7 @@
|
|||
Friendica Addon/Plugin development
|
||||
==========================
|
||||
==============
|
||||
|
||||
* [Home](help)
|
||||
|
||||
Please see the sample addon 'randplace' for a working example of using some of these features.
|
||||
Addons work by intercepting event hooks - which must be registered.
|
||||
|
|
@ -16,12 +18,12 @@ Future extensions may provide for "setup" amd "remove".
|
|||
|
||||
Plugins should contain a comment block with the four following parameters:
|
||||
|
||||
/*
|
||||
* Name: My Great Plugin
|
||||
* Description: This is what my plugin does. It's really cool
|
||||
* Version: 1.0
|
||||
* Author: John Q. Public <john@myfriendicasite.com>
|
||||
*/
|
||||
/*
|
||||
* Name: My Great Plugin
|
||||
* Description: This is what my plugin does. It's really cool.
|
||||
* Version: 1.0
|
||||
* Author: John Q. Public <john@myfriendicasite.com>
|
||||
*/
|
||||
|
||||
Register your plugin hooks during installation.
|
||||
|
||||
|
|
@ -45,7 +47,7 @@ Your hook callback functions will be called with at least one and possibly two a
|
|||
|
||||
If you wish to make changes to the calling data, you must declare them as reference variables (with '&') during function declaration.
|
||||
|
||||
###$a
|
||||
#### $a
|
||||
$a is the Friendica 'App' class.
|
||||
It contains a wealth of information about the current state of Friendica:
|
||||
|
||||
|
|
@ -56,13 +58,13 @@ It contains a wealth of information about the current state of Friendica:
|
|||
|
||||
It is recommeded you call this '$a' to match its usage elsewhere.
|
||||
|
||||
###$b
|
||||
#### $b
|
||||
$b can be called anything you like.
|
||||
This is information specific to the hook currently being processed, and generally contains information that is being immediately processed or acted on that you can use, display, or alter.
|
||||
Remember to declare it with '&' if you wish to alter it.
|
||||
|
||||
Modules
|
||||
--------
|
||||
---
|
||||
|
||||
Plugins/addons may also act as "modules" and intercept all page requests for a given URL path.
|
||||
In order for a plugin to act as a module it needs to define a function "plugin_name_module()" which takes no arguments and needs not do anything.
|
||||
|
|
@ -72,15 +74,15 @@ These are parsed into an array $a->argv, with a corresponding $a->argc indicatin
|
|||
So http://my.web.site/plugin/arg1/arg2 would look for a module named "plugin" and pass its module functions the $a App structure (which is available to many components).
|
||||
This will include:
|
||||
|
||||
$a->argc = 3
|
||||
$a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
|
||||
$a->argc = 3
|
||||
$a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
|
||||
|
||||
Your module functions will often contain the function plugin_name_content(&$a), which defines and returns the page body content.
|
||||
They may also contain plugin_name_post(&$a) which is called before the _content function and typically handles the results of POST forms.
|
||||
You may also have plugin_name_init(&$a) which is called very early on and often does module initialisation.
|
||||
|
||||
Templates
|
||||
----------
|
||||
---
|
||||
|
||||
If your plugin needs some template, you can use the Friendica template system.
|
||||
Friendica uses [smarty3](http://www.smarty.net/) as a template engine.
|
||||
|
|
@ -104,140 +106,140 @@ See also the wiki page [Quick Template Guide](https://github.com/friendica/frien
|
|||
Current hooks
|
||||
-------------
|
||||
|
||||
###'authenticate'
|
||||
### 'authenticate'
|
||||
'authenticate' is called when a user attempts to login.
|
||||
$b is an array containing:
|
||||
|
||||
'username' => the supplied username
|
||||
'password' => the supplied password
|
||||
'username' => the supplied username
|
||||
'password' => the supplied password
|
||||
'authenticated' => set this to non-zero to authenticate the user.
|
||||
'user_record' => successful authentication must also return a valid user record from the database
|
||||
|
||||
###'logged_in'
|
||||
### 'logged_in'
|
||||
'logged_in' is called after a user has successfully logged in.
|
||||
$b contains the $a->user array.
|
||||
|
||||
###'display_item'
|
||||
### 'display_item'
|
||||
'display_item' is called when formatting a post for display.
|
||||
$b is an array:
|
||||
|
||||
'item' => The item (array) details pulled from the database
|
||||
'output' => the (string) HTML representation of this item prior to adding it to the page
|
||||
|
||||
###'post_local'
|
||||
### 'post_local'
|
||||
* called when a status post or comment is entered on the local system
|
||||
* $b is the item array of the information to be stored in the database
|
||||
* Please note: body contents are bbcode - not HTML
|
||||
|
||||
###'post_local_end'
|
||||
### 'post_local_end'
|
||||
* called when a local status post or comment has been stored on the local system
|
||||
* $b is the item array of the information which has just been stored in the database
|
||||
* Please note: body contents are bbcode - not HTML
|
||||
|
||||
###'post_remote'
|
||||
### 'post_remote'
|
||||
* called when receiving a post from another source. This may also be used to post local activity or system generated messages.
|
||||
* $b is the item array of information to be stored in the database and the item body is bbcode.
|
||||
|
||||
###'settings_form'
|
||||
### 'settings_form'
|
||||
* called when generating the HTML for the user Settings page
|
||||
* $b is the (string) HTML of the settings page before the final '</form>' tag.
|
||||
|
||||
###'settings_post'
|
||||
### 'settings_post'
|
||||
* called when the Settings pages are submitted
|
||||
* $b is the $_POST array
|
||||
|
||||
###'plugin_settings'
|
||||
### 'plugin_settings'
|
||||
* called when generating the HTML for the addon settings page
|
||||
* $b is the (string) HTML of the addon settings page before the final '</form>' tag.
|
||||
|
||||
###'plugin_settings_post'
|
||||
### 'plugin_settings_post'
|
||||
* called when the Addon Settings pages are submitted
|
||||
* $b is the $_POST array
|
||||
|
||||
###'profile_post'
|
||||
### 'profile_post'
|
||||
* called when posting a profile page
|
||||
* $b is the $_POST array
|
||||
|
||||
###'profile_edit'
|
||||
### 'profile_edit'
|
||||
'profile_edit' is called prior to output of profile edit page.
|
||||
$b is an array containing:
|
||||
|
||||
'profile' => profile (array) record from the database
|
||||
'entry' => the (string) HTML of the generated entry
|
||||
|
||||
###'profile_advanced'
|
||||
### 'profile_advanced'
|
||||
* called when the HTML is generated for the 'Advanced profile', corresponding to the 'Profile' tab within a person's profile page
|
||||
* $b is the (string) HTML representation of the generated profile
|
||||
* The profile array details are in $a->profile.
|
||||
|
||||
###'directory_item'
|
||||
### 'directory_item'
|
||||
'directory_item' is called from the Directory page when formatting an item for display.
|
||||
$b is an array:
|
||||
|
||||
'contact' => contact (array) record for the person from the database
|
||||
'entry' => the (string) HTML of the generated entry
|
||||
|
||||
###'profile_sidebar_enter'
|
||||
### 'profile_sidebar_enter'
|
||||
* called prior to generating the sidebar "short" profile for a page
|
||||
* $b is the person's profile array
|
||||
|
||||
###'profile_sidebar'
|
||||
### 'profile_sidebar'
|
||||
'profile_sidebar is called when generating the sidebar "short" profile for a page.
|
||||
$b is an array:
|
||||
|
||||
'profile' => profile (array) record for the person from the database
|
||||
'entry' => the (string) HTML of the generated entry
|
||||
|
||||
###'contact_block_end'
|
||||
### 'contact_block_end'
|
||||
is called when formatting the block of contacts/friends on a profile sidebar has completed.
|
||||
$b is an array:
|
||||
|
||||
'contacts' => array of contacts
|
||||
'output' => the (string) generated HTML of the contact block
|
||||
|
||||
###'bbcode'
|
||||
### 'bbcode'
|
||||
* called during conversion of bbcode to html
|
||||
* $b is a string converted text
|
||||
|
||||
###'html2bbcode'
|
||||
### 'html2bbcode'
|
||||
* called during conversion of html to bbcode (e.g. remote message posting)
|
||||
* $b is a string converted text
|
||||
|
||||
###'page_header'
|
||||
### 'page_header'
|
||||
* called after building the page navigation section
|
||||
* $b is a string HTML of nav region
|
||||
|
||||
###'personal_xrd'
|
||||
### 'personal_xrd'
|
||||
'personal_xrd' is called prior to output of personal XRD file.
|
||||
$b is an array:
|
||||
|
||||
'user' => the user record for the person
|
||||
'xml' => the complete XML to be output
|
||||
|
||||
###'home_content'
|
||||
### 'home_content'
|
||||
* called prior to output home page content, shown to unlogged users
|
||||
* $b is (string) HTML of section region
|
||||
|
||||
###'contact_edit'
|
||||
### 'contact_edit'
|
||||
is called when editing contact details on an individual from the Contacts page.
|
||||
$b is an array:
|
||||
|
||||
'contact' => contact record (array) of target contact
|
||||
'output' => the (string) generated HTML of the contact edit page
|
||||
|
||||
###'contact_edit_post'
|
||||
### 'contact_edit_post'
|
||||
* called when posting the contact edit page.
|
||||
* $b is the $_POST array
|
||||
|
||||
###'init_1'
|
||||
### 'init_1'
|
||||
* called just after DB has been opened and before session start
|
||||
* $b is not used or passed
|
||||
|
||||
###'page_end'
|
||||
### 'page_end'
|
||||
* called after HTML content functions have completed
|
||||
* $b is (string) HTML of content div
|
||||
|
||||
###'avatar_lookup'
|
||||
### 'avatar_lookup'
|
||||
'avatar_lookup' is called when looking up the avatar.
|
||||
$b is an array:
|
||||
|
||||
|
|
@ -245,11 +247,11 @@ $b is an array:
|
|||
'email' => email to look up the avatar for
|
||||
'url' => the (string) generated URL of the avatar
|
||||
|
||||
###'emailer_send_prepare'
|
||||
### 'emailer_send_prepare'
|
||||
'emailer_send_prepare' called from Emailer::send() before building the mime message.
|
||||
$b is an array, params to Emailer::send()
|
||||
|
||||
'fromName' => name of the sender
|
||||
'fromName' => name of the sender
|
||||
'fromEmail' => email fo the sender
|
||||
'replyTo' => replyTo address to direct responses
|
||||
'toEmail' => destination email address
|
||||
|
|
@ -258,20 +260,20 @@ $b is an array, params to Emailer::send()
|
|||
'textVersion' => text only version of the message
|
||||
'additionalMailHeader' => additions to the smtp mail header
|
||||
|
||||
###'emailer_send'
|
||||
### 'emailer_send'
|
||||
is called before calling PHP's mail().
|
||||
$b is an array, params to mail()
|
||||
|
||||
'to'
|
||||
'subject'
|
||||
'to'
|
||||
'subject'
|
||||
'body'
|
||||
'headers'
|
||||
|
||||
###'nav_info'
|
||||
### 'nav_info'
|
||||
is called after the navigational menu is build in include/nav.php.
|
||||
$b is an array containing $nav from nav.php.
|
||||
|
||||
###'template_vars'
|
||||
### 'template_vars'
|
||||
is called before vars are passed to the template engine to render the page.
|
||||
The registered function can add,change or remove variables passed to template.
|
||||
$b is an array with:
|
||||
|
|
@ -463,4 +465,3 @@ mod/cb.php: call_hooks('cb_afterpost');
|
|||
mod/cb.php: call_hooks('cb_content', $o);
|
||||
|
||||
mod/directory.php: call_hooks('directory_item', $arr);
|
||||
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ If you run your own server, upload the files and check out the Mozilla wiki link
|
|||
Let's encrypt
|
||||
---
|
||||
|
||||
If you run your own server and you control your name server, the "Let's encrypt" initiative might become an interesting alternative.
|
||||
Their offer is not ready, yet.
|
||||
If you run your own server, the "Let's encrypt" initiative might become an interesting alternative.
|
||||
Their offer is in public beta right now.
|
||||
Check out [their website](https://letsencrypt.org/) for status updates.
|
||||
|
||||
Web server settings
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ You are not required to provide the year.
|
|||
System settings
|
||||
---
|
||||
|
||||
**Settings should be done in the admin panel** (/admin).
|
||||
Those settings found in the database, will always override the settings added to the ``.htconfig.php`` file.
|
||||
|
||||
###Language
|
||||
|
||||
Please see util/README for information on creating language translations.
|
||||
|
|
@ -219,6 +222,8 @@ LOGGER_DEBUG will show a good deal of information about system activity but will
|
|||
You may also select LOGGER_ALL but due to the volume of information we recommend only enabling this when you are tracking down a specific problem.
|
||||
Other log levels are possible but are not being used at the present time.
|
||||
|
||||
Please be aware that turning on the logging can fill up the disk space on your server relatively quick.
|
||||
You should take preventions with e.g. [logrotate](https://en.wikipedia.org/wiki/Log_rotation) or similar tools.
|
||||
|
||||
###PHP error logging
|
||||
|
||||
|
|
@ -237,4 +242,6 @@ The vast majority of issues reported at these levels are completely harmless.
|
|||
Please report to the developers any errors you encounter in the logs using the recommended settings above.
|
||||
They generally indicate issues which need to be resolved.
|
||||
|
||||
If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred.
|
||||
If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred.
|
||||
|
||||
*Note*: PHP logging cannot be activated from the admin panel but has to be configured from the ``.htconfig.php`` file.
|
||||
|
|
|
|||
|
|
@ -21,11 +21,22 @@ You can tag a person on a different network or one that is **not in your social
|
|||
|
||||
* @mike@macgirvin.com - This is called a "remote mention" and can only be an email-style locator, not a web URL.
|
||||
|
||||
Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts. Please note that Friendica blocks incoming "mentions" from people with no relationship to you. This is a spam prevention measure.
|
||||
Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts.
|
||||
Friendica blocks incoming “mentions” from people with no relationship to you.
|
||||
The exception is an ongiong conversation started from a contact of both you and the 3rd person or a conversation in a forum where you are a member of.
|
||||
This is a spam prevention measure.
|
||||
|
||||
Remote mentions are delivered using the OStatus protocol. This protocol is used by Friendica and GNU Social and several other systems, but is not currently implemented in Diaspora.
|
||||
Remote mentions are delivered using the OStatus protocol.
|
||||
This protocol is used by Friendica and GNU Social and several other systems, but is not currently implemented in Diaspora.
|
||||
As the OStatus protocol allows this Friendica user can be @-mentioned by users from platforms using this protocol in conversations if the "Enable OStatus support" is activated on the Friendica node.
|
||||
These @-mentions wont be blocked, even if there is no relationship between the sender and the receiver of the message.
|
||||
|
||||
Friendica makes no distinction between people and groups for the purpose of tagging. (Some other networks use !group to indicate a group.)
|
||||
Friendica makes no distinction between people and forums for the purpose of tagging.
|
||||
(Some other networks use !forum to indicate a forum.)
|
||||
|
||||
If you sort your contacts into groups, you cannot @-mention these groups.
|
||||
But you can select the group in the access control when creating a new posting, to allow (or disallow) a certain group of people to see the posting.
|
||||
See [Groups and Privacy](help/Groups-and-Privacy) for more details about grouping your contacts.
|
||||
|
||||
**Topical Tags**
|
||||
|
||||
|
|
|
|||
436
doc/api.md
436
doc/api.md
|
|
@ -7,6 +7,21 @@ Please refer to the linked documentation for further information.
|
|||
## Implemented API calls
|
||||
|
||||
### General
|
||||
#### HTTP Method
|
||||
|
||||
API endpoints can restrict the method used to request them.
|
||||
Using an invalid method results in HTTP error 405 "Method Not Allowed".
|
||||
|
||||
In this document, the required method is listed after the endpoint name. "*" means every method can be used.
|
||||
|
||||
#### Auth
|
||||
|
||||
Friendica supports basic http auth and OAuth 1 to authenticate the user to the api.
|
||||
|
||||
OAuth settings can be added by the user in web UI under /settings/oauth/
|
||||
|
||||
In this document, endpoints which requires auth are marked with "AUTH" after endpoint name
|
||||
|
||||
#### Unsupported parameters
|
||||
* cursor: Not implemented in GNU Social
|
||||
* trim_user: Not implemented in GNU Social
|
||||
|
|
@ -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/<verb>
|
||||
#### parameters
|
||||
* id: item id
|
||||
|
||||
Add or remove an activity from an item.
|
||||
'verb' can be one of:
|
||||
- like
|
||||
- dislike
|
||||
- attendyes
|
||||
- attendno
|
||||
- attendmaybe
|
||||
|
||||
To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike"
|
||||
Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes".
|
||||
Attend verbs should be used only with event-related items (there is no check at the moment)
|
||||
|
||||
#### Return values
|
||||
|
||||
On success:
|
||||
json
|
||||
```"ok"```
|
||||
|
||||
xml
|
||||
```<ok>true</ok>```
|
||||
|
||||
On error:
|
||||
HTTP 400 BadRequest
|
||||
|
||||
---
|
||||
### friendica/photo
|
||||
#### Parameters
|
||||
* photo_id: Resource id of a photo.
|
||||
* scale: (optional) scale value of the photo
|
||||
|
||||
Returns data of a picture with the given resource.
|
||||
If 'scale' isn't provided, returned data include full url to each scale of the photo.
|
||||
If 'scale' is set, returned data include image data base64 encoded.
|
||||
|
||||
possibile scale value are:
|
||||
0: original or max size by server settings
|
||||
1: image with or height at <= 640
|
||||
2: image with or height at <= 320
|
||||
3: thumbnail 160x160
|
||||
|
||||
4: Profile image at 175x175
|
||||
5: Profile image at 80x80
|
||||
6: Profile image at 48x48
|
||||
|
||||
An image used as profile image has only scale 4-6, other images only 0-3
|
||||
|
||||
#### Return values
|
||||
|
||||
json
|
||||
```
|
||||
{
|
||||
"id": "photo id"
|
||||
"created": "date(YYYY-MM-GG HH:MM:SS)",
|
||||
"edited": "date(YYYY-MM-GG HH:MM:SS)",
|
||||
"title": "photo title",
|
||||
"desc": "photo description",
|
||||
"album": "album name",
|
||||
"filename": "original file name",
|
||||
"type": "mime type",
|
||||
"height": "number",
|
||||
"width": "number",
|
||||
"profile": "1 if is profile photo",
|
||||
"link": {
|
||||
"<scale>": "url to image"
|
||||
...
|
||||
},
|
||||
// if 'scale' is set
|
||||
"datasize": "size in byte",
|
||||
"data": "base64 encoded image data"
|
||||
}
|
||||
```
|
||||
|
||||
xml
|
||||
```
|
||||
<photo>
|
||||
<id>photo id</id>
|
||||
<created>date(YYYY-MM-GG HH:MM:SS)</created>
|
||||
<edited>date(YYYY-MM-GG HH:MM:SS)</edited>
|
||||
<title>photo title</title>
|
||||
<desc>photo description</desc>
|
||||
<album>album name</album>
|
||||
<filename>original file name</filename>
|
||||
<type>mime type</type>
|
||||
<height>number</height>
|
||||
<width>number</width>
|
||||
<profile>1 if is profile photo</profile>
|
||||
<links type="array">
|
||||
<link type="mime type" scale="scale number" href="image url"/>
|
||||
...
|
||||
</links>
|
||||
</photo>
|
||||
```
|
||||
|
||||
---
|
||||
### friendica/photos/list
|
||||
|
||||
Returns a list of all photo resources of the logged in user.
|
||||
|
||||
#### Return values
|
||||
|
||||
json
|
||||
```
|
||||
[
|
||||
{
|
||||
id: "resource_id",
|
||||
album: "album name",
|
||||
filename: "original file name",
|
||||
type: "image mime type",
|
||||
thumb: "url to thumb sized image"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
xml
|
||||
```
|
||||
<photos type="array">
|
||||
<photo id="resource_id"
|
||||
album="album name"
|
||||
filename="original file name"
|
||||
type="image mime type">
|
||||
"url to thumb sized image"
|
||||
</photo>
|
||||
...
|
||||
</photos>
|
||||
```
|
||||
|
||||
---
|
||||
### friends/ids
|
||||
### friends/ids (*; AUTH)
|
||||
#### Parameters
|
||||
* stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)
|
||||
|
||||
|
|
@ -315,15 +200,15 @@ xml
|
|||
Friendica doesn't allow showing friends of other users.
|
||||
|
||||
---
|
||||
### help/test
|
||||
### help/test (*)
|
||||
|
||||
---
|
||||
### media/upload
|
||||
### media/upload (POST,PUT; AUTH)
|
||||
#### Parameters
|
||||
* media: image data
|
||||
|
||||
---
|
||||
### oauth/request_token
|
||||
### oauth/request_token (*)
|
||||
#### Parameters
|
||||
* oauth_callback
|
||||
|
||||
|
|
@ -331,7 +216,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* x_auth_access_type
|
||||
|
||||
---
|
||||
### oauth/access_token
|
||||
### oauth/access_token (*)
|
||||
#### Parameters
|
||||
* oauth_verifier
|
||||
|
||||
|
|
@ -341,7 +226,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* x_auth_mode
|
||||
|
||||
---
|
||||
### statuses/destroy
|
||||
### statuses/destroy (POST,DELETE; AUTH)
|
||||
#### Parameters
|
||||
* id: message number
|
||||
* include_entities: "true" shows entities for pictures and links (Default: false)
|
||||
|
|
@ -350,15 +235,21 @@ Friendica doesn't allow showing friends of other users.
|
|||
* trim_user
|
||||
|
||||
---
|
||||
### statuses/followers
|
||||
### statuses/followers (*; AUTH)
|
||||
|
||||
#### Parameters
|
||||
|
||||
* include_entities: "true" shows entities for pictures and links (Default: false)
|
||||
|
||||
---
|
||||
### statuses/friends
|
||||
### statuses/friends (*; AUTH)
|
||||
|
||||
#### Parameters
|
||||
|
||||
* include_entities: "true" shows entities for pictures and links (Default: false)
|
||||
|
||||
---
|
||||
### statuses/friends_timeline
|
||||
### statuses/friends_timeline (*; AUTH)
|
||||
#### Parameters
|
||||
* count: Items per page (default: 20)
|
||||
* page: page number
|
||||
|
|
@ -374,7 +265,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* contributor_details
|
||||
|
||||
---
|
||||
### statuses/home_timeline
|
||||
### statuses/home_timeline (*; AUTH)
|
||||
#### Parameters
|
||||
* count: Items per page (default: 20)
|
||||
* page: page number
|
||||
|
|
@ -390,7 +281,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* contributor_details
|
||||
|
||||
---
|
||||
### statuses/mentions
|
||||
### statuses/mentions (*; AUTH)
|
||||
#### Parameters
|
||||
* count: Items per page (default: 20)
|
||||
* page: page number
|
||||
|
|
@ -404,7 +295,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* contributor_details
|
||||
|
||||
---
|
||||
### statuses/public_timeline
|
||||
### statuses/public_timeline (*; AUTH)
|
||||
#### Parameters
|
||||
* count: Items per page (default: 20)
|
||||
* page: page number
|
||||
|
|
@ -418,7 +309,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* trim_user
|
||||
|
||||
---
|
||||
### statuses/replies
|
||||
### statuses/replies (*; AUTH)
|
||||
#### Parameters
|
||||
* count: Items per page (default: 20)
|
||||
* page: page number
|
||||
|
|
@ -432,7 +323,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* contributor_details
|
||||
|
||||
---
|
||||
### statuses/retweet
|
||||
### statuses/retweet (POST,PUT; AUTH)
|
||||
#### Parameters
|
||||
* id: message number
|
||||
* include_entities: "true" shows entities for pictures and links (Default: false)
|
||||
|
|
@ -441,7 +332,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* trim_user
|
||||
|
||||
---
|
||||
### statuses/show
|
||||
### statuses/show (*; AUTH)
|
||||
#### Parameters
|
||||
* id: message number
|
||||
* conversation: if set to "1" show all messages of the conversation with the given id
|
||||
|
|
@ -476,7 +367,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
* display_coordinates
|
||||
|
||||
---
|
||||
### statuses/user_timeline
|
||||
### statuses/user_timeline (*; AUTH)
|
||||
#### Parameters
|
||||
* user_id: id of the user
|
||||
* screen_name: screen name (for technical reasons, this value is not unique!)
|
||||
|
|
@ -489,15 +380,16 @@ Friendica doesn't allow showing friends of other users.
|
|||
* include_entities: "true" shows entities for pictures and links (Default: false)
|
||||
|
||||
#### Unsupported parameters
|
||||
|
||||
* include_rts
|
||||
* trim_user
|
||||
* contributor_details
|
||||
|
||||
---
|
||||
### statusnet/config
|
||||
### statusnet/config (*)
|
||||
|
||||
---
|
||||
### statusnet/version
|
||||
### statusnet/version (*)
|
||||
|
||||
#### Unsupported parameters
|
||||
* user_id
|
||||
|
|
@ -507,7 +399,7 @@ Friendica doesn't allow showing friends of other users.
|
|||
Friendica doesn't allow showing followers of other users.
|
||||
|
||||
---
|
||||
### users/search
|
||||
### users/search (*)
|
||||
#### Parameters
|
||||
* q: name of the user
|
||||
|
||||
|
|
@ -517,7 +409,7 @@ Friendica doesn't allow showing followers of other users.
|
|||
* include_entities
|
||||
|
||||
---
|
||||
### users/show
|
||||
### users/show (*)
|
||||
#### Parameters
|
||||
* user_id: id of the user
|
||||
* screen_name: screen name (for technical reasons, this value is not unique!)
|
||||
|
|
@ -533,8 +425,39 @@ Friendica doesn't allow showing friends of other users.
|
|||
|
||||
## Implemented API calls (not compatible with other APIs)
|
||||
|
||||
|
||||
---
|
||||
### friendica/group_show
|
||||
### friendica/activity/<verb>
|
||||
#### parameters
|
||||
* id: item id
|
||||
|
||||
Add or remove an activity from an item.
|
||||
'verb' can be one of:
|
||||
|
||||
- like
|
||||
- dislike
|
||||
- attendyes
|
||||
- attendno
|
||||
- attendmaybe
|
||||
|
||||
To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike"
|
||||
Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes".
|
||||
Attend verbs should be used only with event-related items (there is no check at the moment)
|
||||
|
||||
#### Return values
|
||||
|
||||
On success:
|
||||
json
|
||||
```"ok"```
|
||||
|
||||
xml
|
||||
```<ok>true</ok>```
|
||||
|
||||
On error:
|
||||
HTTP 400 BadRequest
|
||||
|
||||
---
|
||||
### friendica/group_show (*; AUTH)
|
||||
Return all or a specified group of the user with the containing contacts as array.
|
||||
|
||||
#### Parameters
|
||||
|
|
@ -542,22 +465,23 @@ Return all or a specified group of the user with the containing contacts as arra
|
|||
|
||||
#### Return values
|
||||
Array of:
|
||||
|
||||
* name: name of the group
|
||||
* gid: id of the group
|
||||
* user: array of group members (return from api_get_user() function for each member)
|
||||
|
||||
|
||||
---
|
||||
### friendica/group_delete
|
||||
### friendica/group_delete (POST,DELETE; AUTH)
|
||||
delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted.
|
||||
|
||||
---
|
||||
### Parameters
|
||||
#### Parameters
|
||||
* gid: id of the group to be deleted
|
||||
* name: name of the group to be deleted
|
||||
|
||||
#### Return values
|
||||
Array of:
|
||||
|
||||
* success: true if successfully deleted
|
||||
* gid: gid of the deleted group
|
||||
* name: name of the deleted group
|
||||
|
|
@ -566,19 +490,22 @@ Array of:
|
|||
|
||||
|
||||
---
|
||||
### friendica/group_create
|
||||
### friendica/group_create (POST,PUT; AUTH)
|
||||
Create the group with the posted array of contacts as members.
|
||||
|
||||
#### Parameters
|
||||
* name: name of the group to be created
|
||||
|
||||
#### POST data
|
||||
JSON data as Array like the result of „users/group_show“:
|
||||
JSON data as Array like the result of "users/group_show":
|
||||
|
||||
* gid
|
||||
* name
|
||||
* array of users
|
||||
|
||||
#### Return values
|
||||
Array of:
|
||||
|
||||
* success: true if successfully created or reactivated
|
||||
* gid: gid of the created group
|
||||
* name: name of the created group
|
||||
|
|
@ -587,26 +514,175 @@ Array of:
|
|||
|
||||
|
||||
---
|
||||
### friendica/group_update
|
||||
### friendica/group_update (POST)
|
||||
Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted).
|
||||
|
||||
#### Parameters
|
||||
* gid: id of the group to be changed
|
||||
* name: name of the group to be changed
|
||||
|
||||
#### POST data
|
||||
JSON data as array like the result of „users/group_show“:
|
||||
|
||||
* gid
|
||||
* name
|
||||
* array of users
|
||||
|
||||
#### Return values
|
||||
Array of:
|
||||
|
||||
* success: true if successfully updated
|
||||
* gid: gid of the changed group
|
||||
* name: name of the changed group
|
||||
* status: „missing user“ | „ok“
|
||||
* wrong users: array of users, which were not available in the contact table
|
||||
|
||||
|
||||
|
||||
---
|
||||
### friendica/notifications (GET)
|
||||
Return last 50 notification for current user, ordered by date with unseen item on top
|
||||
|
||||
#### Parameters
|
||||
none
|
||||
|
||||
#### Return values
|
||||
Array of:
|
||||
|
||||
* id: id of the note
|
||||
* type: type of notification as int (see NOTIFY_* constants in boot.php)
|
||||
* name: full name of the contact subject of the note
|
||||
* url: contact's profile url
|
||||
* photo: contact's profile photo
|
||||
* date: datetime string of the note
|
||||
* timestamp: timestamp of the node
|
||||
* date_rel: relative date of the note (eg. "1 hour ago")
|
||||
* msg: note message in bbcode
|
||||
* msg_html: note message in html
|
||||
* msg_plain: note message in plain text
|
||||
* link: link to note
|
||||
* seen: seen state: 0 or 1
|
||||
|
||||
|
||||
---
|
||||
### friendica/notifications/seen (POST)
|
||||
Set note as seen, returns item object if possible
|
||||
|
||||
#### Parameters
|
||||
id: id of the note to set seen
|
||||
|
||||
#### Return values
|
||||
If the note is linked to an item, the item is returned, just like one of the "statuses/*_timeline" api.
|
||||
|
||||
If the note is not linked to an item, a success status is returned:
|
||||
|
||||
* "success" (json) | "<status>success</status>" (xml)
|
||||
|
||||
|
||||
---
|
||||
### friendica/photo (*; AUTH)
|
||||
#### Parameters
|
||||
* photo_id: Resource id of a photo.
|
||||
* scale: (optional) scale value of the photo
|
||||
|
||||
Returns data of a picture with the given resource.
|
||||
If 'scale' isn't provided, returned data include full url to each scale of the photo.
|
||||
If 'scale' is set, returned data include image data base64 encoded.
|
||||
|
||||
possibile scale value are:
|
||||
|
||||
* 0: original or max size by server settings
|
||||
* 1: image with or height at <= 640
|
||||
* 2: image with or height at <= 320
|
||||
* 3: thumbnail 160x160
|
||||
* 4: Profile image at 175x175
|
||||
* 5: Profile image at 80x80
|
||||
* 6: Profile image at 48x48
|
||||
|
||||
An image used as profile image has only scale 4-6, other images only 0-3
|
||||
|
||||
#### Return values
|
||||
|
||||
json
|
||||
```
|
||||
{
|
||||
"id": "photo id"
|
||||
"created": "date(YYYY-MM-GG HH:MM:SS)",
|
||||
"edited": "date(YYYY-MM-GG HH:MM:SS)",
|
||||
"title": "photo title",
|
||||
"desc": "photo description",
|
||||
"album": "album name",
|
||||
"filename": "original file name",
|
||||
"type": "mime type",
|
||||
"height": "number",
|
||||
"width": "number",
|
||||
"profile": "1 if is profile photo",
|
||||
"link": {
|
||||
"<scale>": "url to image"
|
||||
...
|
||||
},
|
||||
// if 'scale' is set
|
||||
"datasize": "size in byte",
|
||||
"data": "base64 encoded image data"
|
||||
}
|
||||
```
|
||||
|
||||
xml
|
||||
```
|
||||
<photo>
|
||||
<id>photo id</id>
|
||||
<created>date(YYYY-MM-GG HH:MM:SS)</created>
|
||||
<edited>date(YYYY-MM-GG HH:MM:SS)</edited>
|
||||
<title>photo title</title>
|
||||
<desc>photo description</desc>
|
||||
<album>album name</album>
|
||||
<filename>original file name</filename>
|
||||
<type>mime type</type>
|
||||
<height>number</height>
|
||||
<width>number</width>
|
||||
<profile>1 if is profile photo</profile>
|
||||
<links type="array">
|
||||
<link type="mime type" scale="scale number" href="image url"/>
|
||||
...
|
||||
</links>
|
||||
</photo>
|
||||
```
|
||||
|
||||
---
|
||||
### friendica/photos/list (*; AUTH)
|
||||
|
||||
Returns a list of all photo resources of the logged in user.
|
||||
|
||||
#### Return values
|
||||
|
||||
json
|
||||
```
|
||||
[
|
||||
{
|
||||
id: "resource_id",
|
||||
album: "album name",
|
||||
filename: "original file name",
|
||||
type: "image mime type",
|
||||
thumb: "url to thumb sized image"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
xml
|
||||
```
|
||||
<photos type="array">
|
||||
<photo id="resource_id"
|
||||
album="album name"
|
||||
filename="original file name"
|
||||
type="image mime type">
|
||||
"url to thumb sized image"
|
||||
</photo>
|
||||
...
|
||||
</photos>
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
## Not Implemented API calls
|
||||
The following API calls are implemented in GNU Social but not in Friendica: (incomplete)
|
||||
|
|
@ -702,13 +778,13 @@ The following API calls from the Twitter API aren't implemented neither in Frien
|
|||
### BASH / cURL
|
||||
Betamax has documentated some example API usage from a [bash script](https://en.wikipedia.org/wiki/Bash_(Unix_shell) employing [curl](https://en.wikipedia.org/wiki/CURL) (see [his posting](https://betamax65.de/display/betamax65/43539)).
|
||||
|
||||
/usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"
|
||||
/usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"
|
||||
|
||||
### Python
|
||||
The [RSStoFriedika](https://github.com/pafcu/RSStoFriendika) code can be used as an example of how to use the API with python. The lines for posting are located at [line 21](https://github.com/pafcu/RSStoFriendika/blob/master/RSStoFriendika.py#L21) and following.
|
||||
|
||||
def tweet(server, message, group_allow=None):
|
||||
url = server + '/api/statuses/update'
|
||||
urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))
|
||||
def tweet(server, message, group_allow=None):
|
||||
url = server + '/api/statuses/update'
|
||||
urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))
|
||||
|
||||
There is also a [module for python 3](https://bitbucket.org/tobiasd/python-friendica) for using the API.
|
||||
|
|
|
|||
209
doc/autoloader.md
Normal file
209
doc/autoloader.md
Normal file
|
|
@ -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
|
||||
<?php
|
||||
namespace \Friendica;
|
||||
|
||||
class ItemsManager {
|
||||
public function getAll() { ... }
|
||||
public function getByID($id) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
The class "ItemsManager" has been declared in "Friendica" namespace.
|
||||
Namespaces are useful to keep things separated and avoid names clash (could be that a library you want to use defines a class named "ItemsManager", but as long as is in another namespace, you don't have any problem)
|
||||
|
||||
If we were using composer, we had configured it with path where to find the classes of "Friendica" namespace, and then the composer script will generate the autoloader machinery for us.
|
||||
As we don't use composer, we need check that the autoloader knows the Friendica namespace.
|
||||
So in "include/autoloader/autoload_psr4.php" there should be something like
|
||||
|
||||
```
|
||||
$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
|
||||
$baseDir = dirname($vendorDir);
|
||||
return array(
|
||||
"Friendica" => array($baseDir."/include");
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
That tells the autoloader code to look for files that defines classes in "Friendica" namespace under "include/" folder. (And btw, that's why the file has the same name as the class it defines.)
|
||||
|
||||
*note*: The structure of files in "include/autoloader/" has been copied from the code generated by composer, to ease the work of enable autoloader for external libraries under "library/"
|
||||
|
||||
Let's say now that you need to load some items in a view, maybe in a fictional "mod/network.php".
|
||||
Somewere at the start of the scripts, the autoloader was initialized. In Friendica is done at the top of "boot.php", with "require_once('include/autoloader.php');".
|
||||
|
||||
The code will be something like:
|
||||
|
||||
```
|
||||
file: mod/network.php
|
||||
<?php
|
||||
|
||||
function network_content(&$a) {
|
||||
$itemsmanager = new \Friendica\ItemsManager();
|
||||
$items = $itemsmanager->getAll();
|
||||
|
||||
// pass $items to template
|
||||
// return result
|
||||
}
|
||||
```
|
||||
|
||||
That's a quite simple example, but look: no "require()"!
|
||||
You need to use a class, you use the class and you don't need to do anything more.
|
||||
|
||||
Going further: now we have a bunch of "*Manager" classes that cause some code duplication, let's define a BaseManager class, where to move all code in common between all managers:
|
||||
|
||||
```
|
||||
file: include/BaseManager.php
|
||||
<?php
|
||||
namespace \Friendica;
|
||||
|
||||
class BaseManager {
|
||||
public function thatFunctionEveryManagerUses() { ... }
|
||||
}
|
||||
```
|
||||
|
||||
and then let's change the ItemsManager class to use this code
|
||||
|
||||
```
|
||||
file: include/ItemsManager.php
|
||||
<?php
|
||||
namespace \Friendica;
|
||||
|
||||
class ItemsManager extends BaseManager {
|
||||
public function getAll() { ... }
|
||||
public function getByID($id) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
The autoloader don't mind what you need the class for. You need a class, you get the class.
|
||||
It works with the "BaseManager" example here, it works when we need to call static methods on a class:
|
||||
|
||||
```
|
||||
file: include/dfrn.php
|
||||
<?php
|
||||
namespace \Friendica;
|
||||
|
||||
class dfrn {
|
||||
public static function mail($item, $owner) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
file: mod/mail.php
|
||||
<?php
|
||||
|
||||
mail_post($a){
|
||||
...
|
||||
\Friendica\dfrn::mail($item, $owner);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
If your code is in same namespace as the class you need, you don't need to prepend it:
|
||||
|
||||
```
|
||||
file: include/delivery.php
|
||||
<?php
|
||||
|
||||
namespace \Friendica;
|
||||
|
||||
// this is the same content of current include/delivery.php,
|
||||
// but has been declared to be in "Friendica" namespace
|
||||
|
||||
[...]
|
||||
switch($contact['network']) {
|
||||
|
||||
case NETWORK_DFRN:
|
||||
if ($mail) {
|
||||
$item['body'] = ...
|
||||
$atom = dfrn::mail($item, $owner);
|
||||
} elseif ($fsuggest) {
|
||||
$atom = dfrn::fsuggest($item, $owner);
|
||||
q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
|
||||
} elseif ($relocate)
|
||||
$atom = dfrn::relocate($owner, $uid);
|
||||
[...]
|
||||
```
|
||||
|
||||
This is real "include/delivery.php" unchanged, but as the code is declared to be in "Friendica" namespace, you don't need to write it when you need to use the "dfrn" class.
|
||||
But if you want to use classes from another library, you need to use the full namespace, e.g.
|
||||
|
||||
```
|
||||
<?php
|
||||
namespace \Frienidca;
|
||||
|
||||
class Diaspora {
|
||||
public function md2bbcode() {
|
||||
$html = \Michelf\MarkdownExtra::defaultTransform($text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
if you use that class in many places of the code and you don't want to write the full path to the class everytime, you can use the "use" php keyword
|
||||
|
||||
```
|
||||
<?php
|
||||
namespace \Frienidca;
|
||||
|
||||
use \Michelf\MarkdownExtra;
|
||||
|
||||
class Diaspora {
|
||||
public function md2bbcode() {
|
||||
$html = MarkdownExtra::defaultTransform($text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that namespaces are like paths in filesystem, separated by "\", with the first "\" being the global scope.
|
||||
You can go more deep if you want to, like:
|
||||
|
||||
```
|
||||
<?php
|
||||
namespace \Friendica\Network;
|
||||
|
||||
class DFRN {
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
<?php
|
||||
namespace \Friendica\DBA;
|
||||
|
||||
class MySQL {
|
||||
}
|
||||
```
|
||||
|
||||
So you can think of namespaces as folders in a unix filesystem, with global scope as the root ("\").
|
||||
|
||||
|
|
@ -27,7 +27,6 @@ Database Tables
|
|||
| [group](help/database/db_group) | privacy groups, group info |
|
||||
| [group_member](help/database/db_group_member) | privacy groups, member info |
|
||||
| [gserver](help/database/db_gserver) | |
|
||||
| [guid](help/database/db_guid) | |
|
||||
| [hook](help/database/db_hook) | plugin hook registry |
|
||||
| [intro](help/database/db_intro) | |
|
||||
| [item](help/database/db_item) | all posts |
|
||||
|
|
@ -38,6 +37,8 @@ Database Tables
|
|||
| [manage](help/database/db_manage) | table of accounts that can "su" each other |
|
||||
| [notify](help/database/db_notify) | notifications |
|
||||
| [notify-threads](help/database/db_notify-threads) | |
|
||||
| [oembed](help/database/db_oembed) | cache for OEmbed queries |
|
||||
| [parsed_url](help/database/db_parsed_url) | cache for "parse_url" queries |
|
||||
| [pconfig](help/database/db_pconfig) | personal (per user) configuration storage |
|
||||
| [photo](help/database/db_photo) | photo storage |
|
||||
| [poll](help/database/db_poll) | data for polls |
|
||||
|
|
@ -54,7 +55,6 @@ Database Tables
|
|||
| [term](help/database/db_term) | item taxonomy (categories, tags, etc.) table |
|
||||
| [thread](help/database/db_thread) | |
|
||||
| [tokens](help/database/db_tokens) | OAuth usage |
|
||||
| [unique_contacts](help/database/db_unique_contacts) | |
|
||||
| [user](help/database/db_user) | local user table |
|
||||
| [userd](help/database/db_userd) | |
|
||||
| [workerqueue](help/database/db_workerqueue) | |
|
||||
|
|
|
|||
|
|
@ -18,9 +18,14 @@ Table gcontact
|
|||
| about | | text | NO | | NULL | |
|
||||
| keywords | puplic keywords (interests) | text | NO | | NULL | |
|
||||
| gender | | varchar(32) | NO | | | |
|
||||
| birthday | | varchar(32) | NO | | 0000-00-00 | |
|
||||
| community | 1 if contact is forum account | tinyint(1) | NO | | 0 | |
|
||||
| hide | 1 = should be hidden from search | tinyint(1) | NO | | 0 | |
|
||||
| nsfw | 1 = contact posts nsfw content | tinyint(1) | NO | | 0 | |
|
||||
| network | social network protocol | varchar(255) | NO | | | |
|
||||
| addr | | varchar(255) | NO | | | |
|
||||
| notify | | text | NO | | | |
|
||||
| alias | | varchar(255) | NO | | | |
|
||||
| generation | | tinyint(3) | NO | | 0 | |
|
||||
| server_url | baseurl of the contacts server | varchar(255) | NO | | | |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
Table guid
|
||||
==========
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
|---------|------------------|------------------|------|-----|---------|----------------|
|
||||
| id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment |
|
||||
| guid | | varchar(255) | NO | MUL | | |
|
||||
| plink | | varchar(255) | NO | MUL | | |
|
||||
| uri | | varchar(255) | NO | MUL | | |
|
||||
| network | | varchar(32) | NO | | | |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
|
|
@ -8,6 +8,7 @@ Table item
|
|||
| uri | | varchar(255) | NO | MUL | | |
|
||||
| uid | user.id which owns this copy of the item | int(10) unsigned | NO | MUL | 0 | |
|
||||
| contact-id | contact.id | int(11) | NO | MUL | 0 | |
|
||||
| gcontact-id | ID of the global contact | int(11) | NO | MUL | 0 | |
|
||||
| type | | varchar(255) | NO | | | |
|
||||
| wall | This item was posted to the wall of uid | tinyint(1) | NO | MUL | 0 | |
|
||||
| gravity | | tinyint(1) | NO | | 0 | |
|
||||
|
|
|
|||
10
doc/database/db_oembed.md
Normal file
10
doc/database/db_oembed.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Table oembed
|
||||
============
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| ------------ | ---------------------------------- | ------------ | ---- | --- | ------------------- | ----- |
|
||||
| url | page url | varchar(255) | NO | PRI | NULL | |
|
||||
| content | OEmbed data of the page | text | NO | | NULL | |
|
||||
| created | datetime of creation | datetime | NO | MUL | 0000-00-00 00:00:00 | |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
12
doc/database/db_parsed_url.md
Normal file
12
doc/database/db_parsed_url.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Table parsed_url
|
||||
================
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| ------------ | ---------------------------------- | ------------ | ---- | --- | ------------------- | ----- |
|
||||
| url | page url | varchar(255) | NO | PRI | NULL | |
|
||||
| guessing | is the "guessing" mode active? | tinyint(1) | NO | PRI | 0 | |
|
||||
| oembed | is the data the result of oembed? | tinyint(1) | NO | PRI | 0 | |
|
||||
| content | page data | text | NO | | NULL | |
|
||||
| created | datetime of creation | datetime | NO | MUL | 0000-00-00 00:00:00 | |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
|
|
@ -1,30 +1,31 @@
|
|||
Table thread
|
||||
============
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
|------------|------------------|------------------|------|-----|---------------------|-------|
|
||||
| iid | sequential ID | int(10) unsigned | NO | PRI | 0 | |
|
||||
| uid | | int(10) unsigned | NO | MUL | 0 | |
|
||||
| contact-id | | int(11) unsigned | NO | | 0 | |
|
||||
| created | | datetime | NO | MUL | 0000-00-00 00:00:00 | |
|
||||
| edited | | datetime | NO | | 0000-00-00 00:00:00 | |
|
||||
| commented | | datetime | NO | MUL | 0000-00-00 00:00:00 | |
|
||||
| received | | datetime | NO | | 0000-00-00 00:00:00 | |
|
||||
| changed | | datetime | NO | | 0000-00-00 00:00:00 | |
|
||||
| wall | | tinyint(1) | NO | MUL | 0 | |
|
||||
| private | | tinyint(1) | NO | | 0 | |
|
||||
| pubmail | | tinyint(1) | NO | | 0 | |
|
||||
| moderated | | tinyint(1) | NO | | 0 | |
|
||||
| visible | | tinyint(1) | NO | | 0 | |
|
||||
| spam | | tinyint(1) | NO | | 0 | |
|
||||
| starred | | tinyint(1) | NO | | 0 | |
|
||||
| ignored | | tinyint(1) | NO | | 0 | |
|
||||
| bookmark | | tinyint(1) | NO | | 0 | |
|
||||
| unseen | | tinyint(1) | NO | | 1 | |
|
||||
| deleted | | tinyint(1) | NO | | 0 | |
|
||||
| origin | | tinyint(1) | NO | | 0 | |
|
||||
| forum_mode | | tinyint(1) | NO | | 0 | |
|
||||
| mention | | tinyint(1) | NO | | 0 | |
|
||||
| network | | varchar(32) | NO | | | |
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
|-------------|------------------|------------------|------|-----|---------------------|-------|
|
||||
| iid | sequential ID | int(10) unsigned | NO | PRI | 0 | |
|
||||
| uid | | int(10) unsigned | NO | MUL | 0 | |
|
||||
| contact-id | | int(11) unsigned | NO | | 0 | |
|
||||
| gcontact-id | Global Contact | int(11) unsigned | NO | | 0 | |
|
||||
| created | | datetime | NO | MUL | 0000-00-00 00:00:00 | |
|
||||
| edited | | datetime | NO | | 0000-00-00 00:00:00 | |
|
||||
| commented | | datetime | NO | MUL | 0000-00-00 00:00:00 | |
|
||||
| received | | datetime | NO | | 0000-00-00 00:00:00 | |
|
||||
| changed | | datetime | NO | | 0000-00-00 00:00:00 | |
|
||||
| wall | | tinyint(1) | NO | MUL | 0 | |
|
||||
| private | | tinyint(1) | NO | | 0 | |
|
||||
| pubmail | | tinyint(1) | NO | | 0 | |
|
||||
| moderated | | tinyint(1) | NO | | 0 | |
|
||||
| visible | | tinyint(1) | NO | | 0 | |
|
||||
| spam | | tinyint(1) | NO | | 0 | |
|
||||
| starred | | tinyint(1) | NO | | 0 | |
|
||||
| ignored | | tinyint(1) | NO | | 0 | |
|
||||
| bookmark | | tinyint(1) | NO | | 0 | |
|
||||
| unseen | | tinyint(1) | NO | | 1 | |
|
||||
| deleted | | tinyint(1) | NO | | 0 | |
|
||||
| origin | | tinyint(1) | NO | | 0 | |
|
||||
| forum_mode | | tinyint(1) | NO | | 0 | |
|
||||
| mention | | tinyint(1) | NO | | 0 | |
|
||||
| network | | varchar(32) | NO | | | |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
Table unique_contacts
|
||||
=====================
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
|----------|------------------|--------------|------|-----|---------|----------------|
|
||||
| id | sequential ID | int(11) | NO | PRI | NULL | auto_increment |
|
||||
| url | | varchar(255) | NO | MUL | | |
|
||||
| nick | | varchar(255) | NO | | | |
|
||||
| name | | varchar(255) | NO | | | |
|
||||
| avatar | | varchar(255) | NO | | | |
|
||||
| location | | varchar(255) | NO | | | |
|
||||
| about | | text | NO | | NULL | |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
|
|
@ -1,27 +1,28 @@
|
|||
**Friendica Addon/Plugin-Entwicklung**
|
||||
Friendica Addon/Plugin-Entwicklung
|
||||
==============
|
||||
|
||||
* [Zur Startseite der Hilfe](help)
|
||||
|
||||
Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an.
|
||||
Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren.
|
||||
Addons arbeiten, indem sie Event Hooks abfangen. Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden
|
||||
Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an.
|
||||
Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren.
|
||||
Addons arbeiten, indem sie Event Hooks abfangen.
|
||||
Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden.
|
||||
|
||||
Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt.
|
||||
Du kannst einen lesbaren Namen im Kommentarblock eintragen.
|
||||
Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-/Plugin-Namen basieren; z.B. "plugin1name_install()".
|
||||
Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Plugin benötigt.
|
||||
Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Plugin nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen.
|
||||
Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt.
|
||||
Du kannst einen lesbaren Namen im Kommentarblock eintragen.
|
||||
Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-/Plugin-Namen basieren; z.B. "plugin1name_install()".
|
||||
Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Plugin benötigt.
|
||||
Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Plugin nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen.
|
||||
Zukünftige Extensions werden möglicherweise "Setup" und "Entfernen" anbieten.
|
||||
|
||||
Plugins sollten einen Kommentarblock mit den folgenden vier Parametern enthalten:
|
||||
|
||||
/*
|
||||
* Name: My Great Plugin
|
||||
* Description: This is what my plugin does. It's really cool
|
||||
* Version: 1.0
|
||||
* Author: John Q. Public <john@myfriendicasite.com>
|
||||
*/
|
||||
/*
|
||||
* Name: My Great Plugin
|
||||
* Description: This is what my plugin does. It's really cool.
|
||||
* Version: 1.0
|
||||
* Author: John Q. Public <john@myfriendicasite.com>
|
||||
*/
|
||||
|
||||
Registriere deine Plugin-Hooks während der Installation.
|
||||
|
||||
|
|
@ -29,45 +30,50 @@ Registriere deine Plugin-Hooks während der Installation.
|
|||
|
||||
$hookname ist ein String und entspricht einem bekannten Friendica-Hook.
|
||||
|
||||
$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt.
|
||||
$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt.
|
||||
Das *sollte* "addon/plugin_name/plugin_name.php' sein.
|
||||
|
||||
$function ist ein String und der Name der Funktion, die ausgeführt wird, wenn der Hook aufgerufen wird.
|
||||
|
||||
Argumente
|
||||
---
|
||||
|
||||
Deine Hook-Callback-Funktion wird mit mindestens einem und bis zu zwei Argumenten aufgerufen
|
||||
|
||||
function myhook_function(&$a, &$b) {
|
||||
|
||||
}
|
||||
|
||||
Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren.
|
||||
Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren.
|
||||
|
||||
$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs.
|
||||
Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen.
|
||||
$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs.
|
||||
Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen.
|
||||
|
||||
$b kann frei benannt werden.
|
||||
Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst.
|
||||
Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst.
|
||||
$b kann frei benannt werden.
|
||||
Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst.
|
||||
Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst.
|
||||
|
||||
|
||||
**Module**
|
||||
Module
|
||||
---
|
||||
|
||||
Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen.
|
||||
Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss.
|
||||
Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen.
|
||||
Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss.
|
||||
|
||||
Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://my.web.site/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente.
|
||||
Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden.
|
||||
So würde http://my.web.site/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst:
|
||||
Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://example.com/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente.
|
||||
Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden.
|
||||
So würde http://example.com/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst:
|
||||
|
||||
$a->argc = 3
|
||||
$a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
|
||||
$a->argc = 3
|
||||
$a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
|
||||
|
||||
Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt.
|
||||
Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt.
|
||||
Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt.
|
||||
Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt.
|
||||
Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisert.
|
||||
|
||||
|
||||
**Derzeitige Hooks:**
|
||||
Derzeitige Hooks
|
||||
---
|
||||
|
||||
**'authenticate'** - wird aufgerufen, wenn sich der User einloggt.
|
||||
$b ist ein Array
|
||||
|
|
@ -180,6 +186,9 @@ Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wi
|
|||
- wird aufgerufen nachdem in include/nav,php der Inhalt des Navigations Menüs erzeugt wurde.
|
||||
- $b ist ein Array, das $nav wiederspiegelt.
|
||||
|
||||
Komplette Liste der Hook-Callbacks
|
||||
---
|
||||
|
||||
Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 14-Feb-2012 generiert): Bitte schau in die Quellcodes für Details zu Hooks, die oben nicht dokumentiert sind.
|
||||
|
||||
boot.php: call_hooks('login_hook',$o);
|
||||
|
|
@ -204,7 +213,7 @@ include/text.php: call_hooks('contact_block_end', $arr);
|
|||
|
||||
include/text.php: call_hooks('smilie', $s);
|
||||
|
||||
include/text.php: call_hooks('prepare_body_init', $item);
|
||||
include/text.php: call_hooks('prepare_body_init', $item);
|
||||
|
||||
include/text.php: call_hooks('prepare_body', $prep_arr);
|
||||
|
||||
|
|
@ -359,4 +368,3 @@ mod/cb.php: call_hooks('cb_afterpost');
|
|||
mod/cb.php: call_hooks('cb_content', $o);
|
||||
|
||||
mod/directory.php: call_hooks('directory_item', $arr);
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -4,42 +4,32 @@ Friendica translations
|
|||
Translation Process
|
||||
-------------------
|
||||
|
||||
The strings used in the UI of Friendica is translated at [Transifex] [1] and then
|
||||
included in the git repository at github. If you want to help with translation
|
||||
for any language, be it correcting terms or translating friendica to a
|
||||
currently not supported language, please register an account at transifex.com
|
||||
and contact the friendica translation team there.
|
||||
The strings used in the UI of Friendica is translated at [Transifex] [1] and then included in the git repository at github.
|
||||
If you want to help with translation for any language, be it correcting terms or translating friendica to a currently not supported language, please register an account at transifex.com and contact the friendica translation team there.
|
||||
|
||||
Translating friendica is simple. Just use the online tool at transifex. If you
|
||||
don't want to deal with git & co. that is fine, we check the status of the
|
||||
translations regularly and import them into the source tree at github so that
|
||||
others can use them.
|
||||
Translating friendica is simple.
|
||||
Just use the online tool at transifex.
|
||||
If you don't want to deal with git & co. that is fine, we check the status of the translations regularly and import them into the source tree at github so that others can use them.
|
||||
|
||||
We do not include every translation from transifex in the source tree to avoid
|
||||
a scattered and disturbed overall experience. As an uneducated guess we have a
|
||||
lower limit of 50% translated strings before we include the language (for the
|
||||
core message.po file, addont translation will be included once all strings of
|
||||
an addon are translated. This limit is judging only by the amount of translated
|
||||
strings under the assumption that the most prominent strings for the UI will be
|
||||
translated first by a translation team. If you feel your translation useable
|
||||
before this limit, please contact us and we will probably include your teams
|
||||
work in the source tree.
|
||||
We do not include every translation from transifex in the source tree to avoid a scattered and disturbed overall experience.
|
||||
As an uneducated guess we have a lower limit of 50% translated strings before we include the language (for the core messages.po file, addont translation will be included once all strings of an addon are translated.
|
||||
This limit is judging only by the amount of translated strings under the assumption that the most prominent strings for the UI will be translated first by a translation team.
|
||||
If you feel your translation useable before this limit, please contact us and we will probably include your teams work in the source tree.
|
||||
|
||||
If you want to get your work into the source tree yourself, feel free to do so
|
||||
and contact us with and question that arises. The process is simple and
|
||||
friendica ships with all the tools necessary.
|
||||
If you want to help translating, please concentrate on the core messages.po file first.
|
||||
We will only include translations with a sufficient translated messages.po file.
|
||||
Translations of addons will only be included, when the core file is included as well.
|
||||
|
||||
If you want to get your work into the source tree yourself, feel free to do so and contact us with and question that arises.
|
||||
The process is simple and friendica ships with all the tools necessary.
|
||||
|
||||
The location of the translated files in the source tree is
|
||||
/view/LNG-CODE/
|
||||
where LNG-CODE is the language code used, e.g. de for German or fr for French.
|
||||
For the email templates (the *.tpl files) just place them into the directory
|
||||
and you are done. The translated strings come as a "message.po" file from
|
||||
transifex which needs to be translated into the PHP file friendica uses. To do
|
||||
so, place the file in the directory mentioned above and use the "po2php"
|
||||
utility from the util directory of your friendica installation.
|
||||
The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses.
|
||||
To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation.
|
||||
|
||||
Assuming you want to convert the German localization which is placed in
|
||||
view/de/message.po you would do the following.
|
||||
Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following.
|
||||
|
||||
1. Navigate at the command prompt to the base directory of your
|
||||
friendica installation
|
||||
|
|
@ -47,15 +37,15 @@ view/de/message.po you would do the following.
|
|||
2. Execute the po2php script, which will place the translation
|
||||
in the strings.php file that is used by friendica.
|
||||
|
||||
$> php util/po2php.php view/de/message.po
|
||||
$> php util/po2php.php view/de/messages.po
|
||||
|
||||
The output of the script will be placed at view/de/strings.php where
|
||||
froemdoca os expecting it, so you can test your translation mmediately.
|
||||
|
||||
friendica is expecting it, so you can test your translation immediately.
|
||||
|
||||
3. Visit your friendica page to check if it still works in the language you
|
||||
just translated. If not try to find the error, most likely PHP will give
|
||||
you a hint in the log/warnings.about the error.
|
||||
|
||||
|
||||
For debugging you can also try to "run" the file with PHP. This should
|
||||
not give any output if the file is ok but might give a hint for
|
||||
searching the bug in the file.
|
||||
|
|
@ -69,27 +59,10 @@ view/de/message.po you would do the following.
|
|||
Utilities
|
||||
---------
|
||||
|
||||
Additional to the po2php script there are some more utilities for translation
|
||||
in the "util" directory of the friendica source tree. If you only want to
|
||||
translate friendica into another language you wont need any of these tools most
|
||||
likely but it gives you an idea how the translation process of friendica
|
||||
works.
|
||||
Additional to the po2php script there are some more utilities for translation in the "util" directory of the friendica source tree.
|
||||
If you only want to translate friendica into another language you wont need any of these tools most likely but it gives you an idea how the translation process of friendica works.
|
||||
|
||||
For further information see the utils/README file.
|
||||
|
||||
Known Problems
|
||||
--------------
|
||||
|
||||
Friendica uses the language setting of the visitors browser to determain the
|
||||
language for the UI. Most of the time this works, but there are some known
|
||||
quirks.
|
||||
|
||||
One is that some browsers, like Safari, do the setting to "de-de" but friendica
|
||||
only has a "de" localisation. A workaround would be to add a symbolic link
|
||||
from
|
||||
$friendica/view/de-de
|
||||
pointing to
|
||||
$friendica/view/de
|
||||
|
||||
[1]: https://www.transifex.com/projects/p/friendica/
|
||||
|
||||
|
|
|
|||
|
|
@ -1657,35 +1657,6 @@ LOCK TABLES `tokens` WRITE;
|
|||
/*!40000 ALTER TABLE `tokens` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `unique_contacts`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `unique_contacts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `unique_contacts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`url` varchar(255) NOT NULL DEFAULT '',
|
||||
`nick` varchar(255) NOT NULL DEFAULT '',
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`avatar` varchar(255) NOT NULL DEFAULT '',
|
||||
`location` varchar(255) NOT NULL DEFAULT '',
|
||||
`about` text NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `url` (`url`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `unique_contacts`
|
||||
--
|
||||
|
||||
LOCK TABLES `unique_contacts` WRITE;
|
||||
/*!40000 ALTER TABLE `unique_contacts` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `unique_contacts` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `user`
|
||||
--
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ $a->config['system']['huburl'] = '[internal]';
|
|||
|
||||
// allowed themes (change this from admin panel after installation)
|
||||
|
||||
$a->config['system']['allowed_themes'] = 'dispy,quattro,vier,darkzero,duepuntozero,greenzero,purplezero,slackr,diabook';
|
||||
$a->config['system']['allowed_themes'] = 'quattro,vier,duepuntozero';
|
||||
|
||||
// default system theme
|
||||
|
||||
|
|
|
|||
|
|
@ -132,8 +132,8 @@ function terminate_friendship($user,$self,$contact) {
|
|||
diaspora_unshare($user,$contact);
|
||||
}
|
||||
elseif($contact['network'] === NETWORK_DFRN) {
|
||||
require_once('include/items.php');
|
||||
dfrn_deliver($user,$contact,'placeholder', 1);
|
||||
require_once('include/dfrn.php');
|
||||
dfrn::deliver($user,$contact,'placeholder', 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -205,60 +205,49 @@ function get_contact_details_by_url($url, $uid = -1) {
|
|||
if ((($profile["addr"] == "") OR ($profile["name"] == "")) AND
|
||||
in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS)))
|
||||
proc_run('php',"include/update_gcontact.php", $profile["gid"]);
|
||||
|
||||
} else {
|
||||
$r = q("SELECT `url`, `name`, `nick`, `avatar` AS `photo`, `location`, `about` FROM `unique_contacts` WHERE `url` = '%s'",
|
||||
dbesc(normalise_link($url)));
|
||||
|
||||
if (count($r)) {
|
||||
$profile = $r[0];
|
||||
$profile["keywords"] = "";
|
||||
$profile["gender"] = "";
|
||||
$profile["community"] = false;
|
||||
$profile["network"] = "";
|
||||
$profile["addr"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Fetching further contact data from the contact table
|
||||
$r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` = '%s'",
|
||||
$r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd`, `self` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` IN ('%s', '')",
|
||||
dbesc(normalise_link($url)), intval($uid), dbesc($profile["network"]));
|
||||
|
||||
if (!count($r))
|
||||
$r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
|
||||
if (!count($r) AND !isset($profile))
|
||||
$r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd`, `self` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
|
||||
dbesc(normalise_link($url)), intval($uid));
|
||||
|
||||
if (!count($r))
|
||||
$r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0",
|
||||
if (!count($r) AND !isset($profile))
|
||||
$r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0",
|
||||
dbesc(normalise_link($url)));
|
||||
|
||||
if ($r) {
|
||||
if (isset($r[0]["url"]) AND $r[0]["url"])
|
||||
if (!isset($profile["url"]) AND $r[0]["url"])
|
||||
$profile["url"] = $r[0]["url"];
|
||||
if (isset($r[0]["name"]) AND $r[0]["name"])
|
||||
if (!isset($profile["name"]) AND $r[0]["name"])
|
||||
$profile["name"] = $r[0]["name"];
|
||||
if (isset($r[0]["nick"]) AND $r[0]["nick"] AND ($profile["nick"] == ""))
|
||||
if (!isset($profile["nick"]) AND $r[0]["nick"])
|
||||
$profile["nick"] = $r[0]["nick"];
|
||||
if (isset($r[0]["addr"]) AND $r[0]["addr"] AND ($profile["addr"] == ""))
|
||||
if (!isset($profile["addr"]) AND $r[0]["addr"])
|
||||
$profile["addr"] = $r[0]["addr"];
|
||||
if (isset($r[0]["photo"]) AND $r[0]["photo"])
|
||||
if ((!isset($profile["photo"]) OR $r[0]["self"]) AND $r[0]["photo"])
|
||||
$profile["photo"] = $r[0]["photo"];
|
||||
if (isset($r[0]["location"]) AND $r[0]["location"])
|
||||
if (!isset($profile["location"]) AND $r[0]["location"])
|
||||
$profile["location"] = $r[0]["location"];
|
||||
if (isset($r[0]["about"]) AND $r[0]["about"])
|
||||
if (!isset($profile["about"]) AND $r[0]["about"])
|
||||
$profile["about"] = $r[0]["about"];
|
||||
if (isset($r[0]["keywords"]) AND $r[0]["keywords"])
|
||||
if (!isset($profile["keywords"]) AND $r[0]["keywords"])
|
||||
$profile["keywords"] = $r[0]["keywords"];
|
||||
if (isset($r[0]["gender"]) AND $r[0]["gender"])
|
||||
if (!isset($profile["gender"]) AND $r[0]["gender"])
|
||||
$profile["gender"] = $r[0]["gender"];
|
||||
if (isset($r[0]["forum"]) OR isset($r[0]["prv"]))
|
||||
$profile["community"] = ($r[0]["forum"] OR $r[0]["prv"]);
|
||||
if (isset($r[0]["network"]) AND $r[0]["network"])
|
||||
if (!isset($profile["network"]) AND $r[0]["network"])
|
||||
$profile["network"] = $r[0]["network"];
|
||||
if (isset($r[0]["addr"]) AND $r[0]["addr"])
|
||||
if (!isset($profile["addr"]) AND $r[0]["addr"])
|
||||
$profile["addr"] = $r[0]["addr"];
|
||||
if (isset($r[0]["bd"]) AND $r[0]["bd"])
|
||||
if (!isset($profile["bd"]) AND $r[0]["bd"])
|
||||
$profile["bd"] = $r[0]["bd"];
|
||||
if (isset($r[0]["thumb"]))
|
||||
$profile["thumb"] = $r[0]["thumb"];
|
||||
if ($r[0]["uid"] == 0)
|
||||
$profile["cid"] = 0;
|
||||
else
|
||||
|
|
@ -415,6 +404,7 @@ function get_contact($url, $uid = 0) {
|
|||
$contactid = 0;
|
||||
|
||||
// is it an address in the format user@server.tld?
|
||||
/// @todo use gcontact and/or the addr field for a lookup
|
||||
if (!strstr($url, "http") OR strstr($url, "@")) {
|
||||
$data = probe_url($url);
|
||||
$url = $data["url"];
|
||||
|
|
@ -422,12 +412,12 @@ function get_contact($url, $uid = 0) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
$contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
|
||||
$contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2",
|
||||
dbesc(normalise_link($url)),
|
||||
intval($uid));
|
||||
|
||||
if (!$contact)
|
||||
$contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `alias` IN ('%s', '%s') AND `uid` = %d",
|
||||
$contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `alias` IN ('%s', '%s') AND `uid` = %d ORDER BY `id` LIMIT 1",
|
||||
dbesc($url),
|
||||
dbesc(normalise_link($url)),
|
||||
intval($uid));
|
||||
|
|
@ -451,9 +441,7 @@ function get_contact($url, $uid = 0) {
|
|||
if (!in_array($data["network"], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA)))
|
||||
return 0;
|
||||
|
||||
// tempory programming. Can be deleted after 2015-02-07
|
||||
if (($data["alias"] == "") AND (normalise_link($data["url"]) != normalise_link($url)))
|
||||
$data["alias"] = normalise_link($url);
|
||||
$url = $data["url"];
|
||||
|
||||
if ($contactid == 0) {
|
||||
q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
|
||||
|
|
@ -482,7 +470,7 @@ function get_contact($url, $uid = 0) {
|
|||
dbesc($data["poco"])
|
||||
);
|
||||
|
||||
$contact = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
|
||||
$contact = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2",
|
||||
dbesc(normalise_link($data["url"])),
|
||||
intval($uid));
|
||||
if (!$contact)
|
||||
|
|
@ -491,25 +479,217 @@ function get_contact($url, $uid = 0) {
|
|||
$contactid = $contact[0]["id"];
|
||||
}
|
||||
|
||||
if ((count($contact) > 1) AND ($uid == 0) AND ($contactid != 0) AND ($url != ""))
|
||||
q("DELETE FROM `contact` WHERE `nurl` = '%s' AND `id` != %d",
|
||||
dbesc(normalise_link($url)),
|
||||
intval($contactid));
|
||||
|
||||
require_once("Photo.php");
|
||||
|
||||
$photos = import_profile_photo($data["photo"],$uid,$contactid);
|
||||
update_contact_avatar($data["photo"],$uid,$contactid);
|
||||
|
||||
q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s',
|
||||
`addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
|
||||
`name-date` = '%s', `uri-date` = '%s', `avatar-date` = '%s' WHERE `id` = %d",
|
||||
dbesc($photos[0]),
|
||||
dbesc($photos[1]),
|
||||
dbesc($photos[2]),
|
||||
q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
|
||||
`name-date` = '%s', `uri-date` = '%s' WHERE `id` = %d",
|
||||
dbesc($data["addr"]),
|
||||
dbesc($data["alias"]),
|
||||
dbesc($data["name"]),
|
||||
dbesc($data["nick"]),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
intval($contactid)
|
||||
);
|
||||
|
||||
return $contactid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns posts from a given gcontact
|
||||
*
|
||||
* @param App $a argv application class
|
||||
* @param int $gcontact_id Global contact
|
||||
*
|
||||
* @return string posts in HTML
|
||||
*/
|
||||
function posts_from_gcontact($a, $gcontact_id) {
|
||||
|
||||
require_once('include/conversation.php');
|
||||
|
||||
// There are no posts with "uid = 0" with connector networks
|
||||
// This speeds up the query a lot
|
||||
$r = q("SELECT `network` FROM `gcontact` WHERE `id` = %d", dbesc($gcontact_id));
|
||||
if (in_array($r[0]["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, "")))
|
||||
$sql = "(`item`.`uid` = 0 OR (`item`.`uid` = %d AND `item`.`private`))";
|
||||
else
|
||||
$sql = "`item`.`uid` = %d";
|
||||
|
||||
if(get_config('system', 'old_pager')) {
|
||||
$r = q("SELECT COUNT(*) AS `total` FROM `item`
|
||||
WHERE `gcontact-id` = %d and $sql",
|
||||
intval($gcontact_id),
|
||||
intval(local_user()));
|
||||
|
||||
$a->set_pager_total($r[0]['total']);
|
||||
}
|
||||
|
||||
$r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,
|
||||
`author-name` AS `name`, `owner-avatar` AS `photo`,
|
||||
`owner-link` AS `url`, `owner-avatar` AS `thumb`
|
||||
FROM `item` FORCE INDEX (`gcontactid_uid_created`)
|
||||
WHERE `gcontact-id` = %d AND $sql AND
|
||||
NOT `deleted` AND NOT `moderated` AND `visible`
|
||||
ORDER BY `item`.`created` DESC LIMIT %d, %d",
|
||||
intval($gcontact_id),
|
||||
intval(local_user()),
|
||||
intval($a->pager['start']),
|
||||
intval($a->pager['itemspage'])
|
||||
);
|
||||
|
||||
$o = conversation($a,$r,'community',false);
|
||||
|
||||
if(!get_config('system', 'old_pager')) {
|
||||
$o .= alt_pager($a,count($r));
|
||||
} else {
|
||||
$o .= paginate($a);
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @param App $a argv application class
|
||||
* @param int $contact_id contact
|
||||
*
|
||||
* @return string posts in HTML
|
||||
*/
|
||||
function posts_from_contact($a, $contact_id) {
|
||||
|
||||
require_once('include/conversation.php');
|
||||
|
||||
$r = q("SELECT `url` FROM `contact` WHERE `id` = %d", intval($contact_id));
|
||||
if (!$r)
|
||||
return false;
|
||||
|
||||
$contact = $r[0];
|
||||
|
||||
if(get_config('system', 'old_pager')) {
|
||||
$r = q("SELECT COUNT(*) AS `total` FROM `item`
|
||||
WHERE `item`.`uid` = %d AND `author-link` IN ('%s', '%s')",
|
||||
intval(local_user()),
|
||||
dbesc(str_replace("https://", "http://", $contact["url"])),
|
||||
dbesc(str_replace("http://", "https://", $contact["url"])));
|
||||
|
||||
$a->set_pager_total($r[0]['total']);
|
||||
}
|
||||
|
||||
$r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,
|
||||
`author-name` AS `name`, `owner-avatar` AS `photo`,
|
||||
`owner-link` AS `url`, `owner-avatar` AS `thumb`
|
||||
FROM `item` FORCE INDEX (`uid_contactid_created`)
|
||||
WHERE `item`.`uid` = %d AND `contact-id` = %d
|
||||
AND `author-link` IN ('%s', '%s')
|
||||
AND NOT `deleted` AND NOT `moderated` AND `visible`
|
||||
ORDER BY `item`.`created` DESC LIMIT %d, %d",
|
||||
intval(local_user()),
|
||||
intval($contact_id),
|
||||
dbesc(str_replace("https://", "http://", $contact["url"])),
|
||||
dbesc(str_replace("http://", "https://", $contact["url"])),
|
||||
intval($a->pager['start']),
|
||||
intval($a->pager['itemspage'])
|
||||
);
|
||||
|
||||
$o .= conversation($a,$r,'community',false);
|
||||
|
||||
if(!get_config('system', 'old_pager'))
|
||||
$o .= alt_pager($a,count($r));
|
||||
else
|
||||
$o .= paginate($a);
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a formatted location string from the given profile array
|
||||
*
|
||||
* @param array $profile Profile array (Generated from the "profile" table)
|
||||
*
|
||||
* @return string Location string
|
||||
*/
|
||||
function formatted_location($profile) {
|
||||
$location = '';
|
||||
|
||||
if($profile['locality'])
|
||||
$location .= $profile['locality'];
|
||||
|
||||
if($profile['region'] AND ($profile['locality'] != $profile['region'])) {
|
||||
if($location)
|
||||
$location .= ', ';
|
||||
|
||||
$location .= $profile['region'];
|
||||
}
|
||||
|
||||
if($profile['country-name']) {
|
||||
if($location)
|
||||
$location .= ', ';
|
||||
|
||||
$location .= $profile['country-name'];
|
||||
}
|
||||
|
||||
return $location;
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
190
include/ForumManager.php
Normal file
190
include/ForumManager.php
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file include/forum.php
|
||||
* @brief Functions related to forum functionality *
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief This class handles functions related to the forum functionality
|
||||
*/
|
||||
class ForumManager {
|
||||
|
||||
/**
|
||||
* @brief Function to list all forums a user is connected with
|
||||
*
|
||||
* @param int $uid of the profile owner
|
||||
* @param boolean $showhidden
|
||||
* Show frorums which are not hidden
|
||||
* @param boolean $lastitem
|
||||
* Sort by lastitem
|
||||
* @param boolean $showprivate
|
||||
* Show private groups
|
||||
*
|
||||
* @returns array
|
||||
* 'url' => forum url
|
||||
* 'name' => forum name
|
||||
* 'id' => number of the key from the array
|
||||
* 'micro' => contact photo in format micro
|
||||
*/
|
||||
public static function get_list($uid, $showhidden = true, $lastitem, $showprivate = false) {
|
||||
|
||||
$forumlist = array();
|
||||
|
||||
$order = (($showhidden) ? '' : ' AND NOT `hidden` ');
|
||||
$order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC ');
|
||||
$select = '`forum` ';
|
||||
if ($showprivate) {
|
||||
$select = '(`forum` OR `prv`)';
|
||||
}
|
||||
|
||||
$contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact`
|
||||
WHERE `network`= 'dfrn' AND $select AND `uid` = %d
|
||||
AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive`
|
||||
AND `success_update` > `failure_update`
|
||||
$order ",
|
||||
intval($uid)
|
||||
);
|
||||
|
||||
if (!$contacts)
|
||||
return($forumlist);
|
||||
|
||||
foreach($contacts as $contact) {
|
||||
$forumlist[] = array(
|
||||
'url' => $contact['url'],
|
||||
'name' => $contact['name'],
|
||||
'id' => $contact['id'],
|
||||
'micro' => $contact['micro'],
|
||||
);
|
||||
}
|
||||
return($forumlist);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Forumlist widget
|
||||
*
|
||||
* Sidebar widget to show subcribed friendica forums. If activated
|
||||
* in the settings, it appears at the notwork page sidebar
|
||||
*
|
||||
* @param int $uid The ID of the User
|
||||
* @param int $cid
|
||||
* The contact id which is used to mark a forum as "selected"
|
||||
* @return string
|
||||
*/
|
||||
public static function widget($uid,$cid = 0) {
|
||||
|
||||
if(! intval(feature_enabled(local_user(),'forumlist_widget')))
|
||||
return;
|
||||
|
||||
$o = '';
|
||||
|
||||
//sort by last updated item
|
||||
$lastitem = true;
|
||||
|
||||
$contacts = self::get_list($uid,true,$lastitem, true);
|
||||
$total = count($contacts);
|
||||
$visible_forums = 10;
|
||||
|
||||
if(count($contacts)) {
|
||||
|
||||
$id = 0;
|
||||
|
||||
foreach($contacts as $contact) {
|
||||
|
||||
$selected = (($cid == $contact['id']) ? ' forum-selected' : '');
|
||||
|
||||
$entry = array(
|
||||
'url' => z_root() . '/network?f=&cid=' . $contact['id'],
|
||||
'external_url' => z_root() . '/redir/' . $contact['id'],
|
||||
'name' => $contact['name'],
|
||||
'cid' => $contact['id'],
|
||||
'selected' => $selected,
|
||||
'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO),
|
||||
'id' => ++$id,
|
||||
);
|
||||
$entries[] = $entry;
|
||||
}
|
||||
|
||||
$tpl = get_markup_template('widget_forumlist.tpl');
|
||||
|
||||
$o .= replace_macros($tpl,array(
|
||||
'$title' => t('Forums'),
|
||||
'$forums' => $entries,
|
||||
'$link_desc' => t('External link to forum'),
|
||||
'$total' => $total,
|
||||
'$visible_forums' => $visible_forums,
|
||||
'$showmore' => t('show more'),
|
||||
));
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Format forumlist as contact block
|
||||
*
|
||||
* This function is used to show the forumlist in
|
||||
* the advanced profile.
|
||||
*
|
||||
* @param int $uid The ID of the User
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public static function profile_advanced($uid) {
|
||||
|
||||
$profile = intval(feature_enabled($uid,'forumlist_profile'));
|
||||
if(! $profile)
|
||||
return;
|
||||
|
||||
$o = '';
|
||||
|
||||
// place holder in case somebody wants configurability
|
||||
$show_total = 9999;
|
||||
|
||||
//don't sort by last updated item
|
||||
$lastitem = false;
|
||||
|
||||
$contacts = self::get_list($uid,false,$lastitem,false);
|
||||
|
||||
$total_shown = 0;
|
||||
|
||||
foreach($contacts as $contact) {
|
||||
$forumlist .= micropro($contact,false,'forumlist-profile-advanced');
|
||||
$total_shown ++;
|
||||
if($total_shown == $show_total)
|
||||
break;
|
||||
}
|
||||
|
||||
if(count($contacts) > 0)
|
||||
$o .= $forumlist;
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief count unread forum items
|
||||
*
|
||||
* Count unread items of connected forums and private groups
|
||||
*
|
||||
* @return array
|
||||
* 'id' => contact id
|
||||
* 'name' => contact/forum name
|
||||
* 'count' => counted unseen forum items
|
||||
*
|
||||
*/
|
||||
public static function count_unseen_items() {
|
||||
$r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item`
|
||||
INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id`
|
||||
WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen`
|
||||
AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`)
|
||||
AND NOT `contact`.`blocked` AND NOT `contact`.`hidden`
|
||||
AND NOT `contact`.`pending` AND NOT `contact`.`archive`
|
||||
AND `contact`.`success_update` > `failure_update`
|
||||
GROUP BY `contact`.`id` ",
|
||||
intval(local_user())
|
||||
);
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
}
|
||||
136
include/NotificationsManager.php
Normal file
136
include/NotificationsManager.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
/**
|
||||
* @file include/NotificationsManager.php
|
||||
*/
|
||||
require_once('include/html2plain.php');
|
||||
require_once("include/datetime.php");
|
||||
require_once("include/bbcode.php");
|
||||
|
||||
/**
|
||||
* @brief Read and write notifications from/to database
|
||||
*/
|
||||
class NotificationsManager {
|
||||
private $a;
|
||||
|
||||
public function __construct() {
|
||||
$this->a = get_app();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set some extra note properties
|
||||
*
|
||||
* @param array $notes array of note arrays from db
|
||||
* @return array Copy of input array with added properties
|
||||
*
|
||||
* Set some extra properties to note array from db:
|
||||
* - timestamp as int in default TZ
|
||||
* - date_rel : relative date string
|
||||
* - msg_html: message as html string
|
||||
* - msg_plain: message as plain text string
|
||||
*/
|
||||
private function _set_extra($notes) {
|
||||
$rets = array();
|
||||
foreach($notes as $n) {
|
||||
$local_time = datetime_convert('UTC',date_default_timezone_get(),$n['date']);
|
||||
$n['timestamp'] = strtotime($local_time);
|
||||
$n['date_rel'] = relative_date($n['date']);
|
||||
$n['msg_html'] = bbcode($n['msg'], false, false, false, false);
|
||||
$n['msg_plain'] = explode("\n",trim(html2plain($n['msg_html'], 0)))[0];
|
||||
|
||||
$rets[] = $n;
|
||||
}
|
||||
return $rets;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -720,65 +720,101 @@ function guess_image_type($filename, $fromcurl=false) {
|
|||
|
||||
}
|
||||
|
||||
function import_profile_photo($photo,$uid,$cid) {
|
||||
/**
|
||||
* @brief Updates the avatar links in a contact only if needed
|
||||
*
|
||||
* @param string $avatar Link to avatar picture
|
||||
* @param int $uid User id of contact owner
|
||||
* @param int $cid Contact id
|
||||
* @param bool $force force picture update
|
||||
*
|
||||
* @return array Returns array of the different avatar sizes
|
||||
*/
|
||||
function update_contact_avatar($avatar,$uid,$cid, $force = false) {
|
||||
|
||||
$a = get_app();
|
||||
$r = q("SELECT `avatar`, `photo`, `thumb`, `micro` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid));
|
||||
if (!$r)
|
||||
return false;
|
||||
else
|
||||
$data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]);
|
||||
|
||||
$r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1",
|
||||
intval($uid),
|
||||
intval($cid)
|
||||
);
|
||||
if(count($r) && strlen($r[0]['resource-id'])) {
|
||||
$hash = $r[0]['resource-id'];
|
||||
}
|
||||
else {
|
||||
$hash = photo_new_resource();
|
||||
}
|
||||
if (($r[0]["avatar"] != $avatar) OR $force) {
|
||||
$photos = import_profile_photo($avatar,$uid,$cid, true);
|
||||
|
||||
$photo_failure = false;
|
||||
if ($photos) {
|
||||
q("UPDATE `contact` SET `avatar` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d",
|
||||
dbesc($avatar), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]),
|
||||
dbesc(datetime_convert()), intval($cid));
|
||||
return $photos;
|
||||
}
|
||||
}
|
||||
|
||||
$filename = basename($photo);
|
||||
$img_str = fetch_url($photo,true);
|
||||
return $data;
|
||||
}
|
||||
|
||||
$type = guess_image_type($photo,true);
|
||||
$img = new Photo($img_str, $type);
|
||||
if($img->is_valid()) {
|
||||
function import_profile_photo($photo,$uid,$cid, $quit_on_error = false) {
|
||||
|
||||
$img->scaleImageSquare(175);
|
||||
$a = get_app();
|
||||
|
||||
$r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 );
|
||||
$r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1",
|
||||
intval($uid),
|
||||
intval($cid)
|
||||
);
|
||||
if(count($r) && strlen($r[0]['resource-id'])) {
|
||||
$hash = $r[0]['resource-id'];
|
||||
} else {
|
||||
$hash = photo_new_resource();
|
||||
}
|
||||
|
||||
if($r === false)
|
||||
$photo_failure = true;
|
||||
$photo_failure = false;
|
||||
|
||||
$img->scaleImage(80);
|
||||
$filename = basename($photo);
|
||||
$img_str = fetch_url($photo,true);
|
||||
|
||||
$r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 );
|
||||
if ($quit_on_error AND ($img_str == ""))
|
||||
return false;
|
||||
|
||||
if($r === false)
|
||||
$photo_failure = true;
|
||||
$type = guess_image_type($photo,true);
|
||||
$img = new Photo($img_str, $type);
|
||||
if($img->is_valid()) {
|
||||
|
||||
$img->scaleImage(48);
|
||||
$img->scaleImageSquare(175);
|
||||
|
||||
$r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 );
|
||||
$r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 );
|
||||
|
||||
if($r === false)
|
||||
$photo_failure = true;
|
||||
if($r === false)
|
||||
$photo_failure = true;
|
||||
|
||||
$photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt();
|
||||
$thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt();
|
||||
$micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt();
|
||||
}
|
||||
else
|
||||
$photo_failure = true;
|
||||
$img->scaleImage(80);
|
||||
|
||||
if($photo_failure) {
|
||||
$photo = $a->get_baseurl() . '/images/person-175.jpg';
|
||||
$thumb = $a->get_baseurl() . '/images/person-80.jpg';
|
||||
$micro = $a->get_baseurl() . '/images/person-48.jpg';
|
||||
}
|
||||
$r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 );
|
||||
|
||||
return(array($photo,$thumb,$micro));
|
||||
if($r === false)
|
||||
$photo_failure = true;
|
||||
|
||||
$img->scaleImage(48);
|
||||
|
||||
$r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 );
|
||||
|
||||
if($r === false)
|
||||
$photo_failure = true;
|
||||
|
||||
$photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt();
|
||||
$thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt();
|
||||
$micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt();
|
||||
} else
|
||||
$photo_failure = true;
|
||||
|
||||
if($photo_failure AND $quit_on_error)
|
||||
return false;
|
||||
|
||||
if($photo_failure) {
|
||||
$photo = $a->get_baseurl() . '/images/person-175.jpg';
|
||||
$thumb = $a->get_baseurl() . '/images/person-80.jpg';
|
||||
$micro = $a->get_baseurl() . '/images/person-48.jpg';
|
||||
}
|
||||
|
||||
return(array($photo,$thumb,$micro));
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -792,15 +828,19 @@ function get_photo_info($url) {
|
|||
|
||||
$filesize = strlen($img_str);
|
||||
|
||||
$tempfile = tempnam(get_temppath(), "cache");
|
||||
if (function_exists("getimagesizefromstring"))
|
||||
$data = getimagesizefromstring($img_str);
|
||||
else {
|
||||
$tempfile = tempnam(get_temppath(), "cache");
|
||||
|
||||
$a = get_app();
|
||||
$stamp1 = microtime(true);
|
||||
file_put_contents($tempfile, $img_str);
|
||||
$a->save_timestamp($stamp1, "file");
|
||||
$a = get_app();
|
||||
$stamp1 = microtime(true);
|
||||
file_put_contents($tempfile, $img_str);
|
||||
$a->save_timestamp($stamp1, "file");
|
||||
|
||||
$data = getimagesize($tempfile);
|
||||
unlink($tempfile);
|
||||
$data = getimagesize($tempfile);
|
||||
unlink($tempfile);
|
||||
}
|
||||
|
||||
if ($data)
|
||||
$data["size"] = $filesize;
|
||||
|
|
|
|||
|
|
@ -235,7 +235,9 @@ function scrape_feed($url) {
|
|||
$a = get_app();
|
||||
|
||||
$ret = array();
|
||||
$s = fetch_url($url);
|
||||
$cookiejar = tempnam(get_temppath(), 'cookiejar-scrape-feed-');
|
||||
$s = fetch_url($url, false, $redirects, 0, Null, $cookiejar);
|
||||
unlink($cookiejar);
|
||||
|
||||
$headers = $a->get_curl_headers();
|
||||
$code = $a->get_curl_code();
|
||||
|
|
@ -559,10 +561,11 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
|
|||
$pubkey = $hcard_key;
|
||||
}
|
||||
}
|
||||
|
||||
if($diaspora && $diaspora_base && $diaspora_guid) {
|
||||
if($mode == PROBE_DIASPORA || ! $notify) {
|
||||
$notify = $diaspora_base . 'receive/users/' . $diaspora_guid;
|
||||
$diaspora_notify = $diaspora_base.'receive/users/'.$diaspora_guid;
|
||||
|
||||
if($mode == PROBE_DIASPORA || ! $notify || ($notify == $diaspora_notify)) {
|
||||
$notify = $diaspora_notify;
|
||||
$batch = $diaspora_base . 'receive/public' ;
|
||||
}
|
||||
if(strpos($url,'@'))
|
||||
|
|
@ -661,7 +664,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
|
|||
$vcard['photo'] = $feedret['photo'];
|
||||
require_once('library/simplepie/simplepie.inc');
|
||||
$feed = new SimplePie();
|
||||
$xml = fetch_url($poll);
|
||||
$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();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) {
|
|||
|
||||
$o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"$size\" >\r\n";
|
||||
|
||||
$r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC",
|
||||
$r = q("SELECT `id`, `name` FROM `group` WHERE NOT `deleted` AND `uid` = %d ORDER BY `name` ASC",
|
||||
intval(local_user())
|
||||
);
|
||||
|
||||
|
|
@ -309,7 +309,7 @@ function populate_acl($user = null, $show_jotnets = false) {
|
|||
$pubmail_enabled = false;
|
||||
|
||||
if(! $mail_disabled) {
|
||||
$r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1",
|
||||
$r = q("SELECT `pubmail` FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1",
|
||||
intval(local_user())
|
||||
);
|
||||
if(count($r)) {
|
||||
|
|
@ -407,7 +407,7 @@ function acl_lookup(&$a, $out_type = 'json') {
|
|||
$search = $_REQUEST['query'];
|
||||
}
|
||||
|
||||
// logger("Searching for ".$search." - type ".$type, LOGGER_DEBUG);
|
||||
logger("Searching for ".$search." - type ".$type, LOGGER_DEBUG);
|
||||
|
||||
if ($search!=""){
|
||||
$sql_extra = "AND `name` LIKE '%%".dbesc($search)."%%'";
|
||||
|
|
@ -503,7 +503,7 @@ function acl_lookup(&$a, $out_type = 'json') {
|
|||
}
|
||||
}
|
||||
|
||||
if ($type=='' || $type=='c'){
|
||||
if ($type==''){
|
||||
|
||||
$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, forum FROM `contact`
|
||||
WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != ''
|
||||
|
|
@ -514,6 +514,17 @@ function acl_lookup(&$a, $out_type = 'json') {
|
|||
dbesc(NETWORK_OSTATUS), dbesc(NETWORK_STATUSNET)
|
||||
);
|
||||
}
|
||||
elseif ($type=='c'){
|
||||
|
||||
$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, forum FROM `contact`
|
||||
WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != ''
|
||||
AND NOT (`network` IN ('%s'))
|
||||
$sql_extra2
|
||||
ORDER BY `name` ASC ",
|
||||
intval(local_user()),
|
||||
dbesc(NETWORK_STATUSNET)
|
||||
);
|
||||
}
|
||||
elseif($type == 'm') {
|
||||
$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag` FROM `contact`
|
||||
WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0
|
||||
|
|
|
|||
211
include/api.php
211
include/api.php
|
|
@ -23,6 +23,7 @@
|
|||
require_once('include/message.php');
|
||||
require_once('include/group.php');
|
||||
require_once('include/like.php');
|
||||
require_once('include/NotificationsManager.php');
|
||||
|
||||
|
||||
define('API_METHOD_ANY','*');
|
||||
|
|
@ -250,7 +251,7 @@
|
|||
*/
|
||||
function api_call(&$a){
|
||||
GLOBAL $API, $called_api;
|
||||
|
||||
|
||||
$type="json";
|
||||
if (strpos($a->query_string, ".xml")>0) $type="xml";
|
||||
if (strpos($a->query_string, ".json")>0) $type="json";
|
||||
|
|
@ -394,7 +395,7 @@
|
|||
* Contact url or False if contact id is unknown
|
||||
*/
|
||||
function api_unique_id_to_url($id){
|
||||
$r = q("SELECT `url` FROM `unique_contacts` WHERE `id`=%d LIMIT 1",
|
||||
$r = q("SELECT `url` FROM `gcontact` WHERE `id`=%d LIMIT 1",
|
||||
intval($id));
|
||||
if ($r)
|
||||
return ($r[0]["url"]);
|
||||
|
|
@ -504,9 +505,7 @@
|
|||
$r = array();
|
||||
|
||||
if ($url != "")
|
||||
$r = q("SELECT * FROM `unique_contacts` WHERE `url`='%s' LIMIT 1", $url);
|
||||
elseif ($nick != "")
|
||||
$r = q("SELECT * FROM `unique_contacts` WHERE `nick`='%s' LIMIT 1", $nick);
|
||||
$r = q("SELECT * FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($url)));
|
||||
|
||||
if ($r) {
|
||||
// If no nick where given, extract it from the address
|
||||
|
|
@ -518,14 +517,14 @@
|
|||
'id_str' => (string) $r[0]["id"],
|
||||
'name' => $r[0]["name"],
|
||||
'screen_name' => (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']),
|
||||
'location' => NULL,
|
||||
'description' => NULL,
|
||||
'location' => $r[0]["location"],
|
||||
'description' => $r[0]["about"],
|
||||
'url' => $r[0]["url"],
|
||||
'protected' => false,
|
||||
'followers_count' => 0,
|
||||
'friends_count' => 0,
|
||||
'listed_count' => 0,
|
||||
'created_at' => api_date(0),
|
||||
'created_at' => api_date($r[0]["created"]),
|
||||
'favourites_count' => 0,
|
||||
'utc_offset' => 0,
|
||||
'time_zone' => 'UTC',
|
||||
|
|
@ -536,8 +535,8 @@
|
|||
'contributors_enabled' => false,
|
||||
'is_translator' => false,
|
||||
'is_translation_enabled' => false,
|
||||
'profile_image_url' => $r[0]["avatar"],
|
||||
'profile_image_url_https' => $r[0]["avatar"],
|
||||
'profile_image_url' => $r[0]["photo"],
|
||||
'profile_image_url_https' => $r[0]["photo"],
|
||||
'following' => false,
|
||||
'follow_request_sent' => false,
|
||||
'notifications' => false,
|
||||
|
|
@ -547,7 +546,7 @@
|
|||
'uid' => 0,
|
||||
'cid' => 0,
|
||||
'self' => 0,
|
||||
'network' => '',
|
||||
'network' => $r[0]["network"],
|
||||
);
|
||||
|
||||
return $ret;
|
||||
|
|
@ -618,22 +617,14 @@
|
|||
$uinfo[0]['nick'] = api_get_nick($uinfo[0]["url"]);
|
||||
}
|
||||
|
||||
// Fetching unique id
|
||||
$r = q("SELECT id FROM `unique_contacts` WHERE `url`='%s' LIMIT 1", dbesc(normalise_link($uinfo[0]['url'])));
|
||||
|
||||
// If not there, then add it
|
||||
if (count($r) == 0) {
|
||||
q("INSERT INTO `unique_contacts` (`url`, `name`, `nick`, `avatar`) VALUES ('%s', '%s', '%s', '%s')",
|
||||
dbesc(normalise_link($uinfo[0]['url'])), dbesc($uinfo[0]['name']),dbesc($uinfo[0]['nick']), dbesc($uinfo[0]['micro']));
|
||||
|
||||
$r = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1", dbesc(normalise_link($uinfo[0]['url'])));
|
||||
}
|
||||
|
||||
$network_name = network_to_name($uinfo[0]['network'], $uinfo[0]['url']);
|
||||
|
||||
$gcontact_id = get_gcontact_id(array("url" => $uinfo[0]['url'], "network" => $uinfo[0]['network'],
|
||||
"photo" => $uinfo[0]['micro'], "name" => $uinfo[0]['name']));
|
||||
|
||||
$ret = Array(
|
||||
'id' => intval($r[0]['id']),
|
||||
'id_str' => (string) intval($r[0]['id']),
|
||||
'id' => intval($gcontact_id),
|
||||
'id_str' => (string) intval($gcontact_id),
|
||||
'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
|
||||
'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
|
||||
'location' => ($usr) ? $usr[0]['default-location'] : $network_name,
|
||||
|
|
@ -667,45 +658,12 @@
|
|||
|
||||
function api_item_get_user(&$a, $item) {
|
||||
|
||||
$author = q("SELECT * FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
|
||||
dbesc(normalise_link($item['author-link'])));
|
||||
// Make sure that there is an entry in the global contacts for author and owner
|
||||
get_gcontact_id(array("url" => $item['author-link'], "network" => $item['network'],
|
||||
"photo" => $item['author-avatar'], "name" => $item['author-name']));
|
||||
|
||||
if (count($author) == 0) {
|
||||
q("INSERT INTO `unique_contacts` (`url`, `name`, `avatar`) VALUES ('%s', '%s', '%s')",
|
||||
dbesc(normalise_link($item["author-link"])), dbesc($item["author-name"]), dbesc($item["author-avatar"]));
|
||||
|
||||
$author = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
|
||||
dbesc(normalise_link($item['author-link'])));
|
||||
} else if ($item["author-link"].$item["author-name"] != $author[0]["url"].$author[0]["name"]) {
|
||||
$r = q("SELECT `id` FROM `unique_contacts` WHERE `name` = '%s' AND `avatar` = '%s' AND url = '%s'",
|
||||
dbesc($item["author-name"]), dbesc($item["author-avatar"]),
|
||||
dbesc(normalise_link($item["author-link"])));
|
||||
|
||||
if (!$r)
|
||||
q("UPDATE `unique_contacts` SET `name` = '%s', `avatar` = '%s' WHERE `url` = '%s'",
|
||||
dbesc($item["author-name"]), dbesc($item["author-avatar"]),
|
||||
dbesc(normalise_link($item["author-link"])));
|
||||
}
|
||||
|
||||
$owner = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
|
||||
dbesc(normalise_link($item['owner-link'])));
|
||||
|
||||
if (count($owner) == 0) {
|
||||
q("INSERT INTO `unique_contacts` (`url`, `name`, `avatar`) VALUES ('%s', '%s', '%s')",
|
||||
dbesc(normalise_link($item["owner-link"])), dbesc($item["owner-name"]), dbesc($item["owner-avatar"]));
|
||||
|
||||
$owner = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
|
||||
dbesc(normalise_link($item['owner-link'])));
|
||||
} else if ($item["owner-link"].$item["owner-name"] != $owner[0]["url"].$owner[0]["name"]) {
|
||||
$r = q("SELECT `id` FROM `unique_contacts` WHERE `name` = '%s' AND `avatar` = '%s' AND url = '%s'",
|
||||
dbesc($item["owner-name"]), dbesc($item["owner-avatar"]),
|
||||
dbesc(normalise_link($item["owner-link"])));
|
||||
|
||||
if (!$r)
|
||||
q("UPDATE `unique_contacts` SET `name` = '%s', `avatar` = '%s' WHERE `url` = '%s'",
|
||||
dbesc($item["owner-name"]), dbesc($item["owner-avatar"]),
|
||||
dbesc(normalise_link($item["owner-link"])));
|
||||
}
|
||||
get_gcontact_id(array("url" => $item['owner-link'], "network" => $item['network'],
|
||||
"photo" => $item['owner-avatar'], "name" => $item['owner-name']));
|
||||
|
||||
// Comments in threads may appear as wall-to-wall postings.
|
||||
// So only take the owner at the top posting.
|
||||
|
|
@ -724,6 +682,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</$ename>";
|
||||
}
|
||||
foreach($data as $k=>$v) {
|
||||
$k=trim($k,'$');
|
||||
if (!is_array($v)) {
|
||||
$attrs .= sprintf('%s="%s" ', $k, $v);
|
||||
} else {
|
||||
if (is_numeric($k)) $k=trim($ename,'s');
|
||||
$childs.=api_array_to_xml($v, $k);
|
||||
}
|
||||
}
|
||||
$res = $childs;
|
||||
if ($ename!="") $res = "<$ename $attrs>$res</$ename>";
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* load api $templatename for $type and replace $data array
|
||||
*/
|
||||
|
|
@ -736,13 +722,17 @@
|
|||
case "rss":
|
||||
case "xml":
|
||||
$data = array_xmlify($data);
|
||||
$tpl = get_markup_template("api_".$templatename."_".$type.".tpl");
|
||||
if(! $tpl) {
|
||||
header ("Content-Type: text/xml");
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<status><error>not implemented</error></status>';
|
||||
killme();
|
||||
if ($templatename==="<auto>") {
|
||||
$ret = api_array_to_xml($data);
|
||||
} else {
|
||||
$tpl = get_markup_template("api_".$templatename."_".$type.".tpl");
|
||||
if(! $tpl) {
|
||||
header ("Content-Type: text/xml");
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<status><error>not implemented</error></status>';
|
||||
killme();
|
||||
}
|
||||
$ret = replace_macros($tpl, $data);
|
||||
}
|
||||
$ret = replace_macros($tpl, $data);
|
||||
break;
|
||||
case "json":
|
||||
$ret = $data;
|
||||
|
|
@ -825,8 +815,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);
|
||||
|
|
@ -866,9 +854,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();
|
||||
|
|
@ -1074,7 +1059,7 @@
|
|||
$in_reply_to_status_id= intval($lastwall['parent']);
|
||||
$in_reply_to_status_id_str = (string) intval($lastwall['parent']);
|
||||
|
||||
$r = q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", dbesc(normalise_link($lastwall['item-author'])));
|
||||
$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", dbesc(normalise_link($lastwall['item-author'])));
|
||||
if ($r) {
|
||||
if ($r[0]['nick'] == "")
|
||||
$r[0]['nick'] = api_get_nick($r[0]["url"]);
|
||||
|
|
@ -1196,7 +1181,7 @@
|
|||
$in_reply_to_status_id = intval($lastwall['parent']);
|
||||
$in_reply_to_status_id_str = (string) intval($lastwall['parent']);
|
||||
|
||||
$r = q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", dbesc(normalise_link($reply[0]['item-author'])));
|
||||
$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", dbesc(normalise_link($reply[0]['item-author'])));
|
||||
if ($r) {
|
||||
if ($r[0]['nick'] == "")
|
||||
$r[0]['nick'] = api_get_nick($r[0]["url"]);
|
||||
|
|
@ -1257,9 +1242,9 @@
|
|||
$userlist = array();
|
||||
|
||||
if (isset($_GET["q"])) {
|
||||
$r = q("SELECT id FROM `unique_contacts` WHERE `name`='%s'", dbesc($_GET["q"]));
|
||||
$r = q("SELECT id FROM `gcontact` WHERE `name`='%s'", dbesc($_GET["q"]));
|
||||
if (!count($r))
|
||||
$r = q("SELECT `id` FROM `unique_contacts` WHERE `nick`='%s'", dbesc($_GET["q"]));
|
||||
$r = q("SELECT `id` FROM `gcontact` WHERE `nick`='%s'", dbesc($_GET["q"]));
|
||||
|
||||
if (count($r)) {
|
||||
foreach ($r AS $user) {
|
||||
|
|
@ -1595,6 +1580,8 @@
|
|||
WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
|
||||
AND `contact`.`id` = `item`.`contact-id`
|
||||
AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
|
||||
AND NOT `item`.`private` AND `item`.`allow_cid` = '' AND `item`.`allow`.`gid` = ''
|
||||
AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
|
||||
$sql_extra
|
||||
AND `item`.`id`=%d",
|
||||
intval($id)
|
||||
|
|
@ -1623,7 +1610,8 @@
|
|||
$_REQUEST["source"] = api_source();
|
||||
|
||||
item_post($a);
|
||||
}
|
||||
} else
|
||||
throw new ForbiddenException();
|
||||
|
||||
// this should output the last post (the one we just posted).
|
||||
$called_api = null;
|
||||
|
|
@ -2342,7 +2330,7 @@
|
|||
intval(api_user()),
|
||||
intval($in_reply_to_status_id));
|
||||
if ($r) {
|
||||
$r = q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", dbesc(normalise_link($r[0]['author-link'])));
|
||||
$r = q("SELECT * FROM `gcontact` WHERE `url` = '%s'", dbesc(normalise_link($r[0]['author-link'])));
|
||||
|
||||
if ($r) {
|
||||
if ($r[0]['nick'] == "")
|
||||
|
|
@ -2598,7 +2586,7 @@
|
|||
|
||||
$stringify_ids = (x($_REQUEST,'stringify_ids')?$_REQUEST['stringify_ids']:false);
|
||||
|
||||
$r = q("SELECT `unique_contacts`.`id` FROM `contact`, `unique_contacts` WHERE `contact`.`nurl` = `unique_contacts`.`url` AND `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` $sql_extra",
|
||||
$r = q("SELECT `gcontact`.`id` FROM `contact`, `gcontact` WHERE `contact`.`nurl` = `gcontact`.`nurl` AND `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` $sql_extra",
|
||||
intval(api_user())
|
||||
);
|
||||
|
||||
|
|
@ -3096,11 +3084,8 @@
|
|||
|
||||
//}
|
||||
|
||||
if ($nick != "") {
|
||||
q("UPDATE `unique_contacts` SET `nick` = '%s' WHERE `nick` != '%s' AND url = '%s'",
|
||||
dbesc($nick), dbesc($nick), dbesc(normalise_link($profile)));
|
||||
if ($nick != "")
|
||||
return($nick);
|
||||
}
|
||||
|
||||
return(false);
|
||||
}
|
||||
|
|
@ -3431,6 +3416,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("<auto>", $type, array('$notes' => $notes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set notification as seen and returns associated item (if possible)
|
||||
*
|
||||
* POST request with 'id' param as notification id
|
||||
*
|
||||
* @param App $a
|
||||
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
|
||||
* @return string
|
||||
*/
|
||||
function api_friendica_notification_seen(&$a, $type){
|
||||
if (api_user()===false) throw new ForbiddenException();
|
||||
if ($a->argc!==4) throw new BadRequestException("Invalid argument count");
|
||||
|
||||
$id = (x($_REQUEST, 'id') ? intval($_REQUEST['id']) : 0);
|
||||
|
||||
$nm = new NotificationsManager();
|
||||
$note = $nm->getByID($id);
|
||||
if (is_null($note)) throw new BadRequestException("Invalid argument");
|
||||
|
||||
$nm->setSeen($note);
|
||||
if ($note['otype']=='item') {
|
||||
// would be really better with an ItemsManager and $im->getByID() :-P
|
||||
$r = q("SELECT * FROM `item` WHERE `id`=%d AND `uid`=%d",
|
||||
intval($note['iid']),
|
||||
intval(local_user())
|
||||
);
|
||||
if ($r!==false) {
|
||||
// we found the item, return it to the user
|
||||
$user_info = api_get_user($a);
|
||||
$ret = api_format_items($r,$user_info);
|
||||
$data = array('$statuses' => $ret);
|
||||
return api_apply_template("timeline", $type, $data);
|
||||
}
|
||||
// the item can't be found, but we set the note as seen, so we count this as a success
|
||||
}
|
||||
return api_apply_template('<auto>', $type, array('status' => "success"));
|
||||
}
|
||||
|
||||
api_register_func('api/friendica/notification/seen', 'api_friendica_notification_seen', true, API_METHOD_POST);
|
||||
api_register_func('api/friendica/notification', 'api_friendica_notification', true, API_METHOD_GET);
|
||||
|
||||
|
||||
/*
|
||||
To.Do:
|
||||
[pagename] => api/1.1/statuses/lookup.json
|
||||
|
|
|
|||
69
include/autoloader.php
Normal file
69
include/autoloader.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
/**
|
||||
* @file include/autoloader.php
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief composer-derived autoloader init
|
||||
**/
|
||||
class FriendicaAutoloaderInit
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/autoloader/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('FriendicaAutoloaderInit', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('FriendicaAutoloaderInit', 'loadClassLoader'));
|
||||
|
||||
// library
|
||||
$map = require __DIR__ . '/autoloader/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$map = require __DIR__ . '/autoloader/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoloader/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
$includeFiles = require __DIR__ . '/autoloader/autoload_files.php';
|
||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
||||
friendicaRequire($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
|
||||
function friendicaRequire($fileIdentifier, $file)
|
||||
{
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
require $file;
|
||||
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return FriendicaAutoloaderInit::getLoader();
|
||||
413
include/autoloader/ClassLoader.php
Normal file
413
include/autoloader/ClassLoader.php
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE.composer
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0 class loader
|
||||
*
|
||||
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
|
||||
if ('\\' == $class[0]) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if ($file === null && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if ($file === null) {
|
||||
// Remember that this class does not exist.
|
||||
return $this->classMap[$class] = false;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
|
||||
if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
19
include/autoloader/LICENSE.composer
Normal file
19
include/autoloader/LICENSE.composer
Normal file
|
|
@ -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.
|
||||
9
include/autoloader/autoload_classmap.php
Normal file
9
include/autoloader/autoload_classmap.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
10
include/autoloader/autoload_files.php
Normal file
10
include/autoloader/autoload_files.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
|
||||
);
|
||||
10
include/autoloader/autoload_namespaces.php
Normal file
10
include/autoloader/autoload_namespaces.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
|
||||
);
|
||||
9
include/autoloader/autoload_psr4.php
Normal file
9
include/autoloader/autoload_psr4.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
|
|
@ -3,6 +3,7 @@ require_once("include/oembed.php");
|
|||
require_once('include/event.php');
|
||||
require_once('include/map.php');
|
||||
require_once('mod/proxy.php');
|
||||
require_once('include/Contact.php');
|
||||
|
||||
function bb_PictureCacheExt($matches) {
|
||||
if (strpos($matches[3], "data:image/") === 0)
|
||||
|
|
@ -541,8 +542,23 @@ function bb_ShareAttributes($share, $simplehtml) {
|
|||
$reldate = (($posted) ? " " . relative_date($posted) : '');
|
||||
}
|
||||
|
||||
$userid = GetProfileUsername($profile,$author, false);
|
||||
$userid_compact = GetProfileUsername($profile,$author, true);
|
||||
$data = get_contact_details_by_url($profile);
|
||||
|
||||
if (isset($data["name"]) AND isset($data["addr"]))
|
||||
$userid_compact = $data["name"]." (".$data["addr"].")";
|
||||
else
|
||||
$userid_compact = GetProfileUsername($profile,$author, true);
|
||||
|
||||
if (isset($data["addr"]))
|
||||
$userid = $data["addr"];
|
||||
else
|
||||
$userid = GetProfileUsername($profile,$author, false);
|
||||
|
||||
if (isset($data["name"]))
|
||||
$author = $data["name"];
|
||||
|
||||
if (isset($data["photo"]))
|
||||
$avatar = $data["photo"];
|
||||
|
||||
$preshare = trim($share[1]);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ if(! function_exists('load_config')) {
|
|||
function load_config($family) {
|
||||
global $a;
|
||||
|
||||
$r = q("SELECT * FROM `config` WHERE `cat` = '%s'", dbesc($family));
|
||||
$r = q("SELECT `v`, `k` FROM `config` WHERE `cat` = '%s'", dbesc($family));
|
||||
if(count($r)) {
|
||||
foreach($r as $rr) {
|
||||
$k = $rr['k'];
|
||||
|
|
@ -170,7 +170,7 @@ function set_config($family,$key,$value) {
|
|||
if(! function_exists('load_pconfig')) {
|
||||
function load_pconfig($uid,$family) {
|
||||
global $a;
|
||||
$r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
|
||||
$r = q("SELECT `v`,`k` FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
|
||||
dbesc($family),
|
||||
intval($uid)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -811,16 +811,16 @@ function best_link_url($item,&$sparkle,$ssl_state = false) {
|
|||
if((local_user()) && (local_user() == $item['uid'])) {
|
||||
if(isset($a->contacts) && x($a->contacts,$clean_url)) {
|
||||
if($a->contacts[$clean_url]['network'] === NETWORK_DFRN) {
|
||||
$best_url = $a->get_baseurl($ssl_state) . '/redir/' . $a->contacts[$clean_url]['id'];
|
||||
$best_url = 'redir/'.$a->contacts[$clean_url]['id'];
|
||||
$sparkle = true;
|
||||
} else
|
||||
$best_url = $a->contacts[$clean_url]['url'];
|
||||
}
|
||||
} elseif (local_user()) {
|
||||
$r = q("SELECT `id`, `network` FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` = '%s'",
|
||||
$r = q("SELECT `id` FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` = '%s' LIMIT 1",
|
||||
dbesc(NETWORK_DFRN), intval(local_user()), dbesc(normalise_link($clean_url)));
|
||||
if ($r) {
|
||||
$best_url = $a->get_baseurl($ssl_state).'/redir/'.$r[0]['id'];
|
||||
$best_url = 'redir/'.$r[0]['id'];
|
||||
$sparkle = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -876,7 +876,7 @@ function item_photo_menu($item){
|
|||
if(local_user() && local_user() == $item['uid'] && link_compare($item['url'],$item['author-link'])) {
|
||||
$cid = $item['contact-id'];
|
||||
} else {
|
||||
$r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' ORDER BY `uid` DESC LIMIT 1",
|
||||
$r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' LIMIT 1",
|
||||
intval(local_user()), dbesc(normalise_link($item['author-link'])));
|
||||
if ($r) {
|
||||
$cid = $r[0]["id"];
|
||||
|
|
|
|||
205
include/cron.php
205
include/cron.php
|
|
@ -30,7 +30,6 @@ function cron_run(&$argv, &$argc){
|
|||
|
||||
require_once('include/session.php');
|
||||
require_once('include/datetime.php');
|
||||
require_once('library/simplepie/simplepie.inc');
|
||||
require_once('include/items.php');
|
||||
require_once('include/Contact.php');
|
||||
require_once('include/email.php');
|
||||
|
|
@ -133,9 +132,8 @@ function cron_run(&$argv, &$argc){
|
|||
// Check every conversation
|
||||
check_conversations(false);
|
||||
|
||||
// Follow your friends from your legacy OStatus account
|
||||
// Doesn't work
|
||||
// ostatus_check_follow_friends();
|
||||
// Set the gcontact-id in the item table if missing
|
||||
item_set_gcontact();
|
||||
|
||||
// update nodeinfo data
|
||||
nodeinfo_cron();
|
||||
|
|
@ -159,75 +157,14 @@ function cron_run(&$argv, &$argc){
|
|||
proc_run('php','include/expire.php');
|
||||
}
|
||||
|
||||
$last = get_config('system','cache_last_cleared');
|
||||
// Clear cache entries
|
||||
cron_clear_cache($a);
|
||||
|
||||
if($last) {
|
||||
$next = $last + (3600); // Once per hour
|
||||
$clear_cache = ($next <= time());
|
||||
} else
|
||||
$clear_cache = true;
|
||||
// Repair missing Diaspora values in contacts
|
||||
cron_repair_diaspora($a);
|
||||
|
||||
if ($clear_cache) {
|
||||
// clear old cache
|
||||
Cache::clear();
|
||||
|
||||
// clear old item cache files
|
||||
clear_cache();
|
||||
|
||||
// clear cache for photos
|
||||
clear_cache($a->get_basepath(), $a->get_basepath()."/photo");
|
||||
|
||||
// clear smarty cache
|
||||
clear_cache($a->get_basepath()."/view/smarty3/compiled", $a->get_basepath()."/view/smarty3/compiled");
|
||||
|
||||
// clear cache for image proxy
|
||||
if (!get_config("system", "proxy_disabled")) {
|
||||
clear_cache($a->get_basepath(), $a->get_basepath()."/proxy");
|
||||
|
||||
$cachetime = get_config('system','proxy_cache_time');
|
||||
if (!$cachetime) $cachetime = PROXY_DEFAULT_TIME;
|
||||
|
||||
q('DELETE FROM `photo` WHERE `uid` = 0 AND `resource-id` LIKE "pic:%%" AND `created` < NOW() - INTERVAL %d SECOND', $cachetime);
|
||||
}
|
||||
|
||||
// Maximum table size in megabyte
|
||||
$max_tablesize = intval(get_config('system','optimize_max_tablesize')) * 1000000;
|
||||
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%
|
||||
|
||||
// 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 empty tables
|
||||
if ($table["Data_length"] == 0)
|
||||
continue;
|
||||
|
||||
// Calculate fragmentation
|
||||
$fragmentation = $table["Data_free"] / $table["Data_length"];
|
||||
|
||||
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;
|
||||
|
||||
// So optimize it
|
||||
logger("Optimize Table ".$table["Name"], LOGGER_DEBUG);
|
||||
q("OPTIMIZE TABLE `%s`", dbesc($table["Name"]));
|
||||
}
|
||||
|
||||
set_config('system','cache_last_cleared', time());
|
||||
}
|
||||
// Repair entries in the database
|
||||
cron_repair_database();
|
||||
|
||||
$manual_id = 0;
|
||||
$generation = 0;
|
||||
|
|
@ -373,6 +310,132 @@ function cron_run(&$argv, &$argc){
|
|||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear cache entries
|
||||
*
|
||||
* @param App $a
|
||||
*/
|
||||
function cron_clear_cache(&$a) {
|
||||
|
||||
$last = get_config('system','cache_last_cleared');
|
||||
|
||||
if($last) {
|
||||
$next = $last + (3600); // Once per hour
|
||||
$clear_cache = ($next <= time());
|
||||
} else
|
||||
$clear_cache = true;
|
||||
|
||||
if (!$clear_cache)
|
||||
return;
|
||||
|
||||
// clear old cache
|
||||
Cache::clear();
|
||||
|
||||
// clear old item cache files
|
||||
clear_cache();
|
||||
|
||||
// clear cache for photos
|
||||
clear_cache($a->get_basepath(), $a->get_basepath()."/photo");
|
||||
|
||||
// clear smarty cache
|
||||
clear_cache($a->get_basepath()."/view/smarty3/compiled", $a->get_basepath()."/view/smarty3/compiled");
|
||||
|
||||
// clear cache for image proxy
|
||||
if (!get_config("system", "proxy_disabled")) {
|
||||
clear_cache($a->get_basepath(), $a->get_basepath()."/proxy");
|
||||
|
||||
$cachetime = get_config('system','proxy_cache_time');
|
||||
if (!$cachetime) $cachetime = PROXY_DEFAULT_TIME;
|
||||
|
||||
q('DELETE FROM `photo` WHERE `uid` = 0 AND `resource-id` LIKE "pic:%%" AND `created` < NOW() - INTERVAL %d SECOND', $cachetime);
|
||||
}
|
||||
|
||||
// Delete the cached OEmbed entries that are older than one year
|
||||
q("DELETE FROM `oembed` WHERE `created` < NOW() - INTERVAL 1 YEAR");
|
||||
|
||||
// Delete the cached "parse_url" entries that are older than one year
|
||||
q("DELETE FROM `parsed_url` WHERE `created` < NOW() - INTERVAL 1 YEAR");
|
||||
|
||||
// Maximum table size in megabyte
|
||||
$max_tablesize = intval(get_config('system','optimize_max_tablesize')) * 1000000;
|
||||
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%
|
||||
|
||||
// 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 empty tables
|
||||
if ($table["Data_length"] == 0)
|
||||
continue;
|
||||
|
||||
// Calculate fragmentation
|
||||
$fragmentation = $table["Data_free"] / $table["Data_length"];
|
||||
|
||||
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;
|
||||
|
||||
// So optimize it
|
||||
logger("Optimize Table ".$table["Name"], LOGGER_DEBUG);
|
||||
q("OPTIMIZE TABLE `%s`", dbesc($table["Name"]));
|
||||
}
|
||||
|
||||
set_config('system','cache_last_cleared', time());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Repair missing values in Diaspora contacts
|
||||
*
|
||||
* @param App $a
|
||||
*/
|
||||
function cron_repair_diaspora(&$a) {
|
||||
$r = q("SELECT `id`, `url` FROM `contact`
|
||||
WHERE `network` = '%s' AND (`batch` = '' OR `notify` = '' OR `poll` = '' OR pubkey = '')
|
||||
ORDER BY RAND() LIMIT 50", dbesc(NETWORK_DIASPORA));
|
||||
if ($r) {
|
||||
foreach ($r AS $contact) {
|
||||
if (poco_reachable($contact["url"])) {
|
||||
$data = probe_url($contact["url"]);
|
||||
if ($data["network"] == NETWORK_DIASPORA) {
|
||||
logger("Repair contact ".$contact["id"]." ".$contact["url"], LOGGER_DEBUG);
|
||||
q("UPDATE `contact` SET `batch` = '%s', `notify` = '%s', `poll` = '%s', pubkey = '%s' WHERE `id` = %d",
|
||||
dbesc($data["batch"]), dbesc($data["notify"]), dbesc($data["poll"]), dbesc($data["pubkey"]),
|
||||
intval($contact["id"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Do some repairs in database entries
|
||||
*
|
||||
*/
|
||||
function cron_repair_database() {
|
||||
|
||||
// Set the parent if it wasn't set. (Shouldn't happen - but does sometimes)
|
||||
// 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");
|
||||
|
||||
/// @todo
|
||||
/// - remove thread entries without item
|
||||
/// - remove sign entries without item
|
||||
/// - remove children when parent got lost
|
||||
/// - set contact-id in item when not present
|
||||
}
|
||||
|
||||
if (array_search(__file__,get_included_files())===0){
|
||||
cron_run($_SERVER["argv"],$_SERVER["argc"]);
|
||||
killme();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
<?php
|
||||
/**
|
||||
* @file include/datetime.php
|
||||
* @brief Some functions for date and time related tasks.
|
||||
*/
|
||||
|
||||
// two-level sort for timezones.
|
||||
|
||||
if(! function_exists('timezone_cmp')) {
|
||||
/**
|
||||
* @brief Two-level sort for timezones.
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
* @return int
|
||||
*/
|
||||
function timezone_cmp($a, $b) {
|
||||
if(strstr($a,'/') && strstr($b,'/')) {
|
||||
if ( t($a) == t($b)) return 0;
|
||||
|
|
@ -11,11 +20,16 @@ function timezone_cmp($a, $b) {
|
|||
if(strstr($a,'/')) return -1;
|
||||
if(strstr($b,'/')) return 1;
|
||||
if ( t($a) == t($b)) return 0;
|
||||
return ( t($a) < t($b)) ? -1 : 1;
|
||||
}}
|
||||
|
||||
// emit a timezone selector grouped (primarily) by continent
|
||||
if(! function_exists('select_timezone')) {
|
||||
return ( t($a) < t($b)) ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emit a timezone selector grouped (primarily) by continent
|
||||
*
|
||||
* @param string $current Timezone
|
||||
* @return string Parsed HTML output
|
||||
*/
|
||||
function select_timezone($current = 'America/Los_Angeles') {
|
||||
|
||||
$timezone_identifiers = DateTimeZone::listIdentifiers();
|
||||
|
|
@ -52,13 +66,25 @@ function select_timezone($current = 'America/Los_Angeles') {
|
|||
}
|
||||
$o .= '</optgroup></select>';
|
||||
return $o;
|
||||
}}
|
||||
}
|
||||
|
||||
// return a select using 'field_select_raw' template, with timezones
|
||||
// groupped (primarily) by continent
|
||||
// arguments follow convetion as other field_* template array:
|
||||
// 'name', 'label', $value, 'help'
|
||||
if (!function_exists('field_timezone')){
|
||||
|
||||
|
||||
/**
|
||||
* @brief Generating a Timezone selector
|
||||
*
|
||||
* Return a select using 'field_select_raw' template, with timezones
|
||||
* groupped (primarily) by continent
|
||||
* arguments follow convetion as other field_* template array:
|
||||
* 'name', 'label', $value, 'help'
|
||||
*
|
||||
* @param string $name Name of the selector
|
||||
* @param string $label Label for the selector
|
||||
* @param string $current Timezone
|
||||
* @param string $help Help text
|
||||
*
|
||||
* @return string Parsed HTML
|
||||
*/
|
||||
function field_timezone($name='timezone', $label='', $current = 'America/Los_Angeles', $help){
|
||||
$options = select_timezone($current);
|
||||
$options = str_replace('<select id="timezone_select" name="timezone">','', $options);
|
||||
|
|
@ -69,15 +95,19 @@ function field_timezone($name='timezone', $label='', $current = 'America/Los_Ang
|
|||
'$field' => array($name, $label, $current, $help, $options),
|
||||
));
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
// General purpose date parse/convert function.
|
||||
// $from = source timezone
|
||||
// $to = dest timezone
|
||||
// $s = some parseable date/time string
|
||||
// $fmt = output format
|
||||
|
||||
if(! function_exists('datetime_convert')) {
|
||||
/**
|
||||
* @brief General purpose date parse/convert function.
|
||||
*
|
||||
* @param string $from Source timezone
|
||||
* @param string $to Dest timezone
|
||||
* @param string $s Some parseable date/time string
|
||||
* @param string $fmt Output format recognised from php's DateTime class
|
||||
* http://www.php.net/manual/en/datetime.format.php
|
||||
*
|
||||
* @return string Formatted date according to given format
|
||||
*/
|
||||
function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d H:i:s") {
|
||||
|
||||
// Defaults to UTC if nothing is set, but throws an exception if set to empty string.
|
||||
|
|
@ -123,14 +153,20 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d
|
|||
}
|
||||
|
||||
$d->setTimeZone($to_obj);
|
||||
|
||||
return($d->format($fmt));
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
// wrapper for date selector, tailored for use in birthday fields
|
||||
|
||||
/**
|
||||
* @brief Wrapper for date selector, tailored for use in birthday fields.
|
||||
*
|
||||
* @param string $dob Date of Birth
|
||||
* @return string
|
||||
*/
|
||||
function dob($dob) {
|
||||
list($year,$month,$day) = sscanf($dob,'%4d-%2d-%2d');
|
||||
|
||||
$f = get_config('system','birthday_input_format');
|
||||
if(! $f)
|
||||
$f = 'ymd';
|
||||
|
|
@ -138,62 +174,69 @@ function dob($dob) {
|
|||
$value = '';
|
||||
else
|
||||
$value = (($year) ? datetime_convert('UTC','UTC',$dob,'Y-m-d') : datetime_convert('UTC','UTC',$dob,'m-d'));
|
||||
|
||||
$o = '<input type="text" name="dob" value="' . $value . '" placeholder="' . t('YYYY-MM-DD or MM-DD') . '" />';
|
||||
|
||||
// if ($dob && $dob != '0000-00-00')
|
||||
// $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),mktime(0,0,0,$month,$day,$year),'dob');
|
||||
// else
|
||||
// $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),false,'dob');
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a date selector
|
||||
* @param $format
|
||||
* format string, e.g. 'ymd' or 'mdy'. Not currently supported
|
||||
* @param $min
|
||||
* unix timestamp of minimum date
|
||||
* @param $max
|
||||
* unix timestap of maximum date
|
||||
* @param $default
|
||||
* unix timestamp of default date
|
||||
* @param $id
|
||||
* id and name of datetimepicker (defaults to "datetimepicker")
|
||||
* @brief Returns a date selector
|
||||
*
|
||||
* @param string $format
|
||||
* Format string, e.g. 'ymd' or 'mdy'. Not currently supported
|
||||
* @param string $min
|
||||
* Unix timestamp of minimum date
|
||||
* @param string $max
|
||||
* Unix timestap of maximum date
|
||||
* @param string $default
|
||||
* Unix timestamp of default date
|
||||
* @param string $id
|
||||
* ID and name of datetimepicker (defaults to "datetimepicker")
|
||||
*
|
||||
* @return string Parsed HTML output.
|
||||
*/
|
||||
if(! function_exists('datesel')) {
|
||||
function datesel($format, $min, $max, $default, $id = 'datepicker') {
|
||||
return datetimesel($format,$min,$max,$default,$id,true,false, '','');
|
||||
}}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a time selector
|
||||
* @param $format
|
||||
* format string, e.g. 'ymd' or 'mdy'. Not currently supported
|
||||
* @brief Returns a time selector
|
||||
*
|
||||
* @param string $format
|
||||
* Format string, e.g. 'ymd' or 'mdy'. Not currently supported
|
||||
* @param $h
|
||||
* already selected hour
|
||||
* Already selected hour
|
||||
* @param $m
|
||||
* already selected minute
|
||||
* @param $id
|
||||
* id and name of datetimepicker (defaults to "timepicker")
|
||||
* Already selected minute
|
||||
* @param string $id
|
||||
* ID and name of datetimepicker (defaults to "timepicker")
|
||||
*
|
||||
* @return string Parsed HTML output.
|
||||
*/
|
||||
if(! function_exists('timesel')) {
|
||||
function timesel($format, $h, $m, $id='timepicker') {
|
||||
return datetimesel($format,new DateTime(),new DateTime(),new DateTime("$h:$m"),$id,false,true);
|
||||
}}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a datetime selector.
|
||||
*
|
||||
* @param $format
|
||||
* @param string $format
|
||||
* format string, e.g. 'ymd' or 'mdy'. Not currently supported
|
||||
* @param $min
|
||||
* @param string $min
|
||||
* unix timestamp of minimum date
|
||||
* @param $max
|
||||
* @param string $max
|
||||
* unix timestap of maximum date
|
||||
* @param $default
|
||||
* @param string $default
|
||||
* unix timestamp of default date
|
||||
* @param string $id
|
||||
* id and name of datetimepicker (defaults to "datetimepicker")
|
||||
* @param boolean $pickdate
|
||||
* @param bool $pickdate
|
||||
* true to show date picker (default)
|
||||
* @param boolean $picktime
|
||||
* true to show time picker (default)
|
||||
|
|
@ -201,17 +244,15 @@ function timesel($format, $h, $m, $id='timepicker') {
|
|||
* set minimum date from picker with id $minfrom (none by default)
|
||||
* @param $maxfrom
|
||||
* set maximum date from picker with id $maxfrom (none by default)
|
||||
* @param boolean $required default false
|
||||
* @param bool $required default false
|
||||
*
|
||||
* @return string Parsed HTML output.
|
||||
*
|
||||
* @todo Once browser support is better this could probably be replaced with
|
||||
* native HTML5 date picker.
|
||||
*/
|
||||
if(! function_exists('datetimesel')) {
|
||||
function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pickdate = true, $picktime = true, $minfrom = '', $maxfrom = '', $required = false) {
|
||||
|
||||
$a = get_app();
|
||||
|
||||
// First day of the week (0 = Sunday)
|
||||
$firstDay = get_pconfig(local_user(),'system','first_day_of_week');
|
||||
if ($firstDay === false) $firstDay=0;
|
||||
|
|
@ -224,43 +265,58 @@ function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pic
|
|||
|
||||
$o = '';
|
||||
$dateformat = '';
|
||||
|
||||
if($pickdate) $dateformat .= 'Y-m-d';
|
||||
if($pickdate && $picktime) $dateformat .= ' ';
|
||||
if($picktime) $dateformat .= 'H:i';
|
||||
|
||||
$minjs = $min ? ",minDate: new Date({$min->getTimestamp()}*1000), yearStart: " . $min->format('Y') : '';
|
||||
$maxjs = $max ? ",maxDate: new Date({$max->getTimestamp()}*1000), yearEnd: " . $max->format('Y') : '';
|
||||
|
||||
$input_text = $default ? 'value="' . date($dateformat, $default->getTimestamp()) . '"' : '';
|
||||
$defaultdatejs = $default ? ",defaultDate: new Date({$default->getTimestamp()}*1000)" : '';
|
||||
|
||||
$pickers = '';
|
||||
if(!$pickdate) $pickers .= ',datepicker: false';
|
||||
if(!$picktime) $pickers .= ',timepicker: false';
|
||||
|
||||
$extra_js = '';
|
||||
$pickers .= ",dayOfWeekStart: ".$firstDay.",lang:'".$lang."'";
|
||||
if($minfrom != '')
|
||||
$extra_js .= "\$('#$minfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({minDate: currentDateTime})}})";
|
||||
if($maxfrom != '')
|
||||
$extra_js .= "\$('#$maxfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({maxDate: currentDateTime})}})";
|
||||
|
||||
$readable_format = $dateformat;
|
||||
$readable_format = str_replace('Y','yyyy',$readable_format);
|
||||
$readable_format = str_replace('m','mm',$readable_format);
|
||||
$readable_format = str_replace('d','dd',$readable_format);
|
||||
$readable_format = str_replace('H','HH',$readable_format);
|
||||
$readable_format = str_replace('i','MM',$readable_format);
|
||||
|
||||
$o .= "<div class='date'><input type='text' placeholder='$readable_format' name='$id' id='$id' $input_text />";
|
||||
$o .= '</div>';
|
||||
$o .= "<script type='text/javascript'>";
|
||||
$o .= "\$(function () {var picker = \$('#$id').datetimepicker({step:5,format:'$dateformat' $minjs $maxjs $pickers $defaultdatejs}); $extra_js})";
|
||||
$o .= "</script>";
|
||||
|
||||
return $o;
|
||||
}}
|
||||
}
|
||||
|
||||
// implements "3 seconds ago" etc.
|
||||
// based on $posted_date, (UTC).
|
||||
// Results relative to current timezone
|
||||
// Limited to range of timestamps
|
||||
|
||||
if(! function_exists('relative_date')) {
|
||||
/**
|
||||
* @brief Returns a relative date string.
|
||||
*
|
||||
* Implements "3 seconds ago" etc.
|
||||
* Based on $posted_date, (UTC).
|
||||
* Results relative to current timezone.
|
||||
* Limited to range of timestamps.
|
||||
*
|
||||
* @param string $posted_date
|
||||
* @param string $format (optional) Parsed with sprintf()
|
||||
* <tt>%1$d %2$s ago</tt>, e.g. 22 hours ago, 1 minute ago
|
||||
*
|
||||
* @return string with relative date
|
||||
*/
|
||||
function relative_date($posted_date,$format = null) {
|
||||
|
||||
$localtime = datetime_convert('UTC',date_default_timezone_get(),$posted_date);
|
||||
|
|
@ -300,23 +356,33 @@ function relative_date($posted_date,$format = null) {
|
|||
// translators - e.g. 22 hours ago, 1 minute ago
|
||||
if(! $format)
|
||||
$format = t('%1$d %2$s ago');
|
||||
|
||||
return sprintf( $format,$r, (($r == 1) ? $str[0] : $str[1]));
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
|
||||
// Returns age in years, given a date of birth,
|
||||
// the timezone of the person whose date of birth is provided,
|
||||
// and the timezone of the person viewing the result.
|
||||
// Why? Bear with me. Let's say I live in Mittagong, Australia, and my
|
||||
// birthday is on New Year's. You live in San Bruno, California.
|
||||
// When exactly are you going to see my age increase?
|
||||
// A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start
|
||||
// celebrating and become a year older. If you wish me happy birthday
|
||||
// on January 1 (San Bruno time), you'll be a day late.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns timezone correct age in years.
|
||||
*
|
||||
* Returns the age in years, given a date of birth, the timezone of the person
|
||||
* whose date of birth is provided, and the timezone of the person viewing the
|
||||
* result.
|
||||
*
|
||||
* Why? Bear with me. Let's say I live in Mittagong, Australia, and my birthday
|
||||
* is on New Year's. You live in San Bruno, California.
|
||||
* When exactly are you going to see my age increase?
|
||||
*
|
||||
* A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start celebrating
|
||||
* and become a year older. If you wish me happy birthday on January 1
|
||||
* (San Bruno time), you'll be a day late.
|
||||
*
|
||||
* @param string $dob Date of Birth
|
||||
* @param string $owner_tz (optional) Timezone of the person of interest
|
||||
* @param string $viewer_tz (optional) Timezone of the person viewing
|
||||
*
|
||||
* @return int Age in years
|
||||
*/
|
||||
function age($dob,$owner_tz = '',$viewer_tz = '') {
|
||||
if(! intval($dob))
|
||||
return 0;
|
||||
|
|
@ -333,64 +399,79 @@ function age($dob,$owner_tz = '',$viewer_tz = '') {
|
|||
|
||||
if(($curr_month < $month) || (($curr_month == $month) && ($curr_day < $day)))
|
||||
$year_diff--;
|
||||
|
||||
return $year_diff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Get days in month
|
||||
// get_dim($year, $month);
|
||||
// returns number of days.
|
||||
// $month[1] = 'January';
|
||||
// to match human usage.
|
||||
|
||||
if(! function_exists('get_dim')) {
|
||||
/**
|
||||
* @brief Get days of a month in a given year.
|
||||
*
|
||||
* Returns number of days in the month of the given year.
|
||||
* $m = 1 is 'January' to match human usage.
|
||||
*
|
||||
* @param int $y Year
|
||||
* @param int $m Month (1=January, 12=December)
|
||||
*
|
||||
* @return int Number of days in the given month
|
||||
*/
|
||||
function get_dim($y,$m) {
|
||||
|
||||
$dim = array( 0,
|
||||
31, 28, 31, 30, 31, 30,
|
||||
31, 31, 30, 31, 30, 31);
|
||||
|
||||
if($m != 2)
|
||||
return $dim[$m];
|
||||
if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0))
|
||||
return 29;
|
||||
return $dim[2];
|
||||
}}
|
||||
$dim = array( 0,
|
||||
31, 28, 31, 30, 31, 30,
|
||||
31, 31, 30, 31, 30, 31);
|
||||
|
||||
if($m != 2)
|
||||
return $dim[$m];
|
||||
|
||||
// Returns the first day in month for a given month, year
|
||||
// get_first_dim($year,$month)
|
||||
// returns 0 = Sunday through 6 = Saturday
|
||||
// Months start at 1.
|
||||
if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0))
|
||||
return 29;
|
||||
|
||||
if(! function_exists('get_first_dim')) {
|
||||
return $dim[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the first day in month for a given month, year.
|
||||
*
|
||||
* Months start at 1.
|
||||
*
|
||||
* @param int $y Year
|
||||
* @param int $m Month (1=January, 12=December)
|
||||
*
|
||||
* @return string day 0 = Sunday through 6 = Saturday
|
||||
*/
|
||||
function get_first_dim($y,$m) {
|
||||
$d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m));
|
||||
return datetime_convert('UTC','UTC',$d,'w');
|
||||
}}
|
||||
$d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m));
|
||||
|
||||
// output a calendar for the given month, year.
|
||||
// if $links are provided (array), e.g. $links[12] => 'http://mylink' ,
|
||||
// date 12 will be linked appropriately. Today's date is also noted by
|
||||
// altering td class.
|
||||
// Months count from 1.
|
||||
return datetime_convert('UTC','UTC',$d,'w');
|
||||
}
|
||||
|
||||
|
||||
/// @TODO Provide (prev,next) links, define class variations for different size calendars
|
||||
|
||||
|
||||
if(! function_exists('cal')) {
|
||||
/**
|
||||
* @brief Output a calendar for the given month, year.
|
||||
*
|
||||
* If $links are provided (array), e.g. $links[12] => 'http://mylink' ,
|
||||
* date 12 will be linked appropriately. Today's date is also noted by
|
||||
* altering td class.
|
||||
* Months count from 1.
|
||||
*
|
||||
* @param int $y Year
|
||||
* @param int $m Month
|
||||
* @param bool $links (default false)
|
||||
* @param string $class
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @todo Provide (prev,next) links, define class variations for different size calendars
|
||||
*/
|
||||
function cal($y = 0,$m = 0, $links = false, $class='') {
|
||||
|
||||
|
||||
// month table - start at 1 to match human usage.
|
||||
|
||||
$mtab = array(' ',
|
||||
'January','February','March',
|
||||
'April','May','June',
|
||||
'July','August','September',
|
||||
'October','November','December'
|
||||
'January','February','March',
|
||||
'April','May','June',
|
||||
'July','August','September',
|
||||
'October','November','December'
|
||||
);
|
||||
|
||||
$thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y');
|
||||
|
|
@ -400,54 +481,63 @@ function cal($y = 0,$m = 0, $links = false, $class='') {
|
|||
if(! $m)
|
||||
$m = intval($thismonth);
|
||||
|
||||
$dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
|
||||
$f = get_first_dim($y,$m);
|
||||
$l = get_dim($y,$m);
|
||||
$d = 1;
|
||||
$dow = 0;
|
||||
$started = false;
|
||||
$dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
|
||||
$f = get_first_dim($y,$m);
|
||||
$l = get_dim($y,$m);
|
||||
$d = 1;
|
||||
$dow = 0;
|
||||
$started = false;
|
||||
|
||||
if(($y == $thisyear) && ($m == $thismonth))
|
||||
$tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j'));
|
||||
if(($y == $thisyear) && ($m == $thismonth))
|
||||
$tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j'));
|
||||
|
||||
$str_month = day_translate($mtab[$m]);
|
||||
$o = '<table class="calendar' . $class . '">';
|
||||
$o .= "<caption>$str_month $y</caption><tr>";
|
||||
for($a = 0; $a < 7; $a ++)
|
||||
$o .= '<th>' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '</th>';
|
||||
$o .= '</tr><tr>';
|
||||
$o = '<table class="calendar' . $class . '">';
|
||||
$o .= "<caption>$str_month $y</caption><tr>";
|
||||
for($a = 0; $a < 7; $a ++)
|
||||
$o .= '<th>' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '</th>';
|
||||
|
||||
while($d <= $l) {
|
||||
if(($dow == $f) && (! $started))
|
||||
$started = true;
|
||||
$today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : '');
|
||||
$o .= "<td $today>";
|
||||
$day = str_replace(' ',' ',sprintf('%2.2d', $d));
|
||||
if($started) {
|
||||
if(is_array($links) && isset($links[$d]))
|
||||
$o .= "<a href=\"{$links[$d]}\">$day</a>";
|
||||
else
|
||||
$o .= $day;
|
||||
$d ++;
|
||||
}
|
||||
else
|
||||
$o .= ' ';
|
||||
$o .= '</td>';
|
||||
$dow ++;
|
||||
if(($dow == 7) && ($d <= $l)) {
|
||||
$dow = 0;
|
||||
$o .= '</tr><tr>';
|
||||
}
|
||||
}
|
||||
if($dow)
|
||||
for($a = $dow; $a < 7; $a ++)
|
||||
$o .= '<td> </td>';
|
||||
$o .= '</tr></table>'."\r\n";
|
||||
$o .= '</tr><tr>';
|
||||
|
||||
return $o;
|
||||
}}
|
||||
while($d <= $l) {
|
||||
if(($dow == $f) && (! $started))
|
||||
$started = true;
|
||||
|
||||
$today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : '');
|
||||
$o .= "<td $today>";
|
||||
$day = str_replace(' ',' ',sprintf('%2.2d', $d));
|
||||
if($started) {
|
||||
if(is_array($links) && isset($links[$d]))
|
||||
$o .= "<a href=\"{$links[$d]}\">$day</a>";
|
||||
else
|
||||
$o .= $day;
|
||||
|
||||
$d ++;
|
||||
} else {
|
||||
$o .= ' ';
|
||||
}
|
||||
|
||||
$o .= '</td>';
|
||||
$dow ++;
|
||||
if(($dow == 7) && ($d <= $l)) {
|
||||
$dow = 0;
|
||||
$o .= '</tr><tr>';
|
||||
}
|
||||
}
|
||||
if($dow)
|
||||
for($a = $dow; $a < 7; $a ++)
|
||||
$o .= '<td> </td>';
|
||||
|
||||
$o .= '</tr></table>'."\r\n";
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a birthday event.
|
||||
*
|
||||
* Update the year and the birthday.
|
||||
*/
|
||||
function update_contact_birthdays() {
|
||||
|
||||
// This only handles foreign or alien networks where a birthday has been provided.
|
||||
|
|
@ -474,8 +564,6 @@ function update_contact_birthdays() {
|
|||
$bdtext = sprintf( t('%s\'s birthday'), $rr['name']);
|
||||
$bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $rr['url'] . ']' . $rr['name'] . '[/url]') ;
|
||||
|
||||
|
||||
|
||||
$r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`,`adjust`)
|
||||
VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d' ) ",
|
||||
intval($rr['uid']),
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ function db_definition() {
|
|||
"keywords" => array("type" => "text", "not null" => "1"),
|
||||
"gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
|
||||
"attag" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"photo" => array("type" => "text", "not null" => "1"),
|
||||
"thumb" => array("type" => "text", "not null" => "1"),
|
||||
"micro" => array("type" => "text", "not null" => "1"),
|
||||
|
|
@ -666,9 +667,14 @@ function db_definition() {
|
|||
"about" => array("type" => "text", "not null" => "1"),
|
||||
"keywords" => array("type" => "text", "not null" => "1"),
|
||||
"gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
|
||||
"birthday" => array("type" => "varchar(32)", "not null" => "1", "default" => "0000-00-00"),
|
||||
"community" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
|
||||
"hide" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
|
||||
"nsfw" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
|
||||
"network" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"addr" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"notify" => array("type" => "text", "not null" => "1"),
|
||||
"alias" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"generation" => array("type" => "tinyint(3)", "not null" => "1", "default" => "0"),
|
||||
"server_url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
),
|
||||
|
|
@ -742,21 +748,6 @@ function db_definition() {
|
|||
"nurl" => array("nurl"),
|
||||
)
|
||||
);
|
||||
$database["guid"] = array(
|
||||
"fields" => array(
|
||||
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
|
||||
"guid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"plink" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"network" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
|
||||
),
|
||||
"indexes" => array(
|
||||
"PRIMARY" => array("id"),
|
||||
"guid" => array("guid"),
|
||||
"plink" => array("plink"),
|
||||
"uri" => array("uri"),
|
||||
)
|
||||
);
|
||||
$database["hook"] = array(
|
||||
"fields" => array(
|
||||
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
|
||||
|
|
@ -795,6 +786,7 @@ function db_definition() {
|
|||
"uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
|
||||
"contact-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
|
||||
"gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
|
||||
"type" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"wall" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
|
||||
"gravity" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
|
||||
|
|
@ -871,6 +863,7 @@ function db_definition() {
|
|||
"uid_thrparent" => array("uid","thr-parent"),
|
||||
"uid_parenturi" => array("uid","parent-uri"),
|
||||
"uid_contactid_created" => array("uid","contact-id","created"),
|
||||
"gcontactid_uid_created" => array("gcontact-id","uid","created"),
|
||||
"wall_body" => array("wall","body(6)"),
|
||||
"uid_visible_moderated_created" => array("uid","visible","moderated","created"),
|
||||
"uid_uri" => array("uid","uri"),
|
||||
|
|
@ -1014,6 +1007,30 @@ function db_definition() {
|
|||
"receiver-uid" => array("receiver-uid"),
|
||||
)
|
||||
);
|
||||
$database["oembed"] = array(
|
||||
"fields" => array(
|
||||
"url" => array("type" => "varchar(255)", "not null" => "1", "primary" => "1"),
|
||||
"content" => array("type" => "text", "not null" => "1"),
|
||||
"created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
|
||||
),
|
||||
"indexes" => array(
|
||||
"PRIMARY" => array("url"),
|
||||
"created" => array("created"),
|
||||
)
|
||||
);
|
||||
$database["parsed_url"] = array(
|
||||
"fields" => array(
|
||||
"url" => array("type" => "varchar(255)", "not null" => "1", "primary" => "1"),
|
||||
"guessing" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0", "primary" => "1"),
|
||||
"oembed" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0", "primary" => "1"),
|
||||
"content" => array("type" => "text", "not null" => "1"),
|
||||
"created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
|
||||
),
|
||||
"indexes" => array(
|
||||
"PRIMARY" => array("url", "guessing", "oembed"),
|
||||
"created" => array("created"),
|
||||
)
|
||||
);
|
||||
$database["pconfig"] = array(
|
||||
"fields" => array(
|
||||
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
|
||||
|
|
@ -1287,6 +1304,7 @@ function db_definition() {
|
|||
"iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "primary" => "1"),
|
||||
"uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
|
||||
"contact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
|
||||
"gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
|
||||
"created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
|
||||
"edited" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
|
||||
"commented" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
|
||||
|
|
@ -1316,6 +1334,8 @@ function db_definition() {
|
|||
"uid_network_created" => array("uid","network","created"),
|
||||
"uid_contactid_commented" => array("uid","contact-id","commented"),
|
||||
"uid_contactid_created" => array("uid","contact-id","created"),
|
||||
"uid_gcontactid_commented" => array("uid","gcontact-id","commented"),
|
||||
"uid_gcontactid_created" => array("uid","gcontact-id","created"),
|
||||
"wall_private_received" => array("wall","private","received"),
|
||||
"uid_created" => array("uid","created"),
|
||||
"uid_commented" => array("uid","commented"),
|
||||
|
|
@ -1334,21 +1354,6 @@ function db_definition() {
|
|||
"PRIMARY" => array("id"),
|
||||
)
|
||||
);
|
||||
$database["unique_contacts"] = array(
|
||||
"fields" => array(
|
||||
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
|
||||
"url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"nick" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"location" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
|
||||
"about" => array("type" => "text", "not null" => "1"),
|
||||
),
|
||||
"indexes" => array(
|
||||
"PRIMARY" => array("id"),
|
||||
"url" => array("url"),
|
||||
)
|
||||
);
|
||||
$database["user"] = array(
|
||||
"fields" => array(
|
||||
"uid" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ require_once('include/html2plain.php');
|
|||
require_once("include/Scrape.php");
|
||||
require_once('include/diaspora.php');
|
||||
require_once("include/ostatus.php");
|
||||
require_once("include/dfrn.php");
|
||||
|
||||
function delivery_run(&$argv, &$argc){
|
||||
global $a, $db;
|
||||
|
|
@ -264,8 +265,6 @@ function delivery_run(&$argv, &$argc){
|
|||
if(count($r))
|
||||
$contact = $r[0];
|
||||
|
||||
$hubxml = feed_hublinks();
|
||||
|
||||
if($contact['self'])
|
||||
continue;
|
||||
|
||||
|
|
@ -278,138 +277,54 @@ function delivery_run(&$argv, &$argc){
|
|||
case NETWORK_DFRN:
|
||||
logger('notifier: '.$target_item["guid"].' dfrndelivery: ' . $contact['name']);
|
||||
|
||||
$feed_template = get_markup_template('atom_feed.tpl');
|
||||
$mail_template = get_markup_template('atom_mail.tpl');
|
||||
|
||||
$atom = '';
|
||||
|
||||
|
||||
$birthday = feed_birthday($owner['uid'],$owner['timezone']);
|
||||
|
||||
if(strlen($birthday))
|
||||
$birthday = '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>';
|
||||
|
||||
$atom .= replace_macros($feed_template, array(
|
||||
'$version' => xmlify(FRIENDICA_VERSION),
|
||||
'$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner['nickname'] ),
|
||||
'$feed_title' => xmlify($owner['name']),
|
||||
'$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00' , ATOM_TIME)) ,
|
||||
'$hub' => $hubxml,
|
||||
'$salmon' => '', // private feed, we don't use salmon here
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$profile_page' => xmlify($owner['url']),
|
||||
'$photo' => xmlify($owner['photo']),
|
||||
'$thumb' => xmlify($owner['thumb']),
|
||||
'$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
|
||||
'$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
|
||||
'$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
|
||||
'$birthday' => $birthday,
|
||||
'$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
|
||||
));
|
||||
|
||||
if($mail) {
|
||||
$public_message = false; // mail is not public
|
||||
|
||||
$body = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']);
|
||||
|
||||
$atom .= replace_macros($mail_template, array(
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$profile_page' => xmlify($owner['url']),
|
||||
'$thumb' => xmlify($owner['thumb']),
|
||||
'$item_id' => xmlify($item['uri']),
|
||||
'$subject' => xmlify($item['title']),
|
||||
'$created' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME)),
|
||||
'$content' => xmlify($body),
|
||||
'$parent_id' => xmlify($item['parent-uri'])
|
||||
));
|
||||
} elseif($fsuggest) {
|
||||
$public_message = false; // suggestions are not public
|
||||
|
||||
$sugg_template = get_markup_template('atom_suggest.tpl');
|
||||
|
||||
$atom .= replace_macros($sugg_template, array(
|
||||
'$name' => xmlify($item['name']),
|
||||
'$url' => xmlify($item['url']),
|
||||
'$photo' => xmlify($item['photo']),
|
||||
'$request' => xmlify($item['request']),
|
||||
'$note' => xmlify($item['note'])
|
||||
));
|
||||
|
||||
// We don't need this any more
|
||||
|
||||
q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1",
|
||||
intval($item['id'])
|
||||
);
|
||||
} elseif($relocate) {
|
||||
$public_message = false; // suggestions are not public
|
||||
|
||||
$sugg_template = get_markup_template('atom_relocate.tpl');
|
||||
|
||||
/* get site pubkey. this could be a new installation with no site keys*/
|
||||
$pubkey = get_config('system','site_pubkey');
|
||||
if(! $pubkey) {
|
||||
$res = new_keypair(1024);
|
||||
set_config('system','site_prvkey', $res['prvkey']);
|
||||
set_config('system','site_pubkey', $res['pubkey']);
|
||||
}
|
||||
|
||||
$rp = q("SELECT `resource-id` , `scale`, type FROM `photo`
|
||||
WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid);
|
||||
$photos = array();
|
||||
$ext = Photo::supportedTypes();
|
||||
foreach($rp as $p){
|
||||
$photos[$p['scale']] = $a->get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']];
|
||||
}
|
||||
unset($rp, $ext);
|
||||
|
||||
$atom .= replace_macros($sugg_template, array(
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$photo' => xmlify($photos[4]),
|
||||
'$thumb' => xmlify($photos[5]),
|
||||
'$micro' => xmlify($photos[6]),
|
||||
'$url' => xmlify($owner['url']),
|
||||
'$request' => xmlify($owner['request']),
|
||||
'$confirm' => xmlify($owner['confirm']),
|
||||
'$notify' => xmlify($owner['notify']),
|
||||
'$poll' => xmlify($owner['poll']),
|
||||
'$sitepubkey' => xmlify(get_config('system','site_pubkey')),
|
||||
//'$pubkey' => xmlify($owner['pubkey']),
|
||||
//'$prvkey' => xmlify($owner['prvkey']),
|
||||
));
|
||||
unset($photos);
|
||||
} elseif($followup) {
|
||||
if ($mail) {
|
||||
$item['body'] = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']);
|
||||
$atom = dfrn::mail($item, $owner);
|
||||
} elseif ($fsuggest) {
|
||||
$atom = dfrn::fsuggest($item, $owner);
|
||||
q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
|
||||
} elseif ($relocate)
|
||||
$atom = dfrn::relocate($owner, $uid);
|
||||
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);
|
||||
$atom .= atom_entry($item,'text',null,$owner,false);
|
||||
$msgitems[] = $item;
|
||||
}
|
||||
}
|
||||
$atom = dfrn::entries($msgitems,$owner);
|
||||
} 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'] == 1)
|
||||
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'])
|
||||
$atom .= atom_entry($item,'text',null,$owner,true,(($top_level) ? $contact['id'] : 0));
|
||||
} else
|
||||
$atom .= atom_entry($item,'text',null,$owner,true);
|
||||
if($item_id == $item['id'] || $item['id'] == $item['parent']) {
|
||||
$item["entry:comment-allow"] = true;
|
||||
$item["entry:cid"] = (($top_level) ? $contact['id'] : 0);
|
||||
$msgitems[] = $item;
|
||||
}
|
||||
} else {
|
||||
$item["entry:comment-allow"] = true;
|
||||
$msgitems[] = $item;
|
||||
}
|
||||
}
|
||||
$atom = dfrn::entries($msgitems,$owner);
|
||||
}
|
||||
|
||||
$atom .= '</feed>' . "\r\n";
|
||||
|
||||
logger('notifier: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG);
|
||||
logger('notifier entry: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG);
|
||||
|
||||
logger('notifier: ' . $atom, LOGGER_DATA);
|
||||
$basepath = implode('/', array_slice(explode('/',$contact['url']),0,3));
|
||||
|
|
@ -458,15 +373,14 @@ function delivery_run(&$argv, &$argc){
|
|||
if (($x[0]['page-flags'] == PAGE_SOAPBOX) AND $top_level)
|
||||
break;
|
||||
|
||||
require_once('library/simplepie/simplepie.inc');
|
||||
logger('mod-delivery: local delivery');
|
||||
local_delivery($x[0],$atom);
|
||||
dfrn::import($atom, $x[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(! was_recently_delayed($contact['id']))
|
||||
$deliver_status = dfrn_deliver($owner,$contact,$atom);
|
||||
$deliver_status = dfrn::deliver($owner,$contact,$atom);
|
||||
else
|
||||
$deliver_status = (-1);
|
||||
|
||||
|
|
|
|||
2422
include/dfrn.php
Normal file
2422
include/dfrn.php
Normal file
|
|
@ -0,0 +1,2422 @@
|
|||
<?php
|
||||
/**
|
||||
* @file include/dfrn.php
|
||||
* @brief The implementation of the dfrn protocol
|
||||
*
|
||||
* https://github.com/friendica/friendica/wiki/Protocol
|
||||
*/
|
||||
|
||||
require_once("include/Contact.php");
|
||||
require_once("include/ostatus.php");
|
||||
require_once("include/enotify.php");
|
||||
require_once("include/threads.php");
|
||||
require_once("include/socgraph.php");
|
||||
require_once("include/items.php");
|
||||
require_once("include/tags.php");
|
||||
require_once("include/files.php");
|
||||
require_once("include/event.php");
|
||||
require_once("include/text.php");
|
||||
require_once("include/oembed.php");
|
||||
require_once("include/html2bbcode.php");
|
||||
|
||||
/**
|
||||
* @brief This class contain functions to create and send DFRN XML files
|
||||
*
|
||||
*/
|
||||
class dfrn {
|
||||
|
||||
const DFRN_TOP_LEVEL = 0; // Top level posting
|
||||
const DFRN_REPLY = 1; // Regular reply that is stored locally
|
||||
const DFRN_REPLY_RC = 2; // Reply that will be relayed
|
||||
|
||||
/**
|
||||
* @brief Generates the atom entries for delivery.php
|
||||
*
|
||||
* This function is used whenever content is transmitted via DFRN.
|
||||
*
|
||||
* @param array $items Item elements
|
||||
* @param array $owner Owner record
|
||||
*
|
||||
* @return string DFRN entries
|
||||
*/
|
||||
public static function entries($items,$owner) {
|
||||
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->formatOutput = true;
|
||||
|
||||
$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
|
||||
|
||||
if(! count($items))
|
||||
return trim($doc->saveXML());
|
||||
|
||||
foreach($items as $item) {
|
||||
$entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]);
|
||||
$root->appendChild($entry);
|
||||
}
|
||||
|
||||
return(trim($doc->saveXML()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate an atom feed for the given user
|
||||
*
|
||||
* This function is called when another server is pulling data from the user feed.
|
||||
*
|
||||
* @param string $dfrn_id DFRN ID from the requesting party
|
||||
* @param string $owner_nick Owner nick name
|
||||
* @param string $last_update Date of the last update
|
||||
* @param int $direction Can be -1, 0 or 1.
|
||||
*
|
||||
* @return string DFRN feed entries
|
||||
*/
|
||||
public static function feed($dfrn_id, $owner_nick, $last_update, $direction = 0) {
|
||||
|
||||
$a = get_app();
|
||||
|
||||
$sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
|
||||
$public_feed = (($dfrn_id) ? false : true);
|
||||
$starred = false; // not yet implemented, possible security issues
|
||||
$converse = false;
|
||||
|
||||
if($public_feed && $a->argc > 2) {
|
||||
for($x = 2; $x < $a->argc; $x++) {
|
||||
if($a->argv[$x] == 'converse')
|
||||
$converse = true;
|
||||
if($a->argv[$x] == 'starred')
|
||||
$starred = true;
|
||||
if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
|
||||
$category = $a->argv[$x+1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// default permissions - anonymous user
|
||||
|
||||
$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`
|
||||
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
|
||||
WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
|
||||
dbesc($owner_nick)
|
||||
);
|
||||
|
||||
if(! count($r))
|
||||
killme();
|
||||
|
||||
$owner = $r[0];
|
||||
$owner_id = $owner['user_uid'];
|
||||
$owner_nick = $owner['nickname'];
|
||||
|
||||
$sql_post_table = "";
|
||||
$visibility = "";
|
||||
|
||||
if(! $public_feed) {
|
||||
|
||||
$sql_extra = '';
|
||||
switch($direction) {
|
||||
case (-1):
|
||||
$sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
|
||||
$my_id = $dfrn_id;
|
||||
break;
|
||||
case 0:
|
||||
$sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
|
||||
$my_id = '1:' . $dfrn_id;
|
||||
break;
|
||||
case 1:
|
||||
$sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
|
||||
$my_id = '0:' . $dfrn_id;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break; // NOTREACHED
|
||||
}
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
|
||||
intval($owner_id)
|
||||
);
|
||||
|
||||
if(! count($r))
|
||||
killme();
|
||||
|
||||
$contact = $r[0];
|
||||
require_once('include/security.php');
|
||||
$groups = init_groups_visitor($contact['id']);
|
||||
|
||||
if(count($groups)) {
|
||||
for($x = 0; $x < count($groups); $x ++)
|
||||
$groups[$x] = '<' . intval($groups[$x]) . '>' ;
|
||||
$gs = implode('|', $groups);
|
||||
} else
|
||||
$gs = '<<>>' ; // Impossible to match
|
||||
|
||||
$sql_extra = sprintf("
|
||||
AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
|
||||
AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
|
||||
AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
|
||||
AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
|
||||
",
|
||||
intval($contact['id']),
|
||||
intval($contact['id']),
|
||||
dbesc($gs),
|
||||
dbesc($gs)
|
||||
);
|
||||
}
|
||||
|
||||
if($public_feed)
|
||||
$sort = 'DESC';
|
||||
else
|
||||
$sort = 'ASC';
|
||||
|
||||
$date_field = "`changed`";
|
||||
$sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
|
||||
|
||||
if(! strlen($last_update))
|
||||
$last_update = 'now -30 days';
|
||||
|
||||
if(isset($category)) {
|
||||
$sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
|
||||
dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
|
||||
//$sql_extra .= file_tag_file_query('item',$category,'category');
|
||||
}
|
||||
|
||||
if($public_feed) {
|
||||
if(! $converse)
|
||||
$sql_extra .= " AND `contact`.`self` = 1 ";
|
||||
}
|
||||
|
||||
$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
|
||||
|
||||
// AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
|
||||
// dbesc($check_date),
|
||||
|
||||
$r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
|
||||
`contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
|
||||
`contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
|
||||
`contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
|
||||
`contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
|
||||
`sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
|
||||
FROM `item` $sql_post_table
|
||||
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
||||
AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
|
||||
LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
|
||||
WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
|
||||
AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
|
||||
$sql_extra
|
||||
ORDER BY $sql_order LIMIT 0, 300",
|
||||
intval($owner_id),
|
||||
dbesc($check_date),
|
||||
dbesc($sort)
|
||||
);
|
||||
|
||||
// Will check further below if this actually returned results.
|
||||
// We will provide an empty feed if that is the case.
|
||||
|
||||
$items = $r;
|
||||
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->formatOutput = true;
|
||||
|
||||
$alternatelink = $owner['url'];
|
||||
|
||||
if(isset($category))
|
||||
$alternatelink .= "/category/".$category;
|
||||
|
||||
if ($public_feed)
|
||||
$author = "dfrn:owner";
|
||||
else
|
||||
$author = "author";
|
||||
|
||||
$root = self::add_header($doc, $owner, $author, $alternatelink, true);
|
||||
|
||||
// This hook can't work anymore
|
||||
// call_hooks('atom_feed', $atom);
|
||||
|
||||
if(! count($items)) {
|
||||
$atom = trim($doc->saveXML());
|
||||
|
||||
call_hooks('atom_feed_end', $atom);
|
||||
|
||||
return $atom;
|
||||
}
|
||||
|
||||
foreach($items as $item) {
|
||||
|
||||
// prevent private email from leaking.
|
||||
if($item['network'] === NETWORK_MAIL)
|
||||
continue;
|
||||
|
||||
// public feeds get html, our own nodes use bbcode
|
||||
|
||||
if($public_feed) {
|
||||
$type = 'html';
|
||||
// catch any email that's in a public conversation and make sure it doesn't leak
|
||||
if($item['private'])
|
||||
continue;
|
||||
} else
|
||||
$type = 'text';
|
||||
|
||||
$entry = self::entry($doc, $type, $item, $owner, true);
|
||||
$root->appendChild($entry);
|
||||
|
||||
}
|
||||
|
||||
$atom = trim($doc->saveXML());
|
||||
|
||||
call_hooks('atom_feed_end', $atom);
|
||||
|
||||
return $atom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create XML text for DFRN mails
|
||||
*
|
||||
* @param array $item message elements
|
||||
* @param array $owner Owner record
|
||||
*
|
||||
* @return string DFRN mail
|
||||
*/
|
||||
public static function mail($item, $owner) {
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->formatOutput = true;
|
||||
|
||||
$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
|
||||
|
||||
$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']);
|
||||
|
||||
$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']);
|
||||
|
||||
$root->appendChild($mail);
|
||||
|
||||
return(trim($doc->saveXML()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create XML text for DFRN friend suggestions
|
||||
*
|
||||
* @param array $item suggestion elements
|
||||
* @param array $owner Owner record
|
||||
*
|
||||
* @return string DFRN suggestions
|
||||
*/
|
||||
public static function fsuggest($item, $owner) {
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->formatOutput = true;
|
||||
|
||||
$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
|
||||
|
||||
$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']);
|
||||
|
||||
$root->appendChild($suggest);
|
||||
|
||||
return(trim($doc->saveXML()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create XML text for DFRN relocations
|
||||
*
|
||||
* @param array $owner Owner record
|
||||
* @param int $uid User ID
|
||||
*
|
||||
* @return string DFRN relocations
|
||||
*/
|
||||
public static function relocate($owner, $uid) {
|
||||
|
||||
/* get site pubkey. this could be a new installation with no site keys*/
|
||||
$pubkey = get_config('system','site_pubkey');
|
||||
if(! $pubkey) {
|
||||
$res = new_keypair(1024);
|
||||
set_config('system','site_prvkey', $res['prvkey']);
|
||||
set_config('system','site_pubkey', $res['pubkey']);
|
||||
}
|
||||
|
||||
$rp = q("SELECT `resource-id` , `scale`, type FROM `photo`
|
||||
WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid);
|
||||
$photos = array();
|
||||
$ext = Photo::supportedTypes();
|
||||
|
||||
foreach($rp as $p)
|
||||
$photos[$p['scale']] = app::get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']];
|
||||
|
||||
unset($rp, $ext);
|
||||
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->formatOutput = true;
|
||||
|
||||
$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
|
||||
|
||||
$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'));
|
||||
|
||||
$root->appendChild($relocate);
|
||||
|
||||
return(trim($doc->saveXML()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the header elements for the DFRN protocol
|
||||
*
|
||||
* @param object $doc XML document
|
||||
* @param array $owner Owner record
|
||||
* @param string $authorelement Element name for the author
|
||||
* @param string $alternatelink link to profile or category
|
||||
* @param bool $public Is it a header for public posts?
|
||||
*
|
||||
* @return object XML root object
|
||||
*/
|
||||
private function add_header($doc, $owner, $authorelement, $alternatelink = "", $public = false) {
|
||||
|
||||
if ($alternatelink == "")
|
||||
$alternatelink = $owner['url'];
|
||||
|
||||
$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
|
||||
$doc->appendChild($root);
|
||||
|
||||
$root->setAttribute("xmlns:thr", NAMESPACE_THREAD);
|
||||
$root->setAttribute("xmlns:at", NAMESPACE_TOMB);
|
||||
$root->setAttribute("xmlns:media", NAMESPACE_MEDIA);
|
||||
$root->setAttribute("xmlns:dfrn", NAMESPACE_DFRN);
|
||||
$root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
|
||||
$root->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
|
||||
$root->setAttribute("xmlns:poco", NAMESPACE_POCO);
|
||||
$root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
|
||||
$root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
|
||||
|
||||
xml_add_element($doc, $root, "id", app::get_baseurl()."/profile/".$owner["nick"]);
|
||||
xml_add_element($doc, $root, "title", $owner["name"]);
|
||||
|
||||
$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
|
||||
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);
|
||||
|
||||
$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink);
|
||||
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);
|
||||
|
||||
$attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
|
||||
xml_add_element($doc, $root, "link", "", $attributes);
|
||||
|
||||
$attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
|
||||
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);
|
||||
}
|
||||
|
||||
if ($owner['page-flags'] == PAGE_COMMUNITY)
|
||||
xml_add_element($doc, $root, "dfrn:community", 1);
|
||||
|
||||
/// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP"
|
||||
|
||||
xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
|
||||
|
||||
$author = self::add_author($doc, $owner, $authorelement, $public);
|
||||
$root->appendChild($author);
|
||||
|
||||
return $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the author element in the header for the DFRN protocol
|
||||
*
|
||||
* @param object $doc XML document
|
||||
* @param array $owner Owner record
|
||||
* @param string $authorelement Element name for the author
|
||||
*
|
||||
* @return object XML author object
|
||||
*/
|
||||
private function add_author($doc, $owner, $authorelement, $public) {
|
||||
|
||||
$author = $doc->createElement($authorelement);
|
||||
|
||||
$namdate = datetime_convert('UTC', 'UTC', $owner['name-date'].'+00:00' , ATOM_TIME);
|
||||
$uridate = datetime_convert('UTC', 'UTC', $owner['uri-date'].'+00:00', ATOM_TIME);
|
||||
$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);
|
||||
|
||||
$attributes = array("dfrn:updated" => $namdate);
|
||||
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);
|
||||
|
||||
$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);
|
||||
|
||||
$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);
|
||||
|
||||
$birthday = feed_birthday($owner['user_uid'], $owner['timezone']);
|
||||
|
||||
if ($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)
|
||||
return $author;
|
||||
|
||||
// Only show contact details when we are allowed to
|
||||
$r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`, `user`.`timezone`,
|
||||
`profile`.`locality`, `profile`.`region`, `profile`.`country-name`, `profile`.`pub_keywords`, `profile`.`dob`
|
||||
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']));
|
||||
if ($r) {
|
||||
$profile = $r[0];
|
||||
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: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"));
|
||||
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");
|
||||
$author->appendChild($urls);
|
||||
}
|
||||
|
||||
if (trim($profile["pub_keywords"]) != "") {
|
||||
$keywords = explode(",", $profile["pub_keywords"]);
|
||||
|
||||
foreach ($keywords AS $keyword)
|
||||
xml_add_element($doc, $author, "poco:tags", trim($keyword));
|
||||
|
||||
}
|
||||
|
||||
/// @todo When we are having the XMPP address in the profile we should propagate it here
|
||||
$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");
|
||||
$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));
|
||||
|
||||
if (trim($profile["locality"]) != "")
|
||||
xml_add_element($doc, $element, "poco:locality", $profile["locality"]);
|
||||
|
||||
if (trim($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"]);
|
||||
|
||||
$author->appendChild($element);
|
||||
}
|
||||
}
|
||||
|
||||
return $author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the author elements in the "entry" elements of the DFRN protocol
|
||||
*
|
||||
* @param object $doc XML document
|
||||
* @param string $element Element name for the author
|
||||
* @param string $contact_url Link of the contact
|
||||
* @param array $items Item elements
|
||||
*
|
||||
* @return object XML author object
|
||||
*/
|
||||
private function add_entry_author($doc, $element, $contact_url, $item) {
|
||||
|
||||
$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"]);
|
||||
|
||||
/// @Todo
|
||||
/// - Check real image type and image size
|
||||
/// - Check which of these boths elements we should use
|
||||
$attributes = array(
|
||||
"rel" => "photo",
|
||||
"type" => "image/jpeg",
|
||||
"media:width" => 80,
|
||||
"media:height" => 80,
|
||||
"href" => $contact["photo"]);
|
||||
xml_add_element($doc, $author, "link", "", $attributes);
|
||||
|
||||
$attributes = array(
|
||||
"rel" => "avatar",
|
||||
"type" => "image/jpeg",
|
||||
"media:width" => 80,
|
||||
"media:height" => 80,
|
||||
"href" => $contact["photo"]);
|
||||
xml_add_element($doc, $author, "link", "", $attributes);
|
||||
|
||||
return $author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the activity elements
|
||||
*
|
||||
* @param object $doc XML document
|
||||
* @param string $element Element name for the activity
|
||||
* @param string $activity activity value
|
||||
*
|
||||
* @return object XML activity object
|
||||
*/
|
||||
private function create_activity($doc, $element, $activity) {
|
||||
|
||||
if($activity) {
|
||||
$entry = $doc->createElement($element);
|
||||
|
||||
$r = parse_xml_string($activity, false);
|
||||
if(!$r)
|
||||
return false;
|
||||
if($r->type)
|
||||
xml_add_element($doc, $entry, "activity:object-type", $r->type);
|
||||
if($r->id)
|
||||
xml_add_element($doc, $entry, "id", $r->id);
|
||||
if($r->title)
|
||||
xml_add_element($doc, $entry, "title", $r->title);
|
||||
if($r->link) {
|
||||
if(substr($r->link,0,1) === '<') {
|
||||
if(strstr($r->link,'&') && (! strstr($r->link,'&')))
|
||||
$r->link = str_replace('&','&', $r->link);
|
||||
|
||||
$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
|
||||
|
||||
// XML does need a single element as root element so we add a dummy element here
|
||||
$data = parse_xml_string("<dummy>".$r->link."</dummy>", false);
|
||||
if (is_object($data)) {
|
||||
foreach ($data->link AS $link) {
|
||||
$attributes = array();
|
||||
foreach ($link->attributes() AS $parameter => $value)
|
||||
$attributes[$parameter] = $value;
|
||||
xml_add_element($doc, $entry, "link", "", $attributes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link);
|
||||
xml_add_element($doc, $entry, "link", "", $attributes);
|
||||
}
|
||||
}
|
||||
if($r->content)
|
||||
xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html"));
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the elements for attachments
|
||||
*
|
||||
* @param object $doc XML document
|
||||
* @param object $root XML root
|
||||
* @param array $item Item element
|
||||
*
|
||||
* @return object XML attachment object
|
||||
*/
|
||||
private function get_attachment($doc, $root, $item) {
|
||||
$arr = explode('[/attach],',$item['attach']);
|
||||
if(count($arr)) {
|
||||
foreach($arr as $r) {
|
||||
$matches = false;
|
||||
$cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
|
||||
if($cnt) {
|
||||
$attributes = array("rel" => "enclosure",
|
||||
"href" => $matches[1],
|
||||
"type" => $matches[3]);
|
||||
|
||||
if(intval($matches[2]))
|
||||
$attributes["length"] = intval($matches[2]);
|
||||
|
||||
if(trim($matches[4]) != "")
|
||||
$attributes["title"] = trim($matches[4]);
|
||||
|
||||
xml_add_element($doc, $root, "link", "", $attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the "entry" elements for the DFRN protocol
|
||||
*
|
||||
* @param object $doc XML document
|
||||
* @param string $type "text" or "html"
|
||||
* @param array $item Item element
|
||||
* @param array $owner Owner record
|
||||
* @param bool $comment Trigger the sending of the "comment" element
|
||||
* @param int $cid Contact ID of the recipient
|
||||
*
|
||||
* @return object XML entry object
|
||||
*/
|
||||
private function entry($doc, $type, $item, $owner, $comment = false, $cid = 0) {
|
||||
|
||||
$mentioned = array();
|
||||
|
||||
if(!$item['parent'])
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
$entry = $doc->createElement("entry");
|
||||
|
||||
if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
|
||||
$body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
|
||||
else
|
||||
$body = $item['body'];
|
||||
|
||||
if ($type == 'html') {
|
||||
$htmlbody = $body;
|
||||
|
||||
if ($item['title'] != "")
|
||||
$htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
|
||||
|
||||
$htmlbody = bbcode($htmlbody, false, false, 7);
|
||||
}
|
||||
|
||||
$author = self::add_entry_author($doc, "author", $item["author-link"], $item);
|
||||
$entry->appendChild($author);
|
||||
|
||||
$dfrnowner = self::add_entry_author($doc, "dfrn:owner", $item["owner-link"], $item);
|
||||
$entry->appendChild($dfrnowner);
|
||||
|
||||
if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
|
||||
$parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
|
||||
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
|
||||
$attributes = array("ref" => $parent_item, "type" => "text/html",
|
||||
"href" => app::get_baseurl().'/display/'.$parent[0]['guid'],
|
||||
"dfrn:diaspora_guid" => $parent[0]['guid']);
|
||||
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, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME));
|
||||
xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
|
||||
|
||||
// "dfrn:env" is used to read the content
|
||||
xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true));
|
||||
|
||||
// The "content" field is not read by the receiver. We could remove it when the type is "text"
|
||||
// We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env"
|
||||
xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type));
|
||||
|
||||
// We save this value in "plink". Maybe we should read it from there as well?
|
||||
xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
|
||||
"href" => app::get_baseurl()."/display/".$item["guid"]));
|
||||
|
||||
// "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']));
|
||||
|
||||
if($item['location'])
|
||||
xml_add_element($doc, $entry, "dfrn:location", $item['location']);
|
||||
|
||||
if($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));
|
||||
|
||||
if($item['extid'])
|
||||
xml_add_element($doc, $entry, "dfrn:extid", $item['extid']);
|
||||
|
||||
if($item['bookmark'])
|
||||
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, "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, "activity:verb", construct_verb($item));
|
||||
|
||||
if ($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);
|
||||
else
|
||||
xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT);
|
||||
|
||||
$actobj = self::create_activity($doc, "activity:object", $item['object']);
|
||||
if ($actobj)
|
||||
$entry->appendChild($actobj);
|
||||
|
||||
$actarg = self::create_activity($doc, "activity:target", $item['target']);
|
||||
if ($actarg)
|
||||
$entry->appendChild($actarg);
|
||||
|
||||
$tags = item_getfeedtags($item);
|
||||
|
||||
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]));
|
||||
}
|
||||
|
||||
if(count($tags))
|
||||
foreach($tags as $t)
|
||||
if ($t[0] == "@")
|
||||
$mentioned[$t[1]] = $t[1];
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
self::get_attachment($doc, $entry, $item);
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers the atom content to the contacts
|
||||
*
|
||||
* @param array $owner Owner record
|
||||
* @param array $contactr Contact record of the receiver
|
||||
* @param string $atom Content that will be transmitted
|
||||
* @param bool $dissolve (to be documented)
|
||||
*
|
||||
* @return int Deliver status. -1 means an error.
|
||||
*/
|
||||
public static function deliver($owner,$contact,$atom, $dissolve = false) {
|
||||
|
||||
$a = get_app();
|
||||
|
||||
$idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
|
||||
|
||||
if($contact['duplex'] && $contact['dfrn-id'])
|
||||
$idtosend = '0:' . $orig_id;
|
||||
if($contact['duplex'] && $contact['issued-id'])
|
||||
$idtosend = '1:' . $orig_id;
|
||||
|
||||
|
||||
$rino = get_config('system','rino_encrypt');
|
||||
$rino = intval($rino);
|
||||
// use RINO1 if mcrypt isn't installed and RINO2 was selected
|
||||
if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
|
||||
|
||||
logger("Local rino version: ". $rino, LOGGER_DEBUG);
|
||||
|
||||
$ssl_val = intval(get_config('system','ssl_policy'));
|
||||
$ssl_policy = '';
|
||||
|
||||
switch($ssl_val){
|
||||
case SSL_POLICY_FULL:
|
||||
$ssl_policy = 'full';
|
||||
break;
|
||||
case SSL_POLICY_SELFSIGN:
|
||||
$ssl_policy = 'self';
|
||||
break;
|
||||
case SSL_POLICY_NONE:
|
||||
default:
|
||||
$ssl_policy = 'none';
|
||||
break;
|
||||
}
|
||||
|
||||
$url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
|
||||
|
||||
logger('dfrn_deliver: ' . $url);
|
||||
|
||||
$xml = fetch_url($url);
|
||||
|
||||
$curl_stat = $a->get_curl_code();
|
||||
if(! $curl_stat)
|
||||
return(-1); // timed out
|
||||
|
||||
logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
|
||||
|
||||
if(! $xml)
|
||||
return 3;
|
||||
|
||||
if(strpos($xml,'<?xml') === false) {
|
||||
logger('dfrn_deliver: no valid XML returned');
|
||||
logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
|
||||
return 3;
|
||||
}
|
||||
|
||||
$res = parse_xml_string($xml);
|
||||
|
||||
if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
|
||||
return (($res->status) ? $res->status : 3);
|
||||
|
||||
$postvars = array();
|
||||
$sent_dfrn_id = hex2bin((string) $res->dfrn_id);
|
||||
$challenge = hex2bin((string) $res->challenge);
|
||||
$perm = (($res->perm) ? $res->perm : null);
|
||||
$dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
|
||||
$rino_remote_version = intval($res->rino);
|
||||
$page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
|
||||
|
||||
logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
|
||||
|
||||
if($owner['page-flags'] == PAGE_PRVGROUP)
|
||||
$page = 2;
|
||||
|
||||
$final_dfrn_id = '';
|
||||
|
||||
if($perm) {
|
||||
if((($perm == 'rw') && (! intval($contact['writable'])))
|
||||
|| (($perm == 'r') && (intval($contact['writable'])))) {
|
||||
q("update contact set writable = %d where id = %d",
|
||||
intval(($perm == 'rw') ? 1 : 0),
|
||||
intval($contact['id'])
|
||||
);
|
||||
$contact['writable'] = (string) 1 - intval($contact['writable']);
|
||||
}
|
||||
}
|
||||
|
||||
if(($contact['duplex'] && strlen($contact['pubkey']))
|
||||
|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
|
||||
|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
|
||||
openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
|
||||
openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
|
||||
} else {
|
||||
openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
|
||||
openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
|
||||
}
|
||||
|
||||
$final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
|
||||
|
||||
if(strpos($final_dfrn_id,':') == 1)
|
||||
$final_dfrn_id = substr($final_dfrn_id,2);
|
||||
|
||||
if($final_dfrn_id != $orig_id) {
|
||||
logger('dfrn_deliver: wrong dfrn_id.');
|
||||
// did not decode properly - cannot trust this site
|
||||
return 3;
|
||||
}
|
||||
|
||||
$postvars['dfrn_id'] = $idtosend;
|
||||
$postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
|
||||
if($dissolve)
|
||||
$postvars['dissolve'] = '1';
|
||||
|
||||
|
||||
if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
|
||||
$postvars['data'] = $atom;
|
||||
$postvars['perm'] = 'rw';
|
||||
} else {
|
||||
$postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
|
||||
$postvars['perm'] = 'r';
|
||||
}
|
||||
|
||||
$postvars['ssl_policy'] = $ssl_policy;
|
||||
|
||||
if($page)
|
||||
$postvars['page'] = $page;
|
||||
|
||||
|
||||
if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
|
||||
logger('rino version: '. $rino_remote_version);
|
||||
|
||||
switch($rino_remote_version) {
|
||||
case 1:
|
||||
// Deprecated rino version!
|
||||
$key = substr(random_string(),0,16);
|
||||
$data = aes_encrypt($postvars['data'],$key);
|
||||
break;
|
||||
case 2:
|
||||
// RINO 2 based on php-encryption
|
||||
try {
|
||||
$key = Crypto::createNewRandomKey();
|
||||
} catch (CryptoTestFailed $ex) {
|
||||
logger('Cannot safely create a key');
|
||||
return -1;
|
||||
} catch (CannotPerformOperation $ex) {
|
||||
logger('Cannot safely create a key');
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
$data = Crypto::encrypt($postvars['data'], $key);
|
||||
} catch (CryptoTestFailed $ex) {
|
||||
logger('Cannot safely perform encryption');
|
||||
return -1;
|
||||
} catch (CannotPerformOperation $ex) {
|
||||
logger('Cannot safely perform encryption');
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger("rino: invalid requested verision '$rino_remote_version'");
|
||||
return -1;
|
||||
}
|
||||
|
||||
$postvars['rino'] = $rino_remote_version;
|
||||
$postvars['data'] = bin2hex($data);
|
||||
|
||||
#logger('rino: sent key = ' . $key, LOGGER_DEBUG);
|
||||
|
||||
|
||||
if($dfrn_version >= 2.1) {
|
||||
if(($contact['duplex'] && strlen($contact['pubkey']))
|
||||
|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
|
||||
|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey'])))
|
||||
|
||||
openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
|
||||
else
|
||||
openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
|
||||
|
||||
} else {
|
||||
if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY))
|
||||
openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
|
||||
else
|
||||
openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
|
||||
|
||||
}
|
||||
|
||||
logger('md5 rawkey ' . md5($postvars['key']));
|
||||
|
||||
$postvars['key'] = bin2hex($postvars['key']);
|
||||
}
|
||||
|
||||
|
||||
logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
|
||||
|
||||
$xml = post_url($contact['notify'],$postvars);
|
||||
|
||||
logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
|
||||
|
||||
$curl_stat = $a->get_curl_code();
|
||||
if((! $curl_stat) || (! strlen($xml)))
|
||||
return(-1); // timed out
|
||||
|
||||
if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
|
||||
return(-1);
|
||||
|
||||
if(strpos($xml,'<?xml') === false) {
|
||||
logger('dfrn_deliver: phase 2: no valid XML returned');
|
||||
logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
|
||||
return 3;
|
||||
}
|
||||
|
||||
if($contact['term-date'] != '0000-00-00 00:00:00') {
|
||||
logger("dfrn_deliver: $url back from the dead - removing mark for death");
|
||||
require_once('include/Contact.php');
|
||||
unmark_for_death($contact);
|
||||
}
|
||||
|
||||
$res = parse_xml_string($xml);
|
||||
|
||||
return $res->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add new birthday event for this person
|
||||
*
|
||||
* @param array $contact Contact record
|
||||
* @param string $birthday Birthday of the contact
|
||||
*
|
||||
*/
|
||||
private function birthday_event($contact, $birthday) {
|
||||
|
||||
logger("updating birthday: ".$birthday." for contact ".$contact["id"]);
|
||||
|
||||
$bdtext = sprintf(t("%s\'s birthday"), $contact["name"]);
|
||||
$bdtext2 = sprintf(t("Happy Birthday %s"), " [url=".$contact["url"]."]".$contact["name"]."[/url]") ;
|
||||
|
||||
|
||||
$r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
|
||||
VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ",
|
||||
intval($contact["uid"]),
|
||||
intval($contact["id"]),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert("UTC","UTC", $birthday)),
|
||||
dbesc(datetime_convert("UTC","UTC", $birthday." + 1 day ")),
|
||||
dbesc($bdtext),
|
||||
dbesc($bdtext2),
|
||||
dbesc("birthday")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetch the author data from head or entry items
|
||||
*
|
||||
* @param object $xpath XPath object
|
||||
* @param object $context In which context should the data be searched
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
* @param string $element Element name from which the data is fetched
|
||||
* @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well
|
||||
*
|
||||
* @return Returns an array with relevant data of the author
|
||||
*/
|
||||
private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch) {
|
||||
|
||||
$author = array();
|
||||
$author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue;
|
||||
$author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue;
|
||||
|
||||
$r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`,
|
||||
`name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd`
|
||||
FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
|
||||
intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET));
|
||||
if ($r) {
|
||||
$contact = $r[0];
|
||||
$author["contact-id"] = $r[0]["id"];
|
||||
$author["network"] = $r[0]["network"];
|
||||
} else {
|
||||
$author["contact-id"] = $importer["id"];
|
||||
$author["network"] = $importer["network"];
|
||||
$onlyfetch = true;
|
||||
}
|
||||
|
||||
// Until now we aren't serving different sizes - but maybe later
|
||||
$avatarlist = array();
|
||||
// @todo check if "avatar" or "photo" would be the best field in the specification
|
||||
$avatars = $xpath->query($element."/atom:link[@rel='avatar']", $context);
|
||||
foreach($avatars AS $avatar) {
|
||||
$href = "";
|
||||
$width = 0;
|
||||
foreach($avatar->attributes AS $attributes) {
|
||||
if ($attributes->name == "href")
|
||||
$href = $attributes->textContent;
|
||||
if ($attributes->name == "width")
|
||||
$width = $attributes->textContent;
|
||||
if ($attributes->name == "updated")
|
||||
$contact["avatar-date"] = $attributes->textContent;
|
||||
}
|
||||
if (($width > 0) AND ($href != ""))
|
||||
$avatarlist[$width] = $href;
|
||||
}
|
||||
if (count($avatarlist) > 0) {
|
||||
krsort($avatarlist);
|
||||
$author["avatar"] = current($avatarlist);
|
||||
}
|
||||
|
||||
if ($r AND !$onlyfetch) {
|
||||
|
||||
// When was the last change to name or uri?
|
||||
$name_element = $xpath->query($element."/atom:name", $context)->item(0);
|
||||
foreach($name_element->attributes AS $attributes)
|
||||
if ($attributes->name == "updated")
|
||||
$contact["name-date"] = $attributes->textContent;
|
||||
|
||||
$link_element = $xpath->query($element."/atom:link", $context)->item(0);
|
||||
foreach($link_element->attributes AS $attributes)
|
||||
if ($attributes->name == "updated")
|
||||
$contact["uri-date"] = $attributes->textContent;
|
||||
|
||||
// Update contact data
|
||||
$value = $xpath->evaluate($element."/dfrn:handle/text()", $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["addr"] = $value;
|
||||
|
||||
$value = $xpath->evaluate($element."/poco:displayName/text()", $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["name"] = $value;
|
||||
|
||||
$value = $xpath->evaluate($element."/poco:preferredUsername/text()", $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["nick"] = $value;
|
||||
|
||||
$value = $xpath->evaluate($element."/poco:note/text()", $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["about"] = $value;
|
||||
|
||||
$value = $xpath->evaluate($element."/poco:address/poco:formatted/text()", $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["location"] = $value;
|
||||
|
||||
/// @todo Add support for the following fields that we don't support by now in the contact table:
|
||||
/// - poco:utcOffset
|
||||
/// - poco:ims
|
||||
/// - poco:urls
|
||||
/// - poco:locality
|
||||
/// - poco:region
|
||||
/// - poco:country
|
||||
|
||||
// Save the keywords into the contact table
|
||||
$tags = array();
|
||||
$tagelements = $xpath->evaluate($element."/poco:tags/text()", $context);
|
||||
foreach($tagelements AS $tag)
|
||||
$tags[$tag->nodeValue] = $tag->nodeValue;
|
||||
|
||||
if (count($tags))
|
||||
$contact["keywords"] = implode(", ", $tags);
|
||||
|
||||
// "dfrn:birthday" contains the birthday converted to UTC
|
||||
$old_bdyear = $contact["bdyear"];
|
||||
|
||||
$birthday = $xpath->evaluate($element."/dfrn:birthday/text()", $context)->item(0)->nodeValue;
|
||||
|
||||
if (strtotime($birthday) > time()) {
|
||||
$bd_timestamp = strtotime($birthday);
|
||||
|
||||
$contact["bdyear"] = date("Y", $bd_timestamp);
|
||||
}
|
||||
|
||||
// "poco:birthday" is the birthday in the format "yyyy-mm-dd"
|
||||
$value = $xpath->evaluate($element."/poco:birthday/text()", $context)->item(0)->nodeValue;
|
||||
|
||||
if (!in_array($value, array("", "0000-00-00"))) {
|
||||
$bdyear = date("Y");
|
||||
$value = str_replace("0000", $bdyear, $value);
|
||||
|
||||
if (strtotime($value) < time()) {
|
||||
$value = str_replace($bdyear, $bdyear + 1, $value);
|
||||
$bdyear = $bdyear + 1;
|
||||
}
|
||||
|
||||
$contact["bd"] = $value;
|
||||
}
|
||||
|
||||
if ($old_bdyear != $contact["bdyear"])
|
||||
self::birthday_event($contact, $birthday);
|
||||
|
||||
// Get all field names
|
||||
$fields = array();
|
||||
foreach ($r[0] AS $field => $data)
|
||||
$fields[$field] = $data;
|
||||
|
||||
unset($fields["id"]);
|
||||
unset($fields["uid"]);
|
||||
unset($fields["avatar-date"]);
|
||||
unset($fields["name-date"]);
|
||||
unset($fields["uri-date"]);
|
||||
|
||||
// Update check for this field has to be done differently
|
||||
$datefields = array("name-date", "uri-date");
|
||||
foreach ($datefields AS $field)
|
||||
if (strtotime($contact[$field]) > strtotime($r[0][$field])) {
|
||||
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]) {
|
||||
logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG);
|
||||
$update = true;
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
|
||||
|
||||
q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s',
|
||||
`addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s',
|
||||
`name-date` = '%s', `uri-date` = '%s'
|
||||
WHERE `id` = %d AND `network` = '%s'",
|
||||
dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]),
|
||||
dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]),
|
||||
dbesc($contact["bd"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]),
|
||||
intval($contact["id"]), dbesc($contact["network"]));
|
||||
}
|
||||
|
||||
update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"],
|
||||
(strtotime($contact["avatar-date"]) > strtotime($r[0]["avatar-date"])));
|
||||
|
||||
// The generation is a sign for the reliability of the provided data.
|
||||
// It is used in the socgraph.php to prevent that old contact data
|
||||
// that was relayed over several servers can overwrite contact
|
||||
// data that we received directly.
|
||||
$contact["generation"] = 2;
|
||||
$contact["photo"] = $author["avatar"];
|
||||
update_gcontact($contact);
|
||||
}
|
||||
|
||||
return($author);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transforms activity objects into an XML string
|
||||
*
|
||||
* @param object $xpath XPath object
|
||||
* @param object $activity Activity object
|
||||
* @param text $element element name
|
||||
*
|
||||
* @return string XML string
|
||||
*/
|
||||
private function transform_activity($xpath, $activity, $element) {
|
||||
if (!is_object($activity))
|
||||
return "";
|
||||
|
||||
$obj_doc = new DOMDocument("1.0", "utf-8");
|
||||
$obj_doc->formatOutput = true;
|
||||
|
||||
$obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element);
|
||||
|
||||
$activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue;
|
||||
xml_add_element($obj_doc, $obj_element, "type", $activity_type);
|
||||
|
||||
$id = $xpath->query("atom:id", $activity)->item(0);
|
||||
if (is_object($id))
|
||||
$obj_element->appendChild($obj_doc->importNode($id, true));
|
||||
|
||||
$title = $xpath->query("atom:title", $activity)->item(0);
|
||||
if (is_object($title))
|
||||
$obj_element->appendChild($obj_doc->importNode($title, true));
|
||||
|
||||
$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))
|
||||
$obj_element->appendChild($obj_doc->importNode($content, true));
|
||||
|
||||
$obj_doc->appendChild($obj_element);
|
||||
|
||||
$objxml = $obj_doc->saveXML($obj_element);
|
||||
|
||||
// @todo This isn't totally clean. We should find a way to transform the namespaces
|
||||
$objxml = str_replace("<".$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml);
|
||||
return($objxml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes the mail elements
|
||||
*
|
||||
* @param object $xpath XPath object
|
||||
* @param object $mail mail elements
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
*/
|
||||
private function process_mail($xpath, $mail, $importer) {
|
||||
|
||||
logger("Processing mails");
|
||||
|
||||
$msg = array();
|
||||
$msg["uid"] = $importer["importer_uid"];
|
||||
$msg["from-name"] = $xpath->query("dfrn:sender/dfrn:name/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["from-url"] = $xpath->query("dfrn:sender/dfrn:uri/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["from-photo"] = $xpath->query("dfrn:sender/dfrn:avatar/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["contact-id"] = $importer["id"];
|
||||
$msg["uri"] = $xpath->query("dfrn:id/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["parent-uri"] = $xpath->query("dfrn:in-reply-to/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["created"] = $xpath->query("dfrn:sentdate/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["title"] = $xpath->query("dfrn:subject/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["body"] = $xpath->query("dfrn:content/text()", $mail)->item(0)->nodeValue;
|
||||
$msg["seen"] = 0;
|
||||
$msg["replied"] = 0;
|
||||
|
||||
dbesc_array($msg);
|
||||
|
||||
$r = dbq("INSERT INTO `mail` (`".implode("`, `", array_keys($msg))."`) VALUES ('".implode("', '", array_values($msg))."')");
|
||||
|
||||
// send notifications.
|
||||
|
||||
$notif_params = array(
|
||||
"type" => NOTIFY_MAIL,
|
||||
"notify_flags" => $importer["notify-flags"],
|
||||
"language" => $importer["language"],
|
||||
"to_name" => $importer["username"],
|
||||
"to_email" => $importer["email"],
|
||||
"uid" => $importer["importer_uid"],
|
||||
"item" => $msg,
|
||||
"source_name" => $msg["from-name"],
|
||||
"source_link" => $importer["url"],
|
||||
"source_photo" => $importer["thumb"],
|
||||
"verb" => ACTIVITY_POST,
|
||||
"otype" => "mail"
|
||||
);
|
||||
|
||||
notification($notif_params);
|
||||
|
||||
logger("Mail is processed, notification was sent.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes the suggestion elements
|
||||
*
|
||||
* @param object $xpath XPath object
|
||||
* @param object $suggestion suggestion elements
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
*/
|
||||
private function process_suggestion($xpath, $suggestion, $importer) {
|
||||
|
||||
logger("Processing suggestions");
|
||||
|
||||
$suggest = array();
|
||||
$suggest["uid"] = $importer["importer_uid"];
|
||||
$suggest["cid"] = $importer["id"];
|
||||
$suggest["url"] = $xpath->query("dfrn:url/text()", $suggestion)->item(0)->nodeValue;
|
||||
$suggest["name"] = $xpath->query("dfrn:name/text()", $suggestion)->item(0)->nodeValue;
|
||||
$suggest["photo"] = $xpath->query("dfrn:photo/text()", $suggestion)->item(0)->nodeValue;
|
||||
$suggest["request"] = $xpath->query("dfrn:request/text()", $suggestion)->item(0)->nodeValue;
|
||||
$suggest["body"] = $xpath->query("dfrn:note/text()", $suggestion)->item(0)->nodeValue;
|
||||
|
||||
// Does our member already have a friend matching this description?
|
||||
|
||||
$r = q("SELECT `id` FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($suggest["name"]),
|
||||
dbesc(normalise_link($suggest["url"])),
|
||||
intval($suggest["uid"])
|
||||
);
|
||||
if(count($r))
|
||||
return false;
|
||||
|
||||
// Do we already have an fcontact record for this person?
|
||||
|
||||
$fid = 0;
|
||||
$r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
|
||||
dbesc($suggest["url"]),
|
||||
dbesc($suggest["name"]),
|
||||
dbesc($suggest["request"])
|
||||
);
|
||||
if(count($r)) {
|
||||
$fid = $r[0]["id"];
|
||||
|
||||
// OK, we do. Do we already have an introduction for this person ?
|
||||
$r = q("SELECT `id` FROM `intro` WHERE `uid` = %d AND `fid` = %d LIMIT 1",
|
||||
intval($suggest["uid"]),
|
||||
intval($fid)
|
||||
);
|
||||
if(count($r))
|
||||
return false;
|
||||
}
|
||||
if(!$fid)
|
||||
$r = q("INSERT INTO `fcontact` (`name`,`url`,`photo`,`request`) VALUES ('%s', '%s', '%s', '%s')",
|
||||
dbesc($suggest["name"]),
|
||||
dbesc($suggest["url"]),
|
||||
dbesc($suggest["photo"]),
|
||||
dbesc($suggest["request"])
|
||||
);
|
||||
$r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
|
||||
dbesc($suggest["url"]),
|
||||
dbesc($suggest["name"]),
|
||||
dbesc($suggest["request"])
|
||||
);
|
||||
if(count($r))
|
||||
$fid = $r[0]["id"];
|
||||
else
|
||||
// database record did not get created. Quietly give up.
|
||||
return false;
|
||||
|
||||
|
||||
$hash = random_string();
|
||||
|
||||
$r = q("INSERT INTO `intro` (`uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked`)
|
||||
VALUES(%d, %d, %d, '%s', '%s', '%s', %d)",
|
||||
intval($suggest["uid"]),
|
||||
intval($fid),
|
||||
intval($suggest["cid"]),
|
||||
dbesc($suggest["body"]),
|
||||
dbesc($hash),
|
||||
dbesc(datetime_convert()),
|
||||
intval(0)
|
||||
);
|
||||
|
||||
notification(array(
|
||||
"type" => NOTIFY_SUGGEST,
|
||||
"notify_flags" => $importer["notify-flags"],
|
||||
"language" => $importer["language"],
|
||||
"to_name" => $importer["username"],
|
||||
"to_email" => $importer["email"],
|
||||
"uid" => $importer["importer_uid"],
|
||||
"item" => $suggest,
|
||||
"link" => App::get_baseurl()."/notifications/intros",
|
||||
"source_name" => $importer["name"],
|
||||
"source_link" => $importer["url"],
|
||||
"source_photo" => $importer["photo"],
|
||||
"verb" => ACTIVITY_REQ_FRIEND,
|
||||
"otype" => "intro"
|
||||
));
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes the relocation elements
|
||||
*
|
||||
* @param object $xpath XPath object
|
||||
* @param object $relocation relocation elements
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
*/
|
||||
private function process_relocation($xpath, $relocation, $importer) {
|
||||
|
||||
logger("Processing relocations");
|
||||
|
||||
$relocate = array();
|
||||
$relocate["uid"] = $importer["importer_uid"];
|
||||
$relocate["cid"] = $importer["id"];
|
||||
$relocate["url"] = $xpath->query("dfrn:url/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["name"] = $xpath->query("dfrn:name/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["photo"] = $xpath->query("dfrn:photo/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["thumb"] = $xpath->query("dfrn:thumb/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["micro"] = $xpath->query("dfrn:micro/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["request"] = $xpath->query("dfrn:request/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["confirm"] = $xpath->query("dfrn:confirm/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["notify"] = $xpath->query("dfrn:notify/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["poll"] = $xpath->query("dfrn:poll/text()", $relocation)->item(0)->nodeValue;
|
||||
$relocate["sitepubkey"] = $xpath->query("dfrn:sitepubkey/text()", $relocation)->item(0)->nodeValue;
|
||||
|
||||
// update contact
|
||||
$r = q("SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d;",
|
||||
intval($importer["id"]),
|
||||
intval($importer["importer_uid"]));
|
||||
if (!$r)
|
||||
return false;
|
||||
|
||||
$old = $r[0];
|
||||
|
||||
$x = q("UPDATE `contact` SET
|
||||
`name` = '%s',
|
||||
`photo` = '%s',
|
||||
`thumb` = '%s',
|
||||
`micro` = '%s',
|
||||
`url` = '%s',
|
||||
`nurl` = '%s',
|
||||
`request` = '%s',
|
||||
`confirm` = '%s',
|
||||
`notify` = '%s',
|
||||
`poll` = '%s',
|
||||
`site-pubkey` = '%s'
|
||||
WHERE `id` = %d AND `uid` = %d;",
|
||||
dbesc($relocate["name"]),
|
||||
dbesc($relocate["photo"]),
|
||||
dbesc($relocate["thumb"]),
|
||||
dbesc($relocate["micro"]),
|
||||
dbesc($relocate["url"]),
|
||||
dbesc(normalise_link($relocate["url"])),
|
||||
dbesc($relocate["request"]),
|
||||
dbesc($relocate["confirm"]),
|
||||
dbesc($relocate["notify"]),
|
||||
dbesc($relocate["poll"]),
|
||||
dbesc($relocate["sitepubkey"]),
|
||||
intval($importer["id"]),
|
||||
intval($importer["importer_uid"]));
|
||||
|
||||
if ($x === false)
|
||||
return false;
|
||||
|
||||
// update items
|
||||
$fields = array(
|
||||
'owner-link' => array($old["url"], $relocate["url"]),
|
||||
'author-link' => array($old["url"], $relocate["url"]),
|
||||
'owner-avatar' => array($old["photo"], $relocate["photo"]),
|
||||
'author-avatar' => array($old["photo"], $relocate["photo"]),
|
||||
);
|
||||
foreach ($fields as $n=>$f){
|
||||
$x = q("UPDATE `item` SET `%s` = '%s' WHERE `%s` = '%s' AND `uid` = %d",
|
||||
$n, dbesc($f[1]),
|
||||
$n, dbesc($f[0]),
|
||||
intval($importer["importer_uid"]));
|
||||
if ($x === false)
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @TODO
|
||||
/// merge with current record, current contents have priority
|
||||
/// update record, set url-updated
|
||||
/// update profile photos
|
||||
/// schedule a scan?
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates an item
|
||||
*
|
||||
* @param array $current the current item record
|
||||
* @param array $item the new item record
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
* @param int $entrytype Is it a toplevel entry, a comment or a relayed comment?
|
||||
*/
|
||||
private function update_content($current, $item, $importer, $entrytype) {
|
||||
$changed = false;
|
||||
|
||||
if (edited_timestamp_is_newer($current, $item)) {
|
||||
|
||||
// do not accept (ignore) an earlier edit than one we currently have.
|
||||
if(datetime_convert("UTC","UTC",$item["edited"]) < $current["edited"])
|
||||
return(false);
|
||||
|
||||
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($item["title"]),
|
||||
dbesc($item["body"]),
|
||||
dbesc($item["tag"]),
|
||||
dbesc(datetime_convert("UTC","UTC",$item["edited"])),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item["uri"]),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
create_tags_from_itemuri($item["uri"], $importer["importer_uid"]);
|
||||
update_thread_uri($item["uri"], $importer["importer_uid"]);
|
||||
|
||||
$changed = true;
|
||||
|
||||
if ($entrytype == DFRN_REPLY_RC)
|
||||
proc_run("php", "include/notifier.php","comment-import", $current["id"]);
|
||||
}
|
||||
|
||||
// update last-child if it changes
|
||||
if($item["last-child"] AND ($item["last-child"] != $current["last-child"])) {
|
||||
$r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item["parent-uri"]),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
$r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
intval($item["last-child"]),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item["uri"]),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Detects the entry type of the item
|
||||
*
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
* @param array $item the new item record
|
||||
*
|
||||
* @return int Is it a toplevel entry, a comment or a relayed comment?
|
||||
*/
|
||||
private function get_entry_type($importer, $item) {
|
||||
if ($item["parent-uri"] != $item["uri"]) {
|
||||
$community = false;
|
||||
|
||||
if($importer["page-flags"] == PAGE_COMMUNITY || $importer["page-flags"] == PAGE_PRVGROUP) {
|
||||
$sql_extra = "";
|
||||
$community = true;
|
||||
logger("possible community action");
|
||||
} else
|
||||
$sql_extra = " AND `contact`.`self` AND `item`.`wall` ";
|
||||
|
||||
// was the top-level post for this action written by somebody on this site?
|
||||
// Specifically, the recipient?
|
||||
|
||||
$is_a_remote_action = false;
|
||||
|
||||
$r = q("SELECT `item`.`parent-uri` FROM `item`
|
||||
WHERE `item`.`uri` = '%s'
|
||||
LIMIT 1",
|
||||
dbesc($item["parent-uri"])
|
||||
);
|
||||
if($r && count($r)) {
|
||||
$r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item`
|
||||
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
||||
WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' OR `item`.`thr-parent` = '%s')
|
||||
AND `item`.`uid` = %d
|
||||
$sql_extra
|
||||
LIMIT 1",
|
||||
dbesc($r[0]["parent-uri"]),
|
||||
dbesc($r[0]["parent-uri"]),
|
||||
dbesc($r[0]["parent-uri"]),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
if($r && count($r))
|
||||
$is_a_remote_action = true;
|
||||
}
|
||||
|
||||
// Does this have the characteristics of a community or private group action?
|
||||
// If it's an action to a wall post on a community/prvgroup page it's a
|
||||
// valid community action. Also forum_mode makes it valid for sure.
|
||||
// If neither, it's not.
|
||||
|
||||
if($is_a_remote_action && $community) {
|
||||
if((!$r[0]["forum_mode"]) && (!$r[0]["wall"])) {
|
||||
$is_a_remote_action = false;
|
||||
logger("not a community action");
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_a_remote_action)
|
||||
return DFRN_REPLY_RC;
|
||||
else
|
||||
return DFRN_REPLY;
|
||||
|
||||
} else
|
||||
return DFRN_TOP_LEVEL;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a "poke"
|
||||
*
|
||||
* @param array $item the new item record
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
* @param int $posted_id The record number of item record that was just posted
|
||||
*/
|
||||
private function do_poke($item, $importer, $posted_id) {
|
||||
$verb = urldecode(substr($item["verb"],strpos($item["verb"], "#")+1));
|
||||
if(!$verb)
|
||||
return;
|
||||
$xo = parse_xml_string($item["object"],false);
|
||||
|
||||
if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
|
||||
|
||||
// somebody was poked/prodded. Was it me?
|
||||
foreach($xo->link as $l) {
|
||||
$atts = $l->attributes();
|
||||
switch($atts["rel"]) {
|
||||
case "alternate":
|
||||
$Blink = $atts["href"];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($Blink && link_compare($Blink,App::get_baseurl()."/profile/".$importer["nickname"])) {
|
||||
|
||||
// send a notification
|
||||
notification(array(
|
||||
"type" => NOTIFY_POKE,
|
||||
"notify_flags" => $importer["notify-flags"],
|
||||
"language" => $importer["language"],
|
||||
"to_name" => $importer["username"],
|
||||
"to_email" => $importer["email"],
|
||||
"uid" => $importer["importer_uid"],
|
||||
"item" => $item,
|
||||
"link" => App::get_baseurl()."/display/".urlencode(get_item_guid($posted_id)),
|
||||
"source_name" => stripslashes($item["author-name"]),
|
||||
"source_link" => $item["author-link"],
|
||||
"source_photo" => ((link_compare($item["author-link"],$importer["url"]))
|
||||
? $importer["thumb"] : $item["author-avatar"]),
|
||||
"verb" => $item["verb"],
|
||||
"otype" => "person",
|
||||
"activity" => $verb,
|
||||
"parent" => $item["parent"]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes several actions, depending on the verb
|
||||
*
|
||||
* @param int $entrytype Is it a toplevel entry, a comment or a relayed comment?
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
* @param array $item the new item record
|
||||
* @param bool $is_like Is the verb a "like"?
|
||||
*
|
||||
* @return bool Should the processing of the entries be continued?
|
||||
*/
|
||||
private function process_verbs($entrytype, $importer, &$item, &$is_like) {
|
||||
if (($entrytype == DFRN_TOP_LEVEL)) {
|
||||
// The filling of the the "contact" variable is done for legcy reasons
|
||||
// The functions below are partly used by ostatus.php as well - where we have this variable
|
||||
$r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"]));
|
||||
$contact = $r[0];
|
||||
$nickname = $contact["nick"];
|
||||
|
||||
// Big question: Do we need these functions? They were part of the "consume_feed" function.
|
||||
// This function once was responsible for DFRN and OStatus.
|
||||
if(activity_match($item["verb"],ACTIVITY_FOLLOW)) {
|
||||
logger("New follower");
|
||||
new_follower($importer, $contact, $item, $nickname);
|
||||
return false;
|
||||
}
|
||||
if(activity_match($item["verb"],ACTIVITY_UNFOLLOW)) {
|
||||
logger("Lost follower");
|
||||
lose_follower($importer, $contact, $item);
|
||||
return false;
|
||||
}
|
||||
if(activity_match($item["verb"],ACTIVITY_REQ_FRIEND)) {
|
||||
logger("New friend request");
|
||||
new_follower($importer, $contact, $item, $nickname, true);
|
||||
return false;
|
||||
}
|
||||
if(activity_match($item["verb"],ACTIVITY_UNFRIEND)) {
|
||||
logger("Lost sharer");
|
||||
lose_sharer($importer, $contact, $item);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(($item["verb"] === ACTIVITY_LIKE)
|
||||
|| ($item["verb"] === ACTIVITY_DISLIKE)
|
||||
|| ($item["verb"] === ACTIVITY_ATTEND)
|
||||
|| ($item["verb"] === ACTIVITY_ATTENDNO)
|
||||
|| ($item["verb"] === ACTIVITY_ATTENDMAYBE)) {
|
||||
$is_like = true;
|
||||
$item["type"] = "activity";
|
||||
$item["gravity"] = GRAVITY_LIKE;
|
||||
// only one like or dislike per person
|
||||
// splitted into two queries for performance issues
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($item["uid"]),
|
||||
dbesc($item["author-link"]),
|
||||
dbesc($item["verb"]),
|
||||
dbesc($item["parent-uri"])
|
||||
);
|
||||
if($r && count($r))
|
||||
return false;
|
||||
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($item["uid"]),
|
||||
dbesc($item["author-link"]),
|
||||
dbesc($item["verb"]),
|
||||
dbesc($item["parent-uri"])
|
||||
);
|
||||
if($r && count($r))
|
||||
return false;
|
||||
} else
|
||||
$is_like = false;
|
||||
|
||||
if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) {
|
||||
|
||||
$xo = parse_xml_string($item["object"],false);
|
||||
$xt = parse_xml_string($item["target"],false);
|
||||
|
||||
if($xt->type == ACTIVITY_OBJ_NOTE) {
|
||||
$r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($xt->id),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
|
||||
if(!count($r))
|
||||
return false;
|
||||
|
||||
// extract tag, if not duplicate, add to parent item
|
||||
if($xo->content) {
|
||||
if(!(stristr($r[0]["tag"],trim($xo->content)))) {
|
||||
q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d",
|
||||
dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
|
||||
intval($r[0]["id"])
|
||||
);
|
||||
create_tags_from_item($r[0]["id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes the link elements
|
||||
*
|
||||
* @param object $links link elements
|
||||
* @param array $item the item record
|
||||
*/
|
||||
private function parse_links($links, &$item) {
|
||||
$rel = "";
|
||||
$href = "";
|
||||
$type = "";
|
||||
$length = "0";
|
||||
$title = "";
|
||||
foreach ($links AS $link) {
|
||||
foreach($link->attributes AS $attributes) {
|
||||
if ($attributes->name == "href")
|
||||
$href = $attributes->textContent;
|
||||
if ($attributes->name == "rel")
|
||||
$rel = $attributes->textContent;
|
||||
if ($attributes->name == "type")
|
||||
$type = $attributes->textContent;
|
||||
if ($attributes->name == "length")
|
||||
$length = $attributes->textContent;
|
||||
if ($attributes->name == "title")
|
||||
$title = $attributes->textContent;
|
||||
}
|
||||
if (($rel != "") AND ($href != ""))
|
||||
switch($rel) {
|
||||
case "alternate":
|
||||
$item["plink"] = $href;
|
||||
break;
|
||||
case "enclosure":
|
||||
$enclosure = $href;
|
||||
if(strlen($item["attach"]))
|
||||
$item["attach"] .= ",";
|
||||
|
||||
$item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes the entry elements which contain the items and comments
|
||||
*
|
||||
* @param array $header Array of the header elements that always stay the same
|
||||
* @param object $xpath XPath object
|
||||
* @param object $entry entry elements
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
*/
|
||||
private function process_entry($header, $xpath, $entry, $importer) {
|
||||
|
||||
logger("Processing entries");
|
||||
|
||||
$item = $header;
|
||||
|
||||
// Get the uri
|
||||
$item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
// Fetch the owner
|
||||
$owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", true);
|
||||
|
||||
$item["owner-name"] = $owner["name"];
|
||||
$item["owner-link"] = $owner["link"];
|
||||
$item["owner-avatar"] = $owner["avatar"];
|
||||
|
||||
// fetch the author
|
||||
$author = self::fetchauthor($xpath, $entry, $importer, "atom:author", true);
|
||||
|
||||
$item["author-name"] = $author["name"];
|
||||
$item["author-link"] = $author["link"];
|
||||
$item["author-avatar"] = $author["avatar"];
|
||||
|
||||
$item["title"] = $xpath->query("atom:title/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
$item["created"] = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue;
|
||||
$item["edited"] = $xpath->query("atom:updated/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
$item["body"] = $xpath->query("dfrn:env/text()", $entry)->item(0)->nodeValue;
|
||||
$item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]);
|
||||
// make sure nobody is trying to sneak some html tags by us
|
||||
$item["body"] = notags(base64url_decode($item["body"]));
|
||||
|
||||
$item["body"] = limit_body_size($item["body"]);
|
||||
|
||||
/// @todo Do we really need this check for HTML elements? (It was copied from the old function)
|
||||
if((strpos($item['body'],'<') !== false) && (strpos($item['body'],'>') !== false)) {
|
||||
|
||||
$item['body'] = reltoabs($item['body'],$base_url);
|
||||
|
||||
$item['body'] = html2bb_video($item['body']);
|
||||
|
||||
$item['body'] = oembed_html2bbcode($item['body']);
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Cache.DefinitionImpl', null);
|
||||
|
||||
// we shouldn't need a whitelist, because the bbcode converter
|
||||
// will strip out any unsupported tags.
|
||||
|
||||
$purifier = new HTMLPurifier($config);
|
||||
$item['body'] = $purifier->purify($item['body']);
|
||||
|
||||
$item['body'] = @html2bbcode($item['body']);
|
||||
}
|
||||
|
||||
// We don't need the content element since "dfrn:env" is always present
|
||||
//$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
$item["last-child"] = $xpath->query("dfrn:comment-allow/text()", $entry)->item(0)->nodeValue;
|
||||
$item["location"] = $xpath->query("dfrn:location/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
$georsspoint = $xpath->query("georss:point", $entry);
|
||||
if ($georsspoint)
|
||||
$item["coord"] = $georsspoint->item(0)->nodeValue;
|
||||
|
||||
$item["private"] = $xpath->query("dfrn:private/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
$item["extid"] = $xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
if ($xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue == "true")
|
||||
$item["bookmark"] = true;
|
||||
|
||||
$notice_info = $xpath->query("statusnet:notice_info", $entry);
|
||||
if ($notice_info AND ($notice_info->length > 0)) {
|
||||
foreach($notice_info->item(0)->attributes AS $attributes) {
|
||||
if ($attributes->name == "source")
|
||||
$item["app"] = strip_tags($attributes->textContent);
|
||||
}
|
||||
}
|
||||
|
||||
$item["guid"] = $xpath->query("dfrn:diaspora_guid/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
// We store the data from "dfrn:diaspora_signature" in a different table, this is done in "item_store"
|
||||
$dsprsig = unxmlify($xpath->query("dfrn:diaspora_signature/text()", $entry)->item(0)->nodeValue);
|
||||
if ($dsprsig != "")
|
||||
$item["dsprsig"] = $dsprsig;
|
||||
|
||||
$item["verb"] = $xpath->query("activity:verb/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
if ($xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue != "")
|
||||
$item["object-type"] = $xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue;
|
||||
|
||||
$object = $xpath->query("activity:object", $entry)->item(0);
|
||||
$item["object"] = self::transform_activity($xpath, $object, "object");
|
||||
|
||||
if (trim($item["object"]) != "") {
|
||||
$r = parse_xml_string($item["object"], false);
|
||||
if (isset($r->type))
|
||||
$item["object-type"] = $r->type;
|
||||
}
|
||||
|
||||
$target = $xpath->query("activity:target", $entry)->item(0);
|
||||
$item["target"] = self::transform_activity($xpath, $target, "target");
|
||||
|
||||
$categories = $xpath->query("atom:category", $entry);
|
||||
if ($categories) {
|
||||
foreach ($categories AS $category) {
|
||||
foreach($category->attributes AS $attributes)
|
||||
if ($attributes->name == "term") {
|
||||
$term = $attributes->textContent;
|
||||
if(strlen($item["tag"]))
|
||||
$item["tag"] .= ",";
|
||||
|
||||
$item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$enclosure = "";
|
||||
|
||||
$links = $xpath->query("atom:link", $entry);
|
||||
if ($links)
|
||||
self::parse_links($links, $item);
|
||||
|
||||
// Is it a reply or a top level posting?
|
||||
$item["parent-uri"] = $item["uri"];
|
||||
|
||||
$inreplyto = $xpath->query("thr:in-reply-to", $entry);
|
||||
if (is_object($inreplyto->item(0)))
|
||||
foreach($inreplyto->item(0)->attributes AS $attributes)
|
||||
if ($attributes->name == "ref")
|
||||
$item["parent-uri"] = $attributes->textContent;
|
||||
|
||||
// Get the type of the item (Top level post, reply or remote reply)
|
||||
$entrytype = self::get_entry_type($importer, $item);
|
||||
|
||||
// Now assign the rest of the values that depend on the type of the message
|
||||
if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) {
|
||||
if (!isset($item["object-type"]))
|
||||
$item["object-type"] = ACTIVITY_OBJ_COMMENT;
|
||||
|
||||
if ($item["contact-id"] != $owner["contact-id"])
|
||||
$item["contact-id"] = $owner["contact-id"];
|
||||
|
||||
if (($item["network"] != $owner["network"]) AND ($owner["network"] != ""))
|
||||
$item["network"] = $owner["network"];
|
||||
|
||||
if ($item["contact-id"] != $author["contact-id"])
|
||||
$item["contact-id"] = $author["contact-id"];
|
||||
|
||||
if (($item["network"] != $author["network"]) AND ($author["network"] != ""))
|
||||
$item["network"] = $author["network"];
|
||||
|
||||
// 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) {
|
||||
$item["type"] = "remote-comment";
|
||||
$item["wall"] = 1;
|
||||
} elseif ($entrytype == DFRN_TOP_LEVEL) {
|
||||
if (!isset($item["object-type"]))
|
||||
$item["object-type"] = ACTIVITY_OBJ_NOTE;
|
||||
|
||||
// Is it an event?
|
||||
if ($item["object-type"] == ACTIVITY_OBJ_EVENT) {
|
||||
logger("Item ".$item["uri"]." seems to contain an event.", LOGGER_DEBUG);
|
||||
$ev = bbtoevent($item["body"]);
|
||||
if((x($ev, "desc") || x($ev, "summary")) && x($ev, "start")) {
|
||||
logger("Event in item ".$item["uri"]." was found.", LOGGER_DEBUG);
|
||||
$ev["cid"] = $importer["id"];
|
||||
$ev["uid"] = $importer["uid"];
|
||||
$ev["uri"] = $item["uri"];
|
||||
$ev["edited"] = $item["edited"];
|
||||
$ev['private'] = $item['private'];
|
||||
$ev["guid"] = $item["guid"];
|
||||
|
||||
$r = q("SELECT `id` FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item["uri"]),
|
||||
intval($importer["uid"])
|
||||
);
|
||||
if(count($r))
|
||||
$ev["id"] = $r[0]["id"];
|
||||
|
||||
$event_id = event_store($ev);
|
||||
logger("Event ".$event_id." was stored", LOGGER_DEBUG);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item["uri"]),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
|
||||
if (!self::process_verbs($entrytype, $importer, $item, $is_like)) {
|
||||
logger("Exiting because 'process_verbs' told us so", LOGGER_DEBUG);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update content if 'updated' changes
|
||||
if(count($r)) {
|
||||
if (self::update_content($r[0], $item, $importer, $entrytype))
|
||||
logger("Item ".$item["uri"]." was updated.", LOGGER_DEBUG);
|
||||
else
|
||||
logger("Item ".$item["uri"]." already existed.", LOGGER_DEBUG);
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) {
|
||||
$posted_id = item_store($item);
|
||||
$parent = 0;
|
||||
|
||||
if($posted_id) {
|
||||
|
||||
logger("Reply from contact ".$item["contact-id"]." was stored with id ".$posted_id, LOGGER_DEBUG);
|
||||
|
||||
$item["id"] = $posted_id;
|
||||
|
||||
$r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
|
||||
intval($posted_id),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
if(count($r)) {
|
||||
$parent = $r[0]["parent"];
|
||||
$parent_uri = $r[0]["parent-uri"];
|
||||
}
|
||||
|
||||
if(!$is_like) {
|
||||
$r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
intval($importer["importer_uid"]),
|
||||
intval($r[0]["parent"])
|
||||
);
|
||||
|
||||
$r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
intval($importer["importer_uid"]),
|
||||
intval($posted_id)
|
||||
);
|
||||
}
|
||||
|
||||
if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) {
|
||||
logger("Notifying followers about comment ".$posted_id, LOGGER_DEBUG);
|
||||
proc_run("php", "include/notifier.php", "comment-import", $posted_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else { // $entrytype == DFRN_TOP_LEVEL
|
||||
if(!link_compare($item["owner-link"],$importer["url"])) {
|
||||
// The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
|
||||
// but otherwise there's a possible data mixup on the sender's system.
|
||||
// the tgroup delivery code called from item_store will correct it if it's a forum,
|
||||
// but we're going to unconditionally correct it here so that the post will always be owned by our contact.
|
||||
logger('Correcting item owner.', LOGGER_DEBUG);
|
||||
$item["owner-name"] = $importer["senderName"];
|
||||
$item["owner-link"] = $importer["url"];
|
||||
$item["owner-avatar"] = $importer["thumb"];
|
||||
}
|
||||
|
||||
if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) {
|
||||
logger("Contact ".$importer["id"]." is only follower and tgroup check was negative.", LOGGER_DEBUG);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is my contact on another system, but it's really me.
|
||||
// Turn this into a wall post.
|
||||
$notify = item_is_remote_self($importer, $item);
|
||||
|
||||
$posted_id = item_store($item, false, $notify);
|
||||
|
||||
logger("Item was stored with id ".$posted_id, LOGGER_DEBUG);
|
||||
|
||||
if(stristr($item["verb"],ACTIVITY_POKE))
|
||||
self::do_poke($item, $importer, $posted_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deletes items
|
||||
*
|
||||
* @param object $xpath XPath object
|
||||
* @param object $deletion deletion elements
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
*/
|
||||
private function process_deletion($xpath, $deletion, $importer) {
|
||||
|
||||
logger("Processing deletions");
|
||||
|
||||
foreach($deletion->attributes AS $attributes) {
|
||||
if ($attributes->name == "ref")
|
||||
$uri = $attributes->textContent;
|
||||
if ($attributes->name == "when")
|
||||
$when = $attributes->textContent;
|
||||
}
|
||||
if ($when)
|
||||
$when = datetime_convert("UTC", "UTC", $when, "Y-m-d H:i:s");
|
||||
else
|
||||
$when = datetime_convert("UTC", "UTC", "now", "Y-m-d H:i:s");
|
||||
|
||||
if (!$uri OR !$importer["id"])
|
||||
return false;
|
||||
|
||||
/// @todo Only select the used fields
|
||||
$r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
|
||||
WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
|
||||
dbesc($uri),
|
||||
intval($importer["uid"]),
|
||||
intval($importer["id"])
|
||||
);
|
||||
if(!count($r)) {
|
||||
logger("Item with uri ".$uri." from contact ".$importer["id"]." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG);
|
||||
return;
|
||||
} else {
|
||||
|
||||
$item = $r[0];
|
||||
|
||||
$entrytype = self::get_entry_type($importer, $item);
|
||||
|
||||
if(!$item["deleted"])
|
||||
logger('deleting item '.$item["id"].' uri='.$uri, LOGGER_DEBUG);
|
||||
else
|
||||
return;
|
||||
|
||||
if($item["object-type"] === ACTIVITY_OBJ_EVENT) {
|
||||
logger("Deleting event ".$item["event-id"], LOGGER_DEBUG);
|
||||
event_delete($item["event-id"]);
|
||||
}
|
||||
|
||||
if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) {
|
||||
$xo = parse_xml_string($item["object"],false);
|
||||
$xt = parse_xml_string($item["target"],false);
|
||||
if($xt->type === ACTIVITY_OBJ_NOTE) {
|
||||
$i = q("SELECT `id`, `contact-id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($xt->id),
|
||||
intval($importer["importer_uid"])
|
||||
);
|
||||
if(count($i)) {
|
||||
|
||||
// For tags, the owner cannot remove the tag on the author's copy of the post.
|
||||
|
||||
$owner_remove = (($item["contact-id"] == $i[0]["contact-id"]) ? true: false);
|
||||
$author_remove = (($item["origin"] && $item["self"]) ? true : false);
|
||||
$author_copy = (($item["origin"]) ? true : false);
|
||||
|
||||
if($owner_remove && $author_copy)
|
||||
return;
|
||||
if($author_remove || $owner_remove) {
|
||||
$tags = explode(',',$i[0]["tag"]);
|
||||
$newtags = array();
|
||||
if(count($tags)) {
|
||||
foreach($tags as $tag)
|
||||
if(trim($tag) !== trim($xo->body))
|
||||
$newtags[] = trim($tag);
|
||||
}
|
||||
q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d",
|
||||
dbesc(implode(',',$newtags)),
|
||||
intval($i[0]["id"])
|
||||
);
|
||||
create_tags_from_item($i[0]["id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($entrytype == DFRN_TOP_LEVEL) {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `parent-uri` = '%s' AND `uid` = %d",
|
||||
dbesc($when),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($uri),
|
||||
intval($importer["uid"])
|
||||
);
|
||||
create_tags_from_itemuri($uri, $importer["uid"]);
|
||||
create_files_from_itemuri($uri, $importer["uid"]);
|
||||
update_thread_uri($uri, $importer["uid"]);
|
||||
} else {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($when),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($uri),
|
||||
intval($importer["uid"])
|
||||
);
|
||||
create_tags_from_itemuri($uri, $importer["uid"]);
|
||||
create_files_from_itemuri($uri, $importer["uid"]);
|
||||
update_thread_uri($uri, $importer["importer_uid"]);
|
||||
if($item["last-child"]) {
|
||||
// ensure that last-child is set in case the comment that had it just got wiped.
|
||||
q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item["parent-uri"]),
|
||||
intval($item["uid"])
|
||||
);
|
||||
// who is the last child now?
|
||||
$r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
|
||||
ORDER BY `created` DESC LIMIT 1",
|
||||
dbesc($item["parent-uri"]),
|
||||
intval($importer["uid"])
|
||||
);
|
||||
if(count($r)) {
|
||||
q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
|
||||
intval($r[0]["id"])
|
||||
);
|
||||
}
|
||||
}
|
||||
// if this is a relayed delete, propagate it to other recipients
|
||||
|
||||
if($entrytype == DFRN_REPLY_RC) {
|
||||
logger("Notifying followers about deletion of post ".$item["id"], LOGGER_DEBUG);
|
||||
proc_run("php", "include/notifier.php","drop", $item["id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Imports a DFRN message
|
||||
*
|
||||
* @param text $xml The DFRN message
|
||||
* @param array $importer Record of the importer user mixed with contact of the content
|
||||
* @param bool $sort_by_date Is used when feeds are polled
|
||||
*/
|
||||
public static function import($xml,$importer, $sort_by_date = false) {
|
||||
|
||||
if ($xml == "")
|
||||
return;
|
||||
|
||||
if($importer["readonly"]) {
|
||||
// We aren't receiving stuff from this person. But we will quietly ignore them
|
||||
// rather than a blatant "go away" message.
|
||||
logger('ignoring contact '.$importer["id"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
@$doc->loadXML($xml);
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
$xpath->registerNamespace("atom", NAMESPACE_ATOM1);
|
||||
$xpath->registerNamespace("thr", NAMESPACE_THREAD);
|
||||
$xpath->registerNamespace("at", NAMESPACE_TOMB);
|
||||
$xpath->registerNamespace("media", NAMESPACE_MEDIA);
|
||||
$xpath->registerNamespace("dfrn", NAMESPACE_DFRN);
|
||||
$xpath->registerNamespace("activity", NAMESPACE_ACTIVITY);
|
||||
$xpath->registerNamespace("georss", NAMESPACE_GEORSS);
|
||||
$xpath->registerNamespace("poco", NAMESPACE_POCO);
|
||||
$xpath->registerNamespace("ostatus", NAMESPACE_OSTATUS);
|
||||
$xpath->registerNamespace("statusnet", NAMESPACE_STATUSNET);
|
||||
|
||||
$header = array();
|
||||
$header["uid"] = $importer["uid"];
|
||||
$header["network"] = NETWORK_DFRN;
|
||||
$header["type"] = "remote";
|
||||
$header["wall"] = 0;
|
||||
$header["origin"] = 0;
|
||||
$header["contact-id"] = $importer["id"];
|
||||
|
||||
// Update the contact table if the data has changed
|
||||
// Only the "dfrn:owner" in the head section contains all data
|
||||
self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false);
|
||||
|
||||
logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG);
|
||||
|
||||
// is it a public forum? Private forums aren't supported by now with this method
|
||||
$forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue);
|
||||
|
||||
if ($forum != $importer["forum"])
|
||||
q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d",
|
||||
intval($forum), intval($forum),
|
||||
intval($importer["id"])
|
||||
);
|
||||
|
||||
$mails = $xpath->query("/atom:feed/dfrn:mail");
|
||||
foreach ($mails AS $mail)
|
||||
self::process_mail($xpath, $mail, $importer);
|
||||
|
||||
$suggestions = $xpath->query("/atom:feed/dfrn:suggest");
|
||||
foreach ($suggestions AS $suggestion)
|
||||
self::process_suggestion($xpath, $suggestion, $importer);
|
||||
|
||||
$relocations = $xpath->query("/atom:feed/dfrn:relocate");
|
||||
foreach ($relocations AS $relocation)
|
||||
self::process_relocation($xpath, $relocation, $importer);
|
||||
|
||||
$deletions = $xpath->query("/atom:feed/at:deleted-entry");
|
||||
foreach ($deletions AS $deletion)
|
||||
self::process_deletion($xpath, $deletion, $importer);
|
||||
|
||||
if (!$sort_by_date) {
|
||||
$entries = $xpath->query("/atom:feed/atom:entry");
|
||||
foreach ($entries AS $entry)
|
||||
self::process_entry($header, $xpath, $entry, $importer);
|
||||
} else {
|
||||
$newentries = array();
|
||||
$entries = $xpath->query("/atom:feed/atom:entry");
|
||||
foreach ($entries AS $entry) {
|
||||
$created = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue;
|
||||
$newentries[strtotime($created)] = $entry;
|
||||
}
|
||||
|
||||
// Now sort after the publishing date
|
||||
ksort($newentries);
|
||||
|
||||
foreach ($newentries AS $entry)
|
||||
self::process_entry($header, $xpath, $entry, $importer);
|
||||
}
|
||||
logger("Import done for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
@ -14,6 +14,7 @@ require_once('include/queue_fn.php');
|
|||
require_once('include/lock.php');
|
||||
require_once('include/threads.php');
|
||||
require_once('mod/share.php');
|
||||
require_once('include/enotify.php');
|
||||
|
||||
function diaspora_dispatch_public($msg) {
|
||||
|
||||
|
|
@ -728,7 +729,7 @@ function diaspora_request($importer,$xml) {
|
|||
|
||||
require_once('include/Photo.php');
|
||||
|
||||
$photos = import_profile_photo($contact_record['photo'],$importer['uid'],$contact_record['id']);
|
||||
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
|
||||
|
|
@ -739,26 +740,17 @@ function diaspora_request($importer,$xml) {
|
|||
else
|
||||
$new_relation = CONTACT_IS_FOLLOWER;
|
||||
|
||||
$r = q("UPDATE `contact` SET
|
||||
`photo` = '%s',
|
||||
`thumb` = '%s',
|
||||
`micro` = '%s',
|
||||
`rel` = %d,
|
||||
$r = q("UPDATE `contact` SET `rel` = %d,
|
||||
`name-date` = '%s',
|
||||
`uri-date` = '%s',
|
||||
`avatar-date` = '%s',
|
||||
`blocked` = 0,
|
||||
`pending` = 0,
|
||||
`writable` = 1
|
||||
WHERE `id` = %d
|
||||
",
|
||||
dbesc($photos[0]),
|
||||
dbesc($photos[1]),
|
||||
dbesc($photos[2]),
|
||||
intval($new_relation),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
intval($contact_record['id'])
|
||||
);
|
||||
|
||||
|
|
@ -826,6 +818,23 @@ function diaspora_plink($addr, $guid) {
|
|||
return 'https://'.substr($addr,strpos($addr,'@')+1).'/posts/'.$guid;
|
||||
}
|
||||
|
||||
function diaspora_repair_signature($signature, $handle = "", $level = 1) {
|
||||
|
||||
if ($signature == "")
|
||||
return($signature);
|
||||
|
||||
if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) {
|
||||
$signature = base64_decode($signature);
|
||||
logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG);
|
||||
|
||||
// Do a recursive call to be able to fix even multiple levels
|
||||
if ($level < 10)
|
||||
$signature = diaspora_repair_signature($signature, $handle, ++$level);
|
||||
}
|
||||
|
||||
return($signature);
|
||||
}
|
||||
|
||||
function diaspora_post($importer,$xml,$msg) {
|
||||
|
||||
$a = get_app();
|
||||
|
|
@ -1565,62 +1574,22 @@ function diaspora_comment($importer,$xml,$msg) {
|
|||
//);
|
||||
//}
|
||||
|
||||
if(($parent_item['origin']) && (! $parent_author_signature)) {
|
||||
// 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(base64_encode($author_signature)),
|
||||
dbesc($author_signature_base64),
|
||||
dbesc($diaspora_handle)
|
||||
);
|
||||
|
||||
// if the message isn't already being relayed, notify others
|
||||
// the existence of parent_author_signature means the parent_author or owner
|
||||
// is already relaying.
|
||||
|
||||
// notify others
|
||||
proc_run('php','include/notifier.php','comment-import',$message_id);
|
||||
}
|
||||
|
||||
$myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0 ",
|
||||
dbesc($parent_item['uri']),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
|
||||
if(count($myconv)) {
|
||||
$importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
|
||||
|
||||
foreach($myconv as $conv) {
|
||||
|
||||
// now if we find a match, it means we're in this conversation
|
||||
|
||||
if(! link_compare($conv['author-link'],$importer_url))
|
||||
continue;
|
||||
|
||||
require_once('include/enotify.php');
|
||||
|
||||
$conv_parent = $conv['parent'];
|
||||
|
||||
notification(array(
|
||||
'type' => NOTIFY_COMMENT,
|
||||
'notify_flags' => $importer['notify-flags'],
|
||||
'language' => $importer['language'],
|
||||
'to_name' => $importer['username'],
|
||||
'to_email' => $importer['email'],
|
||||
'uid' => $importer['uid'],
|
||||
'item' => $datarray,
|
||||
'link' => $a->get_baseurl().'/display/'.urlencode($datarray['guid']),
|
||||
'source_name' => $datarray['author-name'],
|
||||
'source_link' => $datarray['author-link'],
|
||||
'source_photo' => $datarray['author-avatar'],
|
||||
'verb' => ACTIVITY_POST,
|
||||
'otype' => 'item',
|
||||
'parent' => $conv_parent,
|
||||
'parent_uri' => $parent_uri
|
||||
));
|
||||
|
||||
// only send one notification
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1775,7 +1744,6 @@ function diaspora_conversation($importer,$xml,$msg) {
|
|||
intval($conversation['id'])
|
||||
);
|
||||
|
||||
require_once('include/enotify.php');
|
||||
notification(array(
|
||||
'type' => NOTIFY_MAIL,
|
||||
'notify_flags' => $importer['notify-flags'],
|
||||
|
|
@ -1973,11 +1941,15 @@ function diaspora_photo($importer,$xml,$msg,$attempt=1) {
|
|||
array($remote_photo_name, 'scaled_full_' . $remote_photo_name));
|
||||
|
||||
if(strpos($parent_item['body'],$link_text) === false) {
|
||||
|
||||
$parent_item['body'] = $link_text . $parent_item['body'];
|
||||
|
||||
$r = q("UPDATE `item` SET `body` = '%s', `visible` = 1 WHERE `id` = %d AND `uid` = %d",
|
||||
dbesc($link_text . $parent_item['body']),
|
||||
dbesc($parent_item['body']),
|
||||
intval($parent_item['id']),
|
||||
intval($parent_item['uid'])
|
||||
);
|
||||
put_item_in_cache($parent_item, true);
|
||||
update_thread($parent_item['id']);
|
||||
}
|
||||
|
||||
|
|
@ -2222,21 +2194,21 @@ EOT;
|
|||
// );
|
||||
//}
|
||||
|
||||
if(! $parent_author_signature) {
|
||||
// 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(base64_encode($author_signature)),
|
||||
dbesc($author_signature_base64),
|
||||
dbesc($diaspora_handle)
|
||||
);
|
||||
}
|
||||
|
||||
// if the message isn't already being relayed, notify others
|
||||
// the existence of parent_author_signature means the parent_author or owner
|
||||
// is already relaying. The parent_item['origin'] indicates the message was created on our system
|
||||
|
||||
if(($parent_item['origin']) && (! $parent_author_signature))
|
||||
// notify others
|
||||
proc_run('php','include/notifier.php','comment-import',$message_id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -2332,8 +2304,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) {
|
|||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
|
||||
$sig_decode = base64_decode($sig);
|
||||
|
||||
|
|
@ -2367,7 +2338,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) {
|
|||
intval($r[0]['parent'])
|
||||
);
|
||||
if(count($p)) {
|
||||
if(($p[0]['origin']) && (! $parent_author_signature)) {
|
||||
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),
|
||||
|
|
@ -2407,10 +2378,10 @@ function diaspora_profile($importer,$xml,$msg) {
|
|||
if(! $contact)
|
||||
return;
|
||||
|
||||
if($contact['blocked']) {
|
||||
logger('diaspora_post: Ignoring this author.');
|
||||
return 202;
|
||||
}
|
||||
//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);
|
||||
|
|
@ -2418,6 +2389,8 @@ function diaspora_profile($importer,$xml,$msg) {
|
|||
$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);
|
||||
|
|
@ -2432,6 +2405,8 @@ function diaspora_profile($importer,$xml,$msg) {
|
|||
$keywords = implode(", ", $keywords);
|
||||
|
||||
$handle_parts = explode("@", $diaspora_handle);
|
||||
$nick = $handle_parts[0];
|
||||
|
||||
if($name === '') {
|
||||
$name = $handle_parts[0];
|
||||
}
|
||||
|
|
@ -2448,7 +2423,7 @@ function diaspora_profile($importer,$xml,$msg) {
|
|||
|
||||
require_once('include/Photo.php');
|
||||
|
||||
$images = import_profile_photo($image_url,$importer['uid'],$contact['id']);
|
||||
update_contact_avatar($image_url,$importer['uid'],$contact['id']);
|
||||
|
||||
// Generic birthday. We don't know the timezone. The year is irrelevant.
|
||||
|
||||
|
|
@ -2466,12 +2441,11 @@ function diaspora_profile($importer,$xml,$msg) {
|
|||
/// @TODO Update name on item['author-name'] if the name changed. See consume_feed()
|
||||
/// (Not doing this currently because D* protocol is scheduled for revision soon).
|
||||
|
||||
$r = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' , `bd` = '%s', `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d",
|
||||
$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(datetime_convert()),
|
||||
dbesc($images[0]),
|
||||
dbesc($images[1]),
|
||||
dbesc($images[2]),
|
||||
dbesc($nick),
|
||||
dbesc($diaspora_handle),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($birthday),
|
||||
dbesc($location),
|
||||
|
|
@ -2482,26 +2456,17 @@ function diaspora_profile($importer,$xml,$msg) {
|
|||
intval($importer['uid'])
|
||||
);
|
||||
|
||||
if (unxmlify($xml->searchable) == "true") {
|
||||
if ($searchable) {
|
||||
require_once('include/socgraph.php');
|
||||
poco_check($contact['url'], $name, NETWORK_DIASPORA, $images[0], $about, $location, $gender, $keywords, "",
|
||||
poco_check($contact['url'], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "",
|
||||
datetime_convert(), 2, $contact['id'], $importer['uid']);
|
||||
}
|
||||
|
||||
$profileurl = "";
|
||||
$author = q("SELECT * FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
|
||||
dbesc(normalise_link($contact['url'])));
|
||||
|
||||
if (count($author) == 0) {
|
||||
q("INSERT INTO `unique_contacts` (`url`, `name`, `avatar`, `location`, `about`) VALUES ('%s', '%s', '%s', '%s', '%s')",
|
||||
dbesc(normalise_link($contact['url'])), dbesc($name), dbesc($location), dbesc($about), dbesc($images[0]));
|
||||
|
||||
$author = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1",
|
||||
dbesc(normalise_link($contact['url'])));
|
||||
} else if (normalise_link($contact['url']).$name.$location.$about != normalise_link($author[0]["url"]).$author[0]["name"].$author[0]["location"].$author[0]["about"]) {
|
||||
q("UPDATE unique_contacts SET name = '%s', avatar = '%s', `location` = '%s', `about` = '%s' WHERE url = '%s'",
|
||||
dbesc($name), dbesc($images[0]), dbesc($location), dbesc($about), dbesc(normalise_link($contact['url'])));
|
||||
}
|
||||
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) {
|
||||
|
|
@ -2643,11 +2608,12 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
|
|||
}
|
||||
|
||||
logger('diaspora_send_status: '.$owner['username'].' -> '.$contact['name'].' base message: '.$msg, LOGGER_DATA);
|
||||
logger('send guid '.$item['guid'], LOGGER_DEBUG);
|
||||
|
||||
$slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
|
||||
//$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
|
||||
|
||||
$return_code = diaspora_transmit($owner,$contact,$slap,$public_batch);
|
||||
$return_code = diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']);
|
||||
|
||||
logger('diaspora_send_status: guid: '.$item['guid'].' result '.$return_code, LOGGER_DEBUG);
|
||||
|
||||
|
|
@ -2758,10 +2724,12 @@ function diaspora_send_images($item,$owner,$contact,$images,$public_batch = fals
|
|||
|
||||
|
||||
logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA);
|
||||
logger('send guid '.$r[0]['guid'], LOGGER_DEBUG);
|
||||
|
||||
$slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
|
||||
//$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
|
||||
|
||||
diaspora_transmit($owner,$contact,$slap,$public_batch);
|
||||
diaspora_transmit($owner,$contact,$slap,$public_batch,false,$r[0]['guid']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2815,7 +2783,7 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
|
|||
// sign it
|
||||
|
||||
if($like)
|
||||
$signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $myaddr;
|
||||
$signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr;
|
||||
else
|
||||
$signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr;
|
||||
|
||||
|
|
@ -2832,11 +2800,12 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
|
|||
));
|
||||
|
||||
logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA);
|
||||
logger('send guid '.$item['guid'], LOGGER_DEBUG);
|
||||
|
||||
$slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
|
||||
//$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
|
||||
|
||||
return(diaspora_transmit($owner,$contact,$slap,$public_batch));
|
||||
return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2847,9 +2816,6 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
|
|||
$myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
|
||||
// $theiraddr = $contact['addr'];
|
||||
|
||||
$body = $item['body'];
|
||||
$text = html_entity_decode(bb2diaspora($body));
|
||||
|
||||
// Diaspora doesn't support threaded comments, but some
|
||||
// versions of Diaspora (i.e. Diaspora-pistos) support
|
||||
// likes on comments
|
||||
|
|
@ -2900,61 +2866,53 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
|
|||
// fetch the original signature if the relayable was created by a Diaspora
|
||||
// or DFRN user. Relayables for other networks are not supported.
|
||||
|
||||
/* $r = q("select * from sign where " . $sql_sign_id . " = %d limit 1",
|
||||
$r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1",
|
||||
intval($item['id'])
|
||||
);
|
||||
if(count($r)) {
|
||||
if(count($r)) {
|
||||
$orig_sign = $r[0];
|
||||
$signed_text = $orig_sign['signed_text'];
|
||||
$authorsig = $orig_sign['signature'];
|
||||
$handle = $orig_sign['signer'];
|
||||
|
||||
// Split the signed text
|
||||
$signed_parts = explode(";", $signed_text);
|
||||
|
||||
// Remove the parent guid
|
||||
array_shift($signed_parts);
|
||||
|
||||
// Remove the comment guid
|
||||
array_shift($signed_parts);
|
||||
|
||||
// Remove the handle
|
||||
array_pop($signed_parts);
|
||||
|
||||
// Glue the parts together
|
||||
$text = implode(";", $signed_parts);
|
||||
}
|
||||
else {
|
||||
// This part is meant for cases where we don't have the signatur. (Which shouldn't happen with posts from Diaspora and Friendica)
|
||||
// This means that the comment won't be accepted by newer Diaspora servers
|
||||
|
||||
// Author signature information (for likes, comments, and retractions of likes or comments,
|
||||
// whether from Diaspora or Friendica) must be placed in the `sign` table before this
|
||||
// function is called
|
||||
logger('diaspora_send_relay: original author signature not found, cannot send relayable');
|
||||
return;
|
||||
}*/
|
||||
$body = $item['body'];
|
||||
$text = html_entity_decode(bb2diaspora($body));
|
||||
|
||||
/* Since the author signature is only checked by the parent, not by the relay recipients,
|
||||
* I think it may not be necessary for us to do so much work to preserve all the original
|
||||
* signatures. The important thing that Diaspora DOES need is the original creator's handle.
|
||||
* Let's just generate that and forget about all the original author signature stuff.
|
||||
*
|
||||
* Note: this might be more of an problem if we want to support likes on comments for older
|
||||
* versions of Diaspora (diaspora-pistos), but since there are a number of problems with
|
||||
* doing that, let's ignore it for now.
|
||||
*
|
||||
* Currently, only DFRN contacts are supported. StatusNet shouldn't be hard, but it hasn't
|
||||
* been done yet
|
||||
*/
|
||||
$handle = diaspora_handle_from_contact($item['contact-id']);
|
||||
if(! $handle)
|
||||
return;
|
||||
|
||||
$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;
|
||||
|
||||
|
||||
if($relay_retract)
|
||||
$sender_signed_text = $item['guid'] . ';' . $target_type;
|
||||
elseif($like)
|
||||
$sender_signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle;
|
||||
else
|
||||
$sender_signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle;
|
||||
$authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
|
||||
}
|
||||
|
||||
// Sign the relayable with the top-level owner's signature
|
||||
//
|
||||
// We'll use the $sender_signed_text that we just created, instead of the $signed_text
|
||||
// stored in the database, because that provides the best chance that Diaspora will
|
||||
// be able to reconstruct the signed text the same way we did. This is particularly a
|
||||
// concern for the comment, whose signed text includes the text of the comment. The
|
||||
// smallest change in the text of the comment, including removing whitespace, will
|
||||
// make the signature verification fail. Since we translate from BB code to Diaspora's
|
||||
// markup at the top of this function, which is AFTER we placed the original $signed_text
|
||||
// in the database, it's hazardous to trust the original $signed_text.
|
||||
|
||||
$parentauthorsig = base64_encode(rsa_sign($sender_signed_text,$owner['uprvkey'],'sha256'));
|
||||
$parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
|
||||
|
||||
$msg = replace_macros($tpl,array(
|
||||
'$guid' => xmlify($item['guid']),
|
||||
|
|
@ -2968,12 +2926,12 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
|
|||
));
|
||||
|
||||
logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA);
|
||||
|
||||
logger('send guid '.$item['guid'], LOGGER_DEBUG);
|
||||
|
||||
$slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
|
||||
//$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
|
||||
|
||||
return(diaspora_transmit($owner,$contact,$slap,$public_batch));
|
||||
return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']));
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -3005,10 +2963,12 @@ function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) {
|
|||
'$signature' => xmlify(base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')))
|
||||
));
|
||||
|
||||
logger('send guid '.$item['guid'], LOGGER_DEBUG);
|
||||
|
||||
$slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
|
||||
//$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
|
||||
|
||||
return(diaspora_transmit($owner,$contact,$slap,$public_batch));
|
||||
return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']));
|
||||
}
|
||||
|
||||
function diaspora_send_mail($item,$owner,$contact) {
|
||||
|
|
@ -3065,16 +3025,17 @@ function diaspora_send_mail($item,$owner,$contact) {
|
|||
}
|
||||
|
||||
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));
|
||||
return(diaspora_transmit($owner,$contact,$slap,false,false,$item['guid']));
|
||||
|
||||
|
||||
}
|
||||
|
||||
function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) {
|
||||
function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false,$guid = "") {
|
||||
|
||||
$enabled = intval(get_config('system','diaspora_enabled'));
|
||||
if(! $enabled) {
|
||||
|
|
@ -3087,9 +3048,9 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false)
|
|||
if(! $dest_url) {
|
||||
logger('diaspora_transmit: no url for contact: ' . $contact['id'] . ' batch mode =' . $public_batch);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
logger('diaspora_transmit: ' . $logid . ' ' . $dest_url);
|
||||
logger('diaspora_transmit: '.$logid.'-'.$guid.' '.$dest_url);
|
||||
|
||||
if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) {
|
||||
$return_code = 0;
|
||||
|
|
@ -3104,7 +3065,7 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false)
|
|||
}
|
||||
}
|
||||
|
||||
logger('diaspora_transmit: ' . $logid . ' returns: ' . $return_code);
|
||||
logger('diaspora_transmit: '.$logid.'-'.$guid.' returns: '.$return_code);
|
||||
|
||||
if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) {
|
||||
logger('diaspora_transmit: queue message');
|
||||
|
|
|
|||
|
|
@ -76,11 +76,18 @@ function discover_poco_run(&$argv, &$argc){
|
|||
update_suggestions();
|
||||
elseif (($mode == 2) AND get_config('system','poco_completion'))
|
||||
discover_users();
|
||||
elseif (($mode == 1) AND ($search != "") and get_config('system','poco_local_search'))
|
||||
elseif (($mode == 1) AND ($search != "") and get_config('system','poco_local_search')) {
|
||||
discover_directory($search);
|
||||
elseif (($mode == 0) AND ($search == "") and (get_config('system','poco_discovery') > 0))
|
||||
gs_search_user($search);
|
||||
} elseif (($mode == 0) AND ($search == "") and (get_config('system','poco_discovery') > 0)) {
|
||||
// Query Friendica and Hubzilla servers for their users
|
||||
poco_discover();
|
||||
|
||||
// Query GNU Social servers for their users ("statistics" addon has to be enabled on the GS server)
|
||||
if (!get_config('system','ostatus_disabled'))
|
||||
gs_discover();
|
||||
}
|
||||
|
||||
logger('end '.$search);
|
||||
|
||||
return;
|
||||
|
|
@ -128,7 +135,7 @@ function discover_users() {
|
|||
else
|
||||
$server_url = poco_detect_server($user["url"]);
|
||||
|
||||
if (poco_check_server($server_url, $gcontacts[0]["network"])) {
|
||||
if (($server_url == "") OR poco_check_server($server_url, $gcontacts[0]["network"])) {
|
||||
logger('Check user '.$user["url"]);
|
||||
poco_last_updated($user["url"], true);
|
||||
|
||||
|
|
@ -191,6 +198,36 @@ function discover_directory($search) {
|
|||
Cache::set("dirsearch:".$search, time(), CACHE_DAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Search for GNU Social user with gstools.org
|
||||
*
|
||||
* @param str $search User name
|
||||
*/
|
||||
function gs_search_user($search) {
|
||||
|
||||
$a = get_app();
|
||||
|
||||
$url = "http://gstools.org/api/users_search/".urlencode($search);
|
||||
|
||||
$result = z_fetch_url($url);
|
||||
if (!$result["success"])
|
||||
return false;
|
||||
|
||||
$contacts = json_decode($result["body"]);
|
||||
|
||||
if ($contacts->status == 'ERROR')
|
||||
return false;
|
||||
|
||||
foreach($contacts->data AS $user) {
|
||||
$contact = probe_url($user->site_address."/".$user->name);
|
||||
if ($contact["network"] != NETWORK_PHANTOM) {
|
||||
$contact["about"] = $user->description;
|
||||
update_gcontact($contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (array_search(__file__,get_included_files())===0){
|
||||
discover_poco_run($_SERVER["argv"],$_SERVER["argc"]);
|
||||
killme();
|
||||
|
|
|
|||
|
|
@ -633,4 +633,129 @@ function notification($params) {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks for item related notifications and sends them
|
||||
*
|
||||
* @param int $itemid ID of the item for which the check should be done
|
||||
* @param int $uid User ID
|
||||
* @param str $defaulttype (Optional) Forces a notification with this type.
|
||||
*/
|
||||
function check_item_notification($itemid, $uid, $defaulttype = "") {
|
||||
|
||||
$notification_data = array("uid" => $uid, "profiles" => array());
|
||||
call_hooks('check_item_notification', $notification_data);
|
||||
|
||||
$profiles = $notification_data["profiles"];
|
||||
|
||||
$user = q("SELECT `notify-flags`, `language`, `username`, `email` FROM `user` WHERE `uid` = %d", intval($uid));
|
||||
if (!$user)
|
||||
return false;
|
||||
|
||||
$owner = q("SELECT `id`, `url` FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", intval($uid));
|
||||
if (!$owner)
|
||||
return false;
|
||||
|
||||
$profiles[] = $owner[0]["url"];
|
||||
|
||||
$profiles2 = array();
|
||||
|
||||
foreach ($profiles AS $profile) {
|
||||
$profiles2[] = normalise_link($profile);
|
||||
$profiles2[] = str_replace("http://", "https://", normalise_link($profile));
|
||||
}
|
||||
|
||||
$profiles = $profiles2;
|
||||
|
||||
$profile_list = "";
|
||||
|
||||
foreach ($profiles AS $profile) {
|
||||
if ($profile_list != "")
|
||||
$profile_list .= "', '";
|
||||
|
||||
$profile_list .= dbesc($profile);
|
||||
}
|
||||
|
||||
$profile_list = "'".$profile_list."'";
|
||||
|
||||
// Only act if it is a "real" post
|
||||
// We need the additional check for the "local_profile" because of mixed situations on connector networks
|
||||
$item = q("SELECT `id`, `mention`, `tag`,`parent`, `title`, `body`, `author-name`, `author-link`, `author-avatar`, `guid`,
|
||||
`parent-uri`, `uri`, `contact-id`
|
||||
FROM `item` WHERE `id` = %d AND `verb` IN ('%s', '') AND `type` != 'activity' AND
|
||||
NOT (`author-link` IN ($profile_list)) LIMIT 1",
|
||||
intval($itemid), dbesc(ACTIVITY_POST));
|
||||
if (!$item)
|
||||
return false;
|
||||
|
||||
// Generate the notification array
|
||||
$params = array();
|
||||
$params["uid"] = $uid;
|
||||
$params["notify_flags"] = $user[0]["notify-flags"];
|
||||
$params["language"] = $user[0]["language"];
|
||||
$params["to_name"] = $user[0]["username"];
|
||||
$params["to_email"] = $user[0]["email"];
|
||||
$params["item"] = $item[0];
|
||||
$params["parent"] = $item[0]["parent"];
|
||||
$params["link"] = App::get_baseurl().'/display/'.urlencode($item[0]["guid"]);
|
||||
$params["otype"] = 'item';
|
||||
$params["source_name"] = $item[0]["author-name"];
|
||||
$params["source_link"] = $item[0]["author-link"];
|
||||
$params["source_photo"] = $item[0]["author-avatar"];
|
||||
|
||||
if ($item[0]["parent-uri"] === $item[0]["uri"]) {
|
||||
// Send a notification for every new post?
|
||||
$r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
|
||||
intval($item[0]['contact-id']),
|
||||
intval($uid)
|
||||
);
|
||||
$send_notification = count($r);
|
||||
|
||||
if (!$send_notification) {
|
||||
$tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
|
||||
intval(TERM_OBJ_POST), intval($itemid), intval(TERM_MENTION), intval($uid));
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags AS $tag) {
|
||||
$r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
|
||||
normalise_link($tag["url"]), intval($uid));
|
||||
if (count($r))
|
||||
$send_notification = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($send_notification) {
|
||||
$params["type"] = NOTIFY_SHARE;
|
||||
$params["verb"] = ACTIVITY_TAG;
|
||||
}
|
||||
}
|
||||
|
||||
// Is the user mentioned in this post?
|
||||
$tagged = false;
|
||||
|
||||
foreach ($profiles AS $profile) {
|
||||
if (strpos($item[0]["tag"], "=".$profile."]") OR strpos($item[0]["body"], "=".$profile."]"))
|
||||
$tagged = true;
|
||||
}
|
||||
|
||||
if ($item[0]["mention"] OR $tagged OR ($defaulttype == NOTIFY_TAGSELF)) {
|
||||
$params["type"] = NOTIFY_TAGSELF;
|
||||
$params["verb"] = ACTIVITY_TAG;
|
||||
}
|
||||
|
||||
// Is it a post that the user had started or where he interacted?
|
||||
$parent = q("SELECT `thread`.`iid` FROM `thread` INNER JOIN `item` ON `item`.`parent` = `thread`.`iid`
|
||||
WHERE `thread`.`iid` = %d AND `thread`.`uid` = %d AND NOT `thread`.`ignored` AND
|
||||
(`thread`.`mention` OR `item`.`author-link` IN ($profile_list))
|
||||
LIMIT 1",
|
||||
intval($item[0]["parent"]), intval($uid));
|
||||
|
||||
if ($parent AND !isset($params["type"])) {
|
||||
$params["type"] = NOTIFY_COMMENT;
|
||||
$params["verb"] = ACTIVITY_POST;
|
||||
}
|
||||
|
||||
if (isset($params["type"]))
|
||||
notification($params);
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ function format_event_html($ev, $simple = false) {
|
|||
. bbcode($ev['location'])
|
||||
. '</span></p>' . "\r\n";
|
||||
|
||||
if (strpos($ev['location'], "[map")===False) {
|
||||
if (strpos($ev['location'], "[map") !== False) {
|
||||
$map = generate_named_map($ev['location']);
|
||||
if ($map!==$ev['location']) $o.=$map;
|
||||
}
|
||||
|
|
@ -76,7 +76,6 @@ function format_event_html($ev, $simple = false) {
|
|||
function parse_event($h) {
|
||||
|
||||
require_once('include/Scrape.php');
|
||||
require_once('library/HTMLPurifier.auto.php');
|
||||
require_once('include/html2bbcode');
|
||||
|
||||
$h = '<html><body>' . $h . '</body></html>';
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ function expire_run(&$argv, &$argc){
|
|||
|
||||
require_once('include/session.php');
|
||||
require_once('include/datetime.php');
|
||||
require_once('library/simplepie/simplepie.inc');
|
||||
require_once('include/items.php');
|
||||
require_once('include/Contact.php');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file include/features.php *
|
||||
* @file include/features.php
|
||||
* @brief Features management
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief check if feature is enabled
|
||||
*
|
||||
* return boolean
|
||||
* @return boolean
|
||||
*/
|
||||
function feature_enabled($uid,$feature) {
|
||||
//return true;
|
||||
|
||||
$x = get_pconfig($uid,'feature',$feature);
|
||||
$x = get_config('feature_lock',$feature);
|
||||
if($x === false) {
|
||||
$x = get_config('feature',$feature);
|
||||
if($x === false)
|
||||
$x = get_feature_default($feature);
|
||||
$x = get_pconfig($uid,'feature',$feature);
|
||||
if($x === false) {
|
||||
$x = get_config('feature',$feature);
|
||||
if($x === false)
|
||||
$x = get_feature_default($feature);
|
||||
}
|
||||
}
|
||||
$arr = array('uid' => $uid, 'feature' => $feature, 'enabled' => $x);
|
||||
call_hooks('feature_enabled',$arr);
|
||||
|
|
@ -42,14 +44,17 @@ function get_feature_default($feature) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @ brief get a list of all available features
|
||||
* @brief Get a list of all available features
|
||||
*
|
||||
* The array includes the setting group, the setting name,
|
||||
* explainations for the setting and if it's enabled or disabled
|
||||
* by default
|
||||
*
|
||||
* @param bool $filtered True removes any locked features
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function get_features() {
|
||||
function get_features($filtered = true) {
|
||||
|
||||
$arr = array(
|
||||
|
||||
|
|
@ -57,56 +62,78 @@ function get_features() {
|
|||
'general' => array(
|
||||
t('General Features'),
|
||||
//array('expire', t('Content Expiration'), t('Remove old posts/comments after a period of time')),
|
||||
array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles'),false),
|
||||
array('photo_location', t('Photo Location'), t('Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map.'),false),
|
||||
array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles'), false, get_config('feature_lock','multi_profiles')),
|
||||
array('photo_location', t('Photo Location'), t('Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map.'), false, get_config('feature_lock','photo_location')),
|
||||
),
|
||||
|
||||
// Post composition
|
||||
'composition' => array(
|
||||
t('Post Composition Features'),
|
||||
array('richtext', t('Richtext Editor'), t('Enable richtext editor'),false),
|
||||
array('preview', t('Post Preview'), t('Allow previewing posts and comments before publishing them'),false),
|
||||
array('aclautomention', t('Auto-mention Forums'), t('Add/remove mention when a fourm page is selected/deselected in ACL window.'),false),
|
||||
array('richtext', t('Richtext Editor'), t('Enable richtext editor'), false, get_config('feature_lock','richtext')),
|
||||
array('preview', t('Post Preview'), t('Allow previewing posts and comments before publishing them'), false, get_config('feature_lock','preview')),
|
||||
array('aclautomention', t('Auto-mention Forums'), t('Add/remove mention when a fourm page is selected/deselected in ACL window.'), false, get_config('feature_lock','aclautomention')),
|
||||
),
|
||||
|
||||
// Network sidebar widgets
|
||||
'widgets' => array(
|
||||
t('Network Sidebar Widgets'),
|
||||
array('archives', t('Search by Date'), t('Ability to select posts by date ranges'),false),
|
||||
array('forumlist_widget', t('List Forums'), t('Enable widget to display the forums your are connected with'),true),
|
||||
array('groups', t('Group Filter'), t('Enable widget to display Network posts only from selected group'),false),
|
||||
array('networks', t('Network Filter'), t('Enable widget to display Network posts only from selected network'),false),
|
||||
array('savedsearch', t('Saved Searches'), t('Save search terms for re-use'),false),
|
||||
array('archives', t('Search by Date'), t('Ability to select posts by date ranges'), false, get_config('feature_lock','archives')),
|
||||
array('forumlist_widget', t('List Forums'), t('Enable widget to display the forums your are connected with'), true, get_config('feature_lock','forumlist_widget')),
|
||||
array('groups', t('Group Filter'), t('Enable widget to display Network posts only from selected group'), false, get_config('feature_lock','groups')),
|
||||
array('networks', t('Network Filter'), t('Enable widget to display Network posts only from selected network'), false, get_config('feature_lock','networks')),
|
||||
array('savedsearch', t('Saved Searches'), t('Save search terms for re-use'), false, get_config('feature_lock','savedsearch')),
|
||||
),
|
||||
|
||||
// Network tabs
|
||||
'net_tabs' => array(
|
||||
t('Network Tabs'),
|
||||
array('personal_tab', t('Network Personal Tab'), t('Enable tab to display only Network posts that you\'ve interacted on'),false),
|
||||
array('new_tab', t('Network New Tab'), t('Enable tab to display only new Network posts (from the last 12 hours)'),false),
|
||||
array('link_tab', t('Network Shared Links Tab'), t('Enable tab to display only Network posts with links in them'),false),
|
||||
array('personal_tab', t('Network Personal Tab'), t('Enable tab to display only Network posts that you\'ve interacted on'), false, get_config('feature_lock','personal_tab')),
|
||||
array('new_tab', t('Network New Tab'), t('Enable tab to display only new Network posts (from the last 12 hours)'), false, get_config('feature_lock','new_tab')),
|
||||
array('link_tab', t('Network Shared Links Tab'), t('Enable tab to display only Network posts with links in them'), false, get_config('feature_lock','link_tab')),
|
||||
),
|
||||
|
||||
// Item tools
|
||||
'tools' => array(
|
||||
t('Post/Comment Tools'),
|
||||
array('multi_delete', t('Multiple Deletion'), t('Select and delete multiple posts/comments at once'),false),
|
||||
array('edit_posts', t('Edit Sent Posts'), t('Edit and correct posts and comments after sending'),false),
|
||||
array('commtag', t('Tagging'), t('Ability to tag existing posts'),false),
|
||||
array('categories', t('Post Categories'), t('Add categories to your posts'),false),
|
||||
array('filing', t('Saved Folders'), t('Ability to file posts under folders'),false),
|
||||
array('dislike', t('Dislike Posts'), t('Ability to dislike posts/comments')),
|
||||
array('star_posts', t('Star Posts'), t('Ability to mark special posts with a star indicator'),false),
|
||||
array('ignore_posts', t('Mute Post Notifications'), t('Ability to mute notifications for a thread'),false),
|
||||
array('multi_delete', t('Multiple Deletion'), t('Select and delete multiple posts/comments at once'), false, get_config('feature_lock','multi_delete')),
|
||||
array('edit_posts', t('Edit Sent Posts'), t('Edit and correct posts and comments after sending'), false, get_config('feature_lock','edit_posts')),
|
||||
array('commtag', t('Tagging'), t('Ability to tag existing posts'), false, get_config('feature_lock','commtag')),
|
||||
array('categories', t('Post Categories'), t('Add categories to your posts'), false, get_config('feature_lock','categories')),
|
||||
array('filing', t('Saved Folders'), t('Ability to file posts under folders'), false, get_config('feature_lock','filing')),
|
||||
array('dislike', t('Dislike Posts'), t('Ability to dislike posts/comments'), false, get_config('feature_lock','dislike')),
|
||||
array('star_posts', t('Star Posts'), t('Ability to mark special posts with a star indicator'), false, get_config('feature_lock','star_posts')),
|
||||
array('ignore_posts', t('Mute Post Notifications'), t('Ability to mute notifications for a thread'), false, get_config('feature_lock','ignore_posts')),
|
||||
),
|
||||
|
||||
// Advanced Profile Settings
|
||||
'advanced_profile' => array(
|
||||
t('Advanced Profile Settings'),
|
||||
array('forumlist_profile', t('List Forums'), t('Show visitors public community forums at the Advanced Profile Page'),false),
|
||||
array('forumlist_profile', t('List Forums'), t('Show visitors public community forums at the Advanced Profile Page'), false, get_config('feature_lock','forumlist_profile')),
|
||||
),
|
||||
);
|
||||
|
||||
// removed any locked features and remove the entire category if this makes it empty
|
||||
|
||||
if($filtered) {
|
||||
foreach($arr as $k => $x) {
|
||||
$has_items = false;
|
||||
$kquantity = count($arr[$k]);
|
||||
for($y = 0; $y < $kquantity; $y ++) {
|
||||
if(is_array($arr[$k][$y])) {
|
||||
if($arr[$k][$y][4] === false) {
|
||||
$has_items = true;
|
||||
}
|
||||
else {
|
||||
unset($arr[$k][$y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(! $has_items) {
|
||||
unset($arr[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
call_hooks('get_features',$arr);
|
||||
return $arr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,24 +264,8 @@ function new_contact($uid,$url,$interactive = false) {
|
|||
|
||||
require_once("include/Photo.php");
|
||||
|
||||
$photos = import_profile_photo($ret['photo'],$uid,$contact_id);
|
||||
|
||||
$r = q("UPDATE `contact` SET `photo` = '%s',
|
||||
`thumb` = '%s',
|
||||
`micro` = '%s',
|
||||
`name-date` = '%s',
|
||||
`uri-date` = '%s',
|
||||
`avatar-date` = '%s'
|
||||
WHERE `id` = %d",
|
||||
dbesc($photos[0]),
|
||||
dbesc($photos[1]),
|
||||
dbesc($photos[2]),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
intval($contact_id)
|
||||
);
|
||||
|
||||
// Update the avatar
|
||||
update_contact_avatar($ret['photo'],$uid,$contact_id);
|
||||
|
||||
// pull feed and consume it, which should subscribe to the hub.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,182 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file include/forums.php
|
||||
* @brief Functions related to forum functionality *
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Function to list all forums a user is connected with
|
||||
*
|
||||
* @param int $uid of the profile owner
|
||||
* @param boolean $showhidden
|
||||
* Show frorums which are not hidden
|
||||
* @param boolean $lastitem
|
||||
* Sort by lastitem
|
||||
* @param boolean $showprivate
|
||||
* Show private groups
|
||||
*
|
||||
* @returns array
|
||||
* 'url' => forum url
|
||||
* 'name' => forum name
|
||||
* 'id' => number of the key from the array
|
||||
* 'micro' => contact photo in format micro
|
||||
*/
|
||||
function get_forumlist($uid, $showhidden = true, $lastitem, $showprivate = false) {
|
||||
|
||||
$forumlist = array();
|
||||
|
||||
$order = (($showhidden) ? '' : ' AND NOT `hidden` ');
|
||||
$order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC ');
|
||||
$select = '`forum` ';
|
||||
if ($showprivate) {
|
||||
$select = '(`forum` OR `prv`)';
|
||||
}
|
||||
|
||||
$contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact`
|
||||
WHERE `network`= 'dfrn' AND $select AND `uid` = %d
|
||||
AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive`
|
||||
AND `success_update` > `failure_update`
|
||||
$order ",
|
||||
intval($uid)
|
||||
);
|
||||
|
||||
foreach($contacts as $contact) {
|
||||
$forumlist[] = array(
|
||||
'url' => $contact['url'],
|
||||
'name' => $contact['name'],
|
||||
'id' => $contact['id'],
|
||||
'micro' => $contact['micro'],
|
||||
);
|
||||
}
|
||||
return($forumlist);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Forumlist widget
|
||||
*
|
||||
* Sidebar widget to show subcribed friendica forums. If activated
|
||||
* in the settings, it appears at the notwork page sidebar
|
||||
*
|
||||
* @param int $uid
|
||||
* @param int $cid
|
||||
* The contact id which is used to mark a forum as "selected"
|
||||
* @return string
|
||||
*/
|
||||
function widget_forumlist($uid,$cid = 0) {
|
||||
|
||||
if(! intval(feature_enabled(local_user(),'forumlist_widget')))
|
||||
return;
|
||||
|
||||
$o = '';
|
||||
|
||||
//sort by last updated item
|
||||
$lastitem = true;
|
||||
|
||||
$contacts = get_forumlist($uid,true,$lastitem, true);
|
||||
$total = count($contacts);
|
||||
$visible_forums = 10;
|
||||
|
||||
if(count($contacts)) {
|
||||
|
||||
$id = 0;
|
||||
|
||||
foreach($contacts as $contact) {
|
||||
|
||||
$selected = (($cid == $contact['id']) ? ' forum-selected' : '');
|
||||
|
||||
$entry = array(
|
||||
'url' => z_root() . '/network?f=&cid=' . $contact['id'],
|
||||
'external_url' => z_root() . '/redir/' . $contact['id'],
|
||||
'name' => $contact['name'],
|
||||
'cid' => $contact['id'],
|
||||
'selected' => $selected,
|
||||
'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO),
|
||||
'id' => ++$id,
|
||||
);
|
||||
$entries[] = $entry;
|
||||
}
|
||||
|
||||
$tpl = get_markup_template('widget_forumlist.tpl');
|
||||
|
||||
$o .= replace_macros($tpl,array(
|
||||
'$title' => t('Forums'),
|
||||
'$forums' => $entries,
|
||||
'$link_desc' => t('External link to forum'),
|
||||
'$total' => $total,
|
||||
'$visible_forums' => $visible_forums,
|
||||
'$showmore' => t('show more'),
|
||||
));
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Format forumlist as contact block
|
||||
*
|
||||
* This function is used to show the forumlist in
|
||||
* the advanced profile.
|
||||
*
|
||||
* @param int $uid
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
function forumlist_profile_advanced($uid) {
|
||||
|
||||
$profile = intval(feature_enabled($uid,'forumlist_profile'));
|
||||
if(! $profile)
|
||||
return;
|
||||
|
||||
$o = '';
|
||||
|
||||
// place holder in case somebody wants configurability
|
||||
$show_total = 9999;
|
||||
|
||||
//don't sort by last updated item
|
||||
$lastitem = false;
|
||||
|
||||
$contacts = get_forumlist($uid,false,$lastitem,false);
|
||||
|
||||
$total_shown = 0;
|
||||
|
||||
foreach($contacts as $contact) {
|
||||
$forumlist .= micropro($contact,false,'forumlist-profile-advanced');
|
||||
$total_shown ++;
|
||||
if($total_shown == $show_total)
|
||||
break;
|
||||
}
|
||||
|
||||
if(count($contacts) > 0)
|
||||
$o .= $forumlist;
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief count unread forum items
|
||||
*
|
||||
* Count unread items of connected forums and private groups
|
||||
*
|
||||
* @return array
|
||||
* 'id' => contact id
|
||||
* 'name' => contact/forum name
|
||||
* 'count' => counted unseen forum items
|
||||
*
|
||||
*/
|
||||
|
||||
function forums_count_unseen() {
|
||||
$r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item`
|
||||
INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id`
|
||||
WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen`
|
||||
AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`)
|
||||
AND NOT `contact`.`blocked` AND NOT `contact`.`hidden`
|
||||
AND NOT `contact`.`pending` AND NOT `contact`.`archive`
|
||||
AND `contact`.`success_update` > `failure_update`
|
||||
GROUP BY `contact`.`id` ",
|
||||
intval(local_user())
|
||||
);
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
|
@ -60,6 +60,8 @@ class FriendicaSmartyEngine implements ITemplateEngine {
|
|||
$template = $s;
|
||||
$s = new FriendicaSmarty();
|
||||
}
|
||||
|
||||
$r['$APP'] = get_app();
|
||||
|
||||
// "middleware": inject variables into templates
|
||||
$arr = array(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ function gprobe_run(&$argv, &$argc){
|
|||
|
||||
$url = hex2bin($argv[1]);
|
||||
|
||||
$r = q("select * from gcontact where nurl = '%s' limit 1",
|
||||
$r = q("SELECT `id`, `url`, `network` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 1",
|
||||
dbesc(normalise_link($url))
|
||||
);
|
||||
|
||||
|
|
@ -58,21 +58,16 @@ function gprobe_run(&$argv, &$argc){
|
|||
if (is_null($result))
|
||||
Cache::set("gprobe:".$urlparts["host"],serialize($arr));
|
||||
|
||||
if(count($arr) && x($arr,'network') && $arr['network'] === NETWORK_DFRN) {
|
||||
q("insert into `gcontact` (`name`,`url`,`nurl`,`photo`)
|
||||
values ( '%s', '%s', '%s', '%s') ",
|
||||
dbesc($arr['name']),
|
||||
dbesc($arr['url']),
|
||||
dbesc(normalise_link($arr['url'])),
|
||||
dbesc($arr['photo'])
|
||||
);
|
||||
}
|
||||
$r = q("select * from gcontact where nurl = '%s' limit 1",
|
||||
if (!in_array($result["network"], array(NETWORK_FEED, NETWORK_PHANTOM)))
|
||||
update_gcontact($arr);
|
||||
|
||||
$r = q("SELECT `id`, `url`, `network` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 1",
|
||||
dbesc(normalise_link($url))
|
||||
);
|
||||
}
|
||||
if(count($r))
|
||||
poco_load(0,0,$r[0]['id'], str_replace('/profile/','/poco/',$r[0]['url']));
|
||||
if ($r[0]["network"] == NETWORK_DFRN)
|
||||
poco_load(0,0,$r[0]['id'], str_replace('/profile/','/poco/',$r[0]['url']));
|
||||
|
||||
logger("gprobe end for ".normalise_link($url), LOGGER_DEBUG);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ function mini_group_select($uid,$gid = 0) {
|
|||
|
||||
/**
|
||||
* @brief Create group sidebar widget
|
||||
*
|
||||
*
|
||||
* @param string $every
|
||||
* @param string $each
|
||||
* @param string $editmode
|
||||
|
|
@ -234,7 +234,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
|
|||
return '';
|
||||
|
||||
$groups = array();
|
||||
|
||||
|
||||
$groups[] = array(
|
||||
'text' => t('Everybody'),
|
||||
'id' => 0,
|
||||
|
|
@ -255,7 +255,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
|
|||
if(count($r)) {
|
||||
foreach($r as $rr) {
|
||||
$selected = (($group_id == $rr['id']) ? ' group-selected' : '');
|
||||
|
||||
|
||||
if ($editmode == "full") {
|
||||
$groupedit = array(
|
||||
'href' => "group/".$rr['id'],
|
||||
|
|
@ -264,7 +264,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
|
|||
} else {
|
||||
$groupedit = null;
|
||||
}
|
||||
|
||||
|
||||
$groups[] = array(
|
||||
'id' => $rr['id'],
|
||||
'cid' => $cid,
|
||||
|
|
@ -297,17 +297,26 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
|
|||
return $o;
|
||||
}
|
||||
|
||||
function expand_groups($a,$check_dead = false) {
|
||||
function expand_groups($a,$check_dead = false, $use_gcontact = false) {
|
||||
if(! (is_array($a) && count($a)))
|
||||
return array();
|
||||
$groups = implode(',', $a);
|
||||
$groups = dbesc($groups);
|
||||
$r = q("SELECT `contact-id` FROM `group_member` WHERE `gid` IN ( $groups )");
|
||||
|
||||
if ($use_gcontact)
|
||||
$r = q("SELECT `gcontact`.`id` AS `contact-id` FROM `group_member`
|
||||
INNER JOIN `contact` ON `contact`.`id` = `group_member`.`contact-id`
|
||||
INNER JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl`
|
||||
WHERE `gid` IN ($groups)");
|
||||
else
|
||||
$r = q("SELECT `contact-id` FROM `group_member` WHERE `gid` IN ( $groups )");
|
||||
|
||||
|
||||
$ret = array();
|
||||
if(count($r))
|
||||
foreach($r as $rr)
|
||||
$ret[] = $rr['contact-id'];
|
||||
if($check_dead) {
|
||||
if($check_dead AND !$use_gcontact) {
|
||||
require_once('include/acl_selectors.php');
|
||||
$ret = prune_deadguys($ret);
|
||||
}
|
||||
|
|
@ -353,17 +362,16 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* @file include/identity.php
|
||||
*/
|
||||
|
||||
require_once('include/forums.php');
|
||||
require_once('include/ForumManager.php');
|
||||
require_once('include/bbcode.php');
|
||||
require_once("mod/proxy.php");
|
||||
|
||||
|
|
@ -300,6 +300,7 @@ function profile_sidebar($profile, $block = 0) {
|
|||
$account_type = "";
|
||||
|
||||
if((x($profile,'address') == 1)
|
||||
|| (x($profile,'location') == 1)
|
||||
|| (x($profile,'locality') == 1)
|
||||
|| (x($profile,'region') == 1)
|
||||
|| (x($profile,'postal-code') == 1)
|
||||
|
|
@ -368,6 +369,8 @@ function profile_sidebar($profile, $block = 0) {
|
|||
|
||||
if (isset($p["address"]))
|
||||
$p["address"] = bbcode($p["address"]);
|
||||
else
|
||||
$p["address"] = bbcode($p["location"]);
|
||||
|
||||
if (isset($p["photo"]))
|
||||
$p["photo"] = proxy_url($p["photo"], false, PROXY_SIZE_SMALL);
|
||||
|
|
@ -652,7 +655,7 @@ function advanced_profile(&$a) {
|
|||
|
||||
//show subcribed forum if it is enabled in the usersettings
|
||||
if (feature_enabled($uid,'forumlist_profile')) {
|
||||
$profile['forumlist'] = array( t('Forums:'), forumlist_profile_advanced($uid));
|
||||
$profile['forumlist'] = array( t('Forums:'), ForumManager::profile_advanced($uid));
|
||||
}
|
||||
|
||||
if ($a->profile['uid'] == local_user())
|
||||
|
|
|
|||
3278
include/items.php
3278
include/items.php
|
|
@ -14,314 +14,19 @@ require_once('include/socgraph.php');
|
|||
require_once('include/plaintext.php');
|
||||
require_once('include/ostatus.php');
|
||||
require_once('include/feed.php');
|
||||
require_once('include/Contact.php');
|
||||
require_once('mod/share.php');
|
||||
require_once('include/enotify.php');
|
||||
require_once('include/dfrn.php');
|
||||
|
||||
require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
|
||||
|
||||
|
||||
function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
|
||||
|
||||
|
||||
$sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
|
||||
$public_feed = (($dfrn_id) ? false : true);
|
||||
$starred = false; // not yet implemented, possible security issues
|
||||
$converse = false;
|
||||
|
||||
if($public_feed && $a->argc > 2) {
|
||||
for($x = 2; $x < $a->argc; $x++) {
|
||||
if($a->argv[$x] == 'converse')
|
||||
$converse = true;
|
||||
if($a->argv[$x] == 'starred')
|
||||
$starred = true;
|
||||
if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
|
||||
$category = $a->argv[$x+1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// default permissions - anonymous user
|
||||
|
||||
$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`
|
||||
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
|
||||
WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
|
||||
dbesc($owner_nick)
|
||||
);
|
||||
|
||||
if(! count($r))
|
||||
killme();
|
||||
|
||||
$owner = $r[0];
|
||||
$owner_id = $owner['user_uid'];
|
||||
$owner_nick = $owner['nickname'];
|
||||
|
||||
$birthday = feed_birthday($owner_id,$owner['timezone']);
|
||||
|
||||
$sql_post_table = "";
|
||||
$visibility = "";
|
||||
|
||||
if(! $public_feed) {
|
||||
|
||||
$sql_extra = '';
|
||||
switch($direction) {
|
||||
case (-1):
|
||||
$sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
|
||||
$my_id = $dfrn_id;
|
||||
break;
|
||||
case 0:
|
||||
$sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
|
||||
$my_id = '1:' . $dfrn_id;
|
||||
break;
|
||||
case 1:
|
||||
$sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
|
||||
$my_id = '0:' . $dfrn_id;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break; // NOTREACHED
|
||||
}
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
|
||||
intval($owner_id)
|
||||
);
|
||||
|
||||
if(! count($r))
|
||||
killme();
|
||||
|
||||
$contact = $r[0];
|
||||
require_once('include/security.php');
|
||||
$groups = init_groups_visitor($contact['id']);
|
||||
|
||||
if(count($groups)) {
|
||||
for($x = 0; $x < count($groups); $x ++)
|
||||
$groups[$x] = '<' . intval($groups[$x]) . '>' ;
|
||||
$gs = implode('|', $groups);
|
||||
}
|
||||
else
|
||||
$gs = '<<>>' ; // Impossible to match
|
||||
|
||||
$sql_extra = sprintf("
|
||||
AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
|
||||
AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
|
||||
AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
|
||||
AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
|
||||
",
|
||||
intval($contact['id']),
|
||||
intval($contact['id']),
|
||||
dbesc($gs),
|
||||
dbesc($gs)
|
||||
);
|
||||
}
|
||||
|
||||
if($public_feed)
|
||||
$sort = 'DESC';
|
||||
else
|
||||
$sort = 'ASC';
|
||||
|
||||
// Include answers to status.net posts in pubsub feeds
|
||||
if($forpubsub) {
|
||||
$sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
|
||||
LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
|
||||
$visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
|
||||
dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
|
||||
$date_field = "`received`";
|
||||
$sql_order = "`item`.`received` DESC";
|
||||
} else {
|
||||
$date_field = "`changed`";
|
||||
$sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
|
||||
}
|
||||
|
||||
if(! strlen($last_update))
|
||||
$last_update = 'now -30 days';
|
||||
|
||||
if(isset($category)) {
|
||||
$sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
|
||||
dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
|
||||
//$sql_extra .= file_tag_file_query('item',$category,'category');
|
||||
}
|
||||
|
||||
if($public_feed) {
|
||||
if(! $converse)
|
||||
$sql_extra .= " AND `contact`.`self` = 1 ";
|
||||
}
|
||||
|
||||
$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
|
||||
|
||||
// AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
|
||||
// dbesc($check_date),
|
||||
|
||||
$r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
|
||||
`contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
|
||||
`contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
|
||||
`contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
|
||||
`contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
|
||||
`sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
|
||||
FROM `item` $sql_post_table
|
||||
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
||||
AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
|
||||
LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
|
||||
WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
|
||||
AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
|
||||
$sql_extra
|
||||
ORDER BY $sql_order LIMIT 0, 300",
|
||||
intval($owner_id),
|
||||
dbesc($check_date),
|
||||
dbesc($sort)
|
||||
);
|
||||
|
||||
// Will check further below if this actually returned results.
|
||||
// We will provide an empty feed if that is the case.
|
||||
|
||||
$items = $r;
|
||||
|
||||
$feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
|
||||
|
||||
$atom = '';
|
||||
|
||||
$hubxml = feed_hublinks();
|
||||
|
||||
$salmon = feed_salmonlinks($owner_nick);
|
||||
|
||||
$alternatelink = $owner['url'];
|
||||
|
||||
if(isset($category))
|
||||
$alternatelink .= "/category/".$category;
|
||||
|
||||
$atom .= replace_macros($feed_template, array(
|
||||
'$version' => xmlify(FRIENDICA_VERSION),
|
||||
'$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
|
||||
'$feed_title' => xmlify($owner['name']),
|
||||
'$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
|
||||
'$hub' => $hubxml,
|
||||
'$salmon' => $salmon,
|
||||
'$alternatelink' => xmlify($alternatelink),
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$profile_page' => xmlify($owner['url']),
|
||||
'$photo' => xmlify($owner['photo']),
|
||||
'$thumb' => xmlify($owner['thumb']),
|
||||
'$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
|
||||
'$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
|
||||
'$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
|
||||
'$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
|
||||
'$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
|
||||
));
|
||||
|
||||
call_hooks('atom_feed', $atom);
|
||||
|
||||
if(! count($items)) {
|
||||
|
||||
call_hooks('atom_feed_end', $atom);
|
||||
|
||||
$atom .= '</feed>' . "\r\n";
|
||||
return $atom;
|
||||
}
|
||||
|
||||
foreach($items as $item) {
|
||||
|
||||
// prevent private email from leaking.
|
||||
if($item['network'] === NETWORK_MAIL)
|
||||
continue;
|
||||
|
||||
// public feeds get html, our own nodes use bbcode
|
||||
|
||||
if($public_feed) {
|
||||
$type = 'html';
|
||||
// catch any email that's in a public conversation and make sure it doesn't leak
|
||||
if($item['private'])
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
$type = 'text';
|
||||
}
|
||||
|
||||
$atom .= atom_entry($item,$type,null,$owner,true);
|
||||
}
|
||||
|
||||
call_hooks('atom_feed_end', $atom);
|
||||
|
||||
$atom .= '</feed>' . "\r\n";
|
||||
|
||||
return $atom;
|
||||
}
|
||||
|
||||
|
||||
function construct_verb($item) {
|
||||
if($item['verb'])
|
||||
return $item['verb'];
|
||||
return ACTIVITY_POST;
|
||||
}
|
||||
|
||||
function construct_activity_object($item) {
|
||||
|
||||
if($item['object']) {
|
||||
$o = '<as:object>' . "\r\n";
|
||||
$r = parse_xml_string($item['object'],false);
|
||||
|
||||
|
||||
if(! $r)
|
||||
return '';
|
||||
if($r->type)
|
||||
$o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
|
||||
if($r->id)
|
||||
$o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
|
||||
if($r->title)
|
||||
$o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
|
||||
if($r->link) {
|
||||
if(substr($r->link,0,1) === '<') {
|
||||
// patch up some facebook "like" activity objects that got stored incorrectly
|
||||
// for a couple of months prior to 9-Jun-2011 and generated bad XML.
|
||||
// we can probably remove this hack here and in the following function in a few months time.
|
||||
if(strstr($r->link,'&') && (! strstr($r->link,'&')))
|
||||
$r->link = str_replace('&','&', $r->link);
|
||||
$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
|
||||
$o .= $r->link;
|
||||
}
|
||||
else
|
||||
$o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
|
||||
}
|
||||
if($r->content)
|
||||
$o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
|
||||
$o .= '</as:object>' . "\r\n";
|
||||
return $o;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function construct_activity_target($item) {
|
||||
|
||||
if($item['target']) {
|
||||
$o = '<as:target>' . "\r\n";
|
||||
$r = parse_xml_string($item['target'],false);
|
||||
if(! $r)
|
||||
return '';
|
||||
if($r->type)
|
||||
$o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
|
||||
if($r->id)
|
||||
$o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
|
||||
if($r->title)
|
||||
$o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
|
||||
if($r->link) {
|
||||
if(substr($r->link,0,1) === '<') {
|
||||
if(strstr($r->link,'&') && (! strstr($r->link,'&')))
|
||||
$r->link = str_replace('&','&', $r->link);
|
||||
$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
|
||||
$o .= $r->link;
|
||||
}
|
||||
else
|
||||
$o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
|
||||
}
|
||||
if($r->content)
|
||||
$o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
|
||||
$o .= '</as:target>' . "\r\n";
|
||||
return $o;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/* limit_body_size()
|
||||
*
|
||||
* The purpose of this function is to apply system message length limits to
|
||||
|
|
@ -440,482 +145,6 @@ function title_is_body($title, $body) {
|
|||
return($title == $body);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function get_atom_elements($feed, $item, $contact = array()) {
|
||||
|
||||
require_once('library/HTMLPurifier.auto.php');
|
||||
require_once('include/html2bbcode.php');
|
||||
|
||||
$best_photo = array();
|
||||
|
||||
$res = array();
|
||||
|
||||
$author = $item->get_author();
|
||||
if($author) {
|
||||
$res['author-name'] = unxmlify($author->get_name());
|
||||
$res['author-link'] = unxmlify($author->get_link());
|
||||
}
|
||||
else {
|
||||
$res['author-name'] = unxmlify($feed->get_title());
|
||||
$res['author-link'] = unxmlify($feed->get_permalink());
|
||||
}
|
||||
$res['uri'] = unxmlify($item->get_id());
|
||||
$res['title'] = unxmlify($item->get_title());
|
||||
$res['body'] = unxmlify($item->get_content());
|
||||
$res['plink'] = unxmlify($item->get_link(0));
|
||||
|
||||
if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
|
||||
logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
|
||||
$res['title'] = "";
|
||||
$res['body'] = nl2br($res['body']);
|
||||
}
|
||||
|
||||
// removing the content of the title if its identically to the body
|
||||
// This helps with auto generated titles e.g. from tumblr
|
||||
if (title_is_body($res["title"], $res["body"]))
|
||||
$res['title'] = "";
|
||||
|
||||
if($res['plink'])
|
||||
$base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
|
||||
else
|
||||
$base_url = '';
|
||||
|
||||
// look for a photo. We should check media size and find the best one,
|
||||
// but for now let's just find any author photo
|
||||
// Additionally we look for an alternate author link. On OStatus this one is the one we want.
|
||||
|
||||
$authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
|
||||
if (is_array($authorlinks)) {
|
||||
foreach ($authorlinks as $link) {
|
||||
$linkdata = array_shift($link["attribs"]);
|
||||
|
||||
if ($linkdata["rel"] == "alternate")
|
||||
$res["author-link"] = $linkdata["href"];
|
||||
};
|
||||
}
|
||||
|
||||
$rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
|
||||
|
||||
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
|
||||
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
|
||||
foreach($base as $link) {
|
||||
if($link['attribs']['']['rel'] === 'alternate')
|
||||
$res['author-link'] = unxmlify($link['attribs']['']['href']);
|
||||
|
||||
if(!x($res, 'author-avatar') || !$res['author-avatar']) {
|
||||
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
|
||||
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
|
||||
|
||||
if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
|
||||
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
|
||||
if($base && count($base)) {
|
||||
foreach($base as $link) {
|
||||
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
|
||||
$res['author-link'] = unxmlify($link['attribs']['']['href']);
|
||||
if(!x($res, 'author-avatar') || !$res['author-avatar']) {
|
||||
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
|
||||
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No photo/profile-link on the item - look at the feed level
|
||||
|
||||
if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
|
||||
$rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
|
||||
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
|
||||
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
|
||||
foreach($base as $link) {
|
||||
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
|
||||
$res['author-link'] = unxmlify($link['attribs']['']['href']);
|
||||
if(! $res['author-avatar']) {
|
||||
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
|
||||
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
|
||||
|
||||
if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
|
||||
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
|
||||
|
||||
if($base && count($base)) {
|
||||
foreach($base as $link) {
|
||||
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
|
||||
$res['author-link'] = unxmlify($link['attribs']['']['href']);
|
||||
if(! (x($res,'author-avatar'))) {
|
||||
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
|
||||
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
|
||||
if($apps && $apps[0]['attribs']['']['source']) {
|
||||
$res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
|
||||
if($res['app'] === 'web')
|
||||
$res['app'] = 'OStatus';
|
||||
}
|
||||
|
||||
// base64 encoded json structure representing Diaspora signature
|
||||
|
||||
$dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
|
||||
if($dsig) {
|
||||
$res['dsprsig'] = unxmlify($dsig[0]['data']);
|
||||
}
|
||||
|
||||
$dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
|
||||
if($dguid)
|
||||
$res['guid'] = unxmlify($dguid[0]['data']);
|
||||
|
||||
$bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
|
||||
if($bm)
|
||||
$res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
|
||||
|
||||
|
||||
/**
|
||||
* If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
|
||||
*/
|
||||
|
||||
$have_real_body = false;
|
||||
|
||||
$rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
|
||||
if($rawenv) {
|
||||
$have_real_body = true;
|
||||
$res['body'] = $rawenv[0]['data'];
|
||||
$res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
|
||||
// make sure nobody is trying to sneak some html tags by us
|
||||
$res['body'] = notags(base64url_decode($res['body']));
|
||||
}
|
||||
|
||||
|
||||
$res['body'] = limit_body_size($res['body']);
|
||||
|
||||
// It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
|
||||
// the content type. Our own network only emits text normally, though it might have been converted to
|
||||
// html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
|
||||
// have to assume it is all html and needs to be purified.
|
||||
|
||||
// It doesn't matter all that much security wise - because before this content is used anywhere, we are
|
||||
// going to escape any tags we find regardless, but this lets us import a limited subset of html from
|
||||
// the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
|
||||
// html.
|
||||
|
||||
if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
|
||||
|
||||
$res['body'] = reltoabs($res['body'],$base_url);
|
||||
|
||||
$res['body'] = html2bb_video($res['body']);
|
||||
|
||||
$res['body'] = oembed_html2bbcode($res['body']);
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Cache.DefinitionImpl', null);
|
||||
|
||||
// we shouldn't need a whitelist, because the bbcode converter
|
||||
// will strip out any unsupported tags.
|
||||
|
||||
$purifier = new HTMLPurifier($config);
|
||||
$res['body'] = $purifier->purify($res['body']);
|
||||
|
||||
$res['body'] = @html2bbcode($res['body']);
|
||||
|
||||
|
||||
}
|
||||
elseif(! $have_real_body) {
|
||||
|
||||
// it's not one of our messages and it has no tags
|
||||
// so it's probably just text. We'll escape it just to be safe.
|
||||
|
||||
$res['body'] = escape_tags($res['body']);
|
||||
}
|
||||
|
||||
|
||||
// this tag is obsolete but we keep it for really old sites
|
||||
|
||||
$allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
|
||||
if($allow && $allow[0]['data'] == 1)
|
||||
$res['last-child'] = 1;
|
||||
else
|
||||
$res['last-child'] = 0;
|
||||
|
||||
$private = $item->get_item_tags(NAMESPACE_DFRN,'private');
|
||||
if($private && intval($private[0]['data']) > 0)
|
||||
$res['private'] = intval($private[0]['data']);
|
||||
else
|
||||
$res['private'] = 0;
|
||||
|
||||
$extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
|
||||
if($extid && $extid[0]['data'])
|
||||
$res['extid'] = $extid[0]['data'];
|
||||
|
||||
$rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
|
||||
if($rawlocation)
|
||||
$res['location'] = unxmlify($rawlocation[0]['data']);
|
||||
|
||||
|
||||
$rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
|
||||
if($rawcreated)
|
||||
$res['created'] = unxmlify($rawcreated[0]['data']);
|
||||
|
||||
|
||||
$rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
|
||||
if($rawedited)
|
||||
$res['edited'] = unxmlify($rawedited[0]['data']);
|
||||
|
||||
if((x($res,'edited')) && (! (x($res,'created'))))
|
||||
$res['created'] = $res['edited'];
|
||||
|
||||
if(! $res['created'])
|
||||
$res['created'] = $item->get_date('c');
|
||||
|
||||
if(! $res['edited'])
|
||||
$res['edited'] = $item->get_date('c');
|
||||
|
||||
|
||||
// Disallow time travelling posts
|
||||
|
||||
$d1 = strtotime($res['created']);
|
||||
$d2 = strtotime($res['edited']);
|
||||
$d3 = strtotime('now');
|
||||
|
||||
if($d1 > $d3)
|
||||
$res['created'] = datetime_convert();
|
||||
if($d2 > $d3)
|
||||
$res['edited'] = datetime_convert();
|
||||
|
||||
$rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
|
||||
if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
|
||||
$res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
|
||||
elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
|
||||
$res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
|
||||
if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
|
||||
$res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
|
||||
elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
|
||||
$res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
|
||||
|
||||
if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
|
||||
$base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
|
||||
|
||||
foreach($base as $link) {
|
||||
if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
|
||||
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
|
||||
$res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
|
||||
if($rawgeo)
|
||||
$res['coord'] = unxmlify($rawgeo[0]['data']);
|
||||
|
||||
if ($contact["network"] == NETWORK_FEED) {
|
||||
$res['verb'] = ACTIVITY_POST;
|
||||
$res['object-type'] = ACTIVITY_OBJ_NOTE;
|
||||
}
|
||||
|
||||
$rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
|
||||
|
||||
// select between supported verbs
|
||||
|
||||
if($rawverb) {
|
||||
$res['verb'] = unxmlify($rawverb[0]['data']);
|
||||
}
|
||||
|
||||
// translate OStatus unfollow to activity streams if it happened to get selected
|
||||
|
||||
if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
|
||||
$res['verb'] = ACTIVITY_UNFOLLOW;
|
||||
|
||||
$cats = $item->get_categories();
|
||||
if($cats) {
|
||||
$tag_arr = array();
|
||||
foreach($cats as $cat) {
|
||||
$term = $cat->get_term();
|
||||
if(! $term)
|
||||
$term = $cat->get_label();
|
||||
$scheme = $cat->get_scheme();
|
||||
if($scheme && $term && stristr($scheme,'X-DFRN:'))
|
||||
$tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
|
||||
elseif($term)
|
||||
$tag_arr[] = notags(trim($term));
|
||||
}
|
||||
$res['tag'] = implode(',', $tag_arr);
|
||||
}
|
||||
|
||||
$attach = $item->get_enclosures();
|
||||
if($attach) {
|
||||
$att_arr = array();
|
||||
foreach($attach as $att) {
|
||||
$len = intval($att->get_length());
|
||||
$link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
|
||||
$title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
|
||||
$type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
|
||||
if(strpos($type,';'))
|
||||
$type = substr($type,0,strpos($type,';'));
|
||||
if((! $link) || (strpos($link,'http') !== 0))
|
||||
continue;
|
||||
|
||||
if(! $title)
|
||||
$title = ' ';
|
||||
if(! $type)
|
||||
$type = 'application/octet-stream';
|
||||
|
||||
$att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
|
||||
}
|
||||
$res['attach'] = implode(',', $att_arr);
|
||||
}
|
||||
|
||||
$rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
|
||||
|
||||
if($rawobj) {
|
||||
$res['object'] = '<object>' . "\n";
|
||||
$child = $rawobj[0]['child'];
|
||||
if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
|
||||
$res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
|
||||
$res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
|
||||
}
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
|
||||
$res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
|
||||
$res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
|
||||
$res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
|
||||
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
|
||||
if(! $body)
|
||||
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
|
||||
// preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
|
||||
$res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
|
||||
if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
|
||||
|
||||
$body = html2bb_video($body);
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Cache.DefinitionImpl', null);
|
||||
|
||||
$purifier = new HTMLPurifier($config);
|
||||
$body = $purifier->purify($body);
|
||||
$body = html2bbcode($body);
|
||||
}
|
||||
|
||||
$res['object'] .= '<content>' . $body . '</content>' . "\n";
|
||||
}
|
||||
|
||||
$res['object'] .= '</object>' . "\n";
|
||||
}
|
||||
|
||||
$rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
|
||||
|
||||
if($rawobj) {
|
||||
$res['target'] = '<target>' . "\n";
|
||||
$child = $rawobj[0]['child'];
|
||||
if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
|
||||
$res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
|
||||
}
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
|
||||
$res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
|
||||
$res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
|
||||
$res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
|
||||
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
|
||||
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
|
||||
if(! $body)
|
||||
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
|
||||
// preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
|
||||
$res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
|
||||
if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
|
||||
|
||||
$body = html2bb_video($body);
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Cache.DefinitionImpl', null);
|
||||
|
||||
$purifier = new HTMLPurifier($config);
|
||||
$body = $purifier->purify($body);
|
||||
$body = html2bbcode($body);
|
||||
}
|
||||
|
||||
$res['target'] .= '<content>' . $body . '</content>' . "\n";
|
||||
}
|
||||
|
||||
$res['target'] .= '</target>' . "\n";
|
||||
}
|
||||
|
||||
// This is some experimental stuff. By now retweets are shown with "RT:"
|
||||
// But: There is data so that the message could be shown similar to native retweets
|
||||
// There is some better way to parse this array - but it didn't worked for me.
|
||||
$child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
|
||||
if (is_array($child)) {
|
||||
logger('get_atom_elements: Looking for status.net repeated message');
|
||||
|
||||
$message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
|
||||
$orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
|
||||
$author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
|
||||
$uri = $author["uri"][0]["data"];
|
||||
$name = $author["name"][0]["data"];
|
||||
$avatar = @array_shift($author["link"][2]["attribs"]);
|
||||
$avatar = $avatar["href"];
|
||||
|
||||
if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
|
||||
logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
|
||||
|
||||
if (!intval(get_config('system','wall-to-wall_share'))) {
|
||||
$prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
|
||||
|
||||
$res["body"] = $prefix.html2bbcode($message)."[/share]";
|
||||
} else {
|
||||
$res["owner-name"] = $res["author-name"];
|
||||
$res["owner-link"] = $res["author-link"];
|
||||
$res["owner-avatar"] = $res["author-avatar"];
|
||||
|
||||
$res["author-name"] = $name;
|
||||
$res["author-link"] = $uri;
|
||||
$res["author-avatar"] = $avatar;
|
||||
|
||||
$res["body"] = html2bbcode($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
|
||||
$preview = "";
|
||||
|
||||
// Handle enclosures and treat them as preview picture
|
||||
if (isset($attach))
|
||||
foreach ($attach AS $attachment)
|
||||
if ($attachment->type == "image/jpeg")
|
||||
$preview = $attachment->link;
|
||||
|
||||
$res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
|
||||
$res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
|
||||
$res["title"] = "";
|
||||
$res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
|
||||
unset($res["attach"]);
|
||||
} elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
|
||||
$res["body"] = add_page_info_to_body($res["body"]);
|
||||
elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
|
||||
$res["body"] = add_page_info_to_body($res["body"]);
|
||||
}
|
||||
|
||||
$arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
|
||||
|
||||
call_hooks('parse_atom', $arr);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function add_page_info_data($data) {
|
||||
call_hooks('page_info_data', $data);
|
||||
|
||||
|
|
@ -955,8 +184,9 @@ function add_page_info_data($data) {
|
|||
$a = get_app();
|
||||
$hashtags = "\n";
|
||||
foreach ($data["keywords"] AS $keyword) {
|
||||
$hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
|
||||
array("","", "", "", "", ""), $keyword);
|
||||
/// @todo make a positive list of allowed characters
|
||||
$hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
|
||||
array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
|
||||
$hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
|
||||
}
|
||||
}
|
||||
|
|
@ -967,12 +197,7 @@ function add_page_info_data($data) {
|
|||
function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
|
||||
require_once("mod/parse_url.php");
|
||||
|
||||
$data = Cache::get("parse_url:".$url);
|
||||
if (is_null($data)){
|
||||
$data = parseurl_getsiteinfo($url, true);
|
||||
Cache::set("parse_url:".$url,serialize($data), CACHE_DAY);
|
||||
} else
|
||||
$data = unserialize($data);
|
||||
$data = parseurl_getsiteinfo_cached($url, true);
|
||||
|
||||
if ($photo != "")
|
||||
$data["images"][0]["src"] = $photo;
|
||||
|
|
@ -1066,37 +291,6 @@ function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
|
|||
return $body;
|
||||
}
|
||||
|
||||
function encode_rel_links($links) {
|
||||
$o = '';
|
||||
if(! ((is_array($links)) && (count($links))))
|
||||
return $o;
|
||||
foreach($links as $link) {
|
||||
$o .= '<link ';
|
||||
if($link['attribs']['']['rel'])
|
||||
$o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
|
||||
if($link['attribs']['']['type'])
|
||||
$o .= 'type="' . $link['attribs']['']['type'] . '" ';
|
||||
if($link['attribs']['']['href'])
|
||||
$o .= 'href="' . $link['attribs']['']['href'] . '" ';
|
||||
if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
|
||||
$o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
|
||||
if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
|
||||
$o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
|
||||
$o .= ' />' . "\n" ;
|
||||
}
|
||||
return xmlify($o);
|
||||
}
|
||||
|
||||
function add_guid($item) {
|
||||
$r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
|
||||
if ($r)
|
||||
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.
|
||||
|
|
@ -1142,6 +336,30 @@ function item_add_language_opt(&$arr) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates an unique guid out of a given uri
|
||||
*
|
||||
* @param string $uri uri of an item entry
|
||||
* @return string unique guid
|
||||
*/
|
||||
function uri_to_guid($uri) {
|
||||
|
||||
// Our regular guid routine is using this kind of prefix as well
|
||||
// We have to avoid that different routines could accidentally create the same value
|
||||
$parsed = parse_url($uri);
|
||||
$guid_prefix = hash("crc32", $parsed["host"]);
|
||||
|
||||
// Remove the scheme to make sure that "https" and "http" doesn't make a difference
|
||||
unset($parsed["scheme"]);
|
||||
|
||||
$host_id = implode("/", $parsed);
|
||||
|
||||
// We could use any hash algorithm since it isn't a security issue
|
||||
$host_hash = hash("ripemd128", $host_id);
|
||||
|
||||
return $guid_prefix.$host_hash;
|
||||
}
|
||||
|
||||
function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
|
||||
|
||||
// If it is a posting where users should get notifications, then define it as wall posting
|
||||
|
|
@ -1217,33 +435,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
|
|||
}
|
||||
}
|
||||
|
||||
// If there is no guid then take the same guid that was taken before for the same uri
|
||||
if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
|
||||
logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
|
||||
$r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
|
||||
dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
|
||||
|
||||
if(count($r)) {
|
||||
$arr['guid'] = $r[0]["guid"];
|
||||
logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no guid then take the same guid that was taken before for the same plink
|
||||
if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
|
||||
logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
|
||||
$r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
|
||||
dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
|
||||
|
||||
if(count($r)) {
|
||||
$arr['guid'] = $r[0]["guid"];
|
||||
logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
|
||||
|
||||
if ($r[0]["uri"] != $arr['uri'])
|
||||
logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
|
||||
// Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
|
||||
//if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
|
||||
|
|
@ -1253,6 +444,10 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
|
|||
|
||||
if ($notify)
|
||||
$guid_prefix = "";
|
||||
elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
|
||||
$arr['guid'] = uri_to_guid($arr['plink']);
|
||||
elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
|
||||
$arr['guid'] = uri_to_guid($arr['uri']);
|
||||
else {
|
||||
$parsed = parse_url($arr["author-link"]);
|
||||
$guid_prefix = hash("crc32", $parsed["host"]);
|
||||
|
|
@ -1304,6 +499,16 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
|
|||
$arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
|
||||
$arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
|
||||
|
||||
|
||||
if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) {
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
|
||||
foreach ($trace AS $func)
|
||||
$function[] = $func["function"];
|
||||
|
||||
$function = implode(", ", $function);
|
||||
logger("Both author-link and owner-link are empty. Called by: ".$function, LOGGER_DEBUG);
|
||||
}
|
||||
|
||||
if ($arr['plink'] == "") {
|
||||
$a = get_app();
|
||||
$arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
|
||||
|
|
@ -1338,6 +543,38 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
|
|||
logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
|
||||
}
|
||||
|
||||
// The contact-id should be set before "item_store" was called - but there seems to be some issues
|
||||
if ($arr["contact-id"] == 0) {
|
||||
// First we are looking for a suitable contact that matches with the author of the post
|
||||
// This is done only for comments (See below explanation at "gcontact-id")
|
||||
if($arr['parent-uri'] != $arr['uri'])
|
||||
$arr["contact-id"] = get_contact($arr['author-link'], $uid);
|
||||
|
||||
// If not present then maybe the owner was found
|
||||
if ($arr["contact-id"] == 0)
|
||||
$arr["contact-id"] = get_contact($arr['owner-link'], $uid);
|
||||
|
||||
// Still missing? Then use the "self" contact of the current user
|
||||
if ($arr["contact-id"] == 0) {
|
||||
$r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
|
||||
if ($r)
|
||||
$arr["contact-id"] = $r[0]["id"];
|
||||
}
|
||||
logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
|
||||
}
|
||||
|
||||
if ($arr["gcontact-id"] == 0) {
|
||||
// The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
|
||||
// This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
|
||||
// On comments the author is the better choice.
|
||||
if($arr['parent-uri'] === $arr['uri'])
|
||||
$arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
|
||||
"photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
|
||||
else
|
||||
$arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
|
||||
"photo" => $arr['author-avatar'], "name" => $arr['author-name']));
|
||||
}
|
||||
|
||||
if ($arr['guid'] != "") {
|
||||
// Checking if there is already an item with the same guid
|
||||
logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
|
||||
|
|
@ -1531,9 +768,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
|
|||
return 0;
|
||||
} elseif(count($r)) {
|
||||
|
||||
// Store the guid and other relevant data
|
||||
add_guid($arr);
|
||||
|
||||
$current_post = $r[0]['id'];
|
||||
logger('item_store: created item ' . $current_post);
|
||||
|
||||
|
|
@ -1609,6 +843,14 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
|
|||
);
|
||||
|
||||
if($dsprsig) {
|
||||
|
||||
// Friendica servers lower than 3.4.3-2 had double encoded the signature ...
|
||||
// We can check for this condition when we decode and encode the stuff again.
|
||||
if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
|
||||
$dsprsig->signature = base64_decode($dsprsig->signature);
|
||||
logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
|
||||
}
|
||||
|
||||
q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
|
||||
intval($current_post),
|
||||
dbesc($dsprsig->signed_text),
|
||||
|
|
@ -1653,67 +895,15 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
|
|||
create_files_from_item($current_post);
|
||||
|
||||
// Only check for notifications on start posts
|
||||
if ($arr['parent-uri'] === $arr['uri']) {
|
||||
if ($arr['parent-uri'] === $arr['uri'])
|
||||
add_thread($current_post);
|
||||
logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
|
||||
|
||||
// Send a notification for every new post?
|
||||
$r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
|
||||
intval($arr['contact-id']),
|
||||
intval($arr['uid'])
|
||||
);
|
||||
$send_notification = count($r);
|
||||
|
||||
if (!$send_notification) {
|
||||
$tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
|
||||
intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags AS $tag) {
|
||||
$r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
|
||||
normalise_link($tag["url"]), intval($arr['uid']));
|
||||
if (count($r))
|
||||
$send_notification = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($send_notification) {
|
||||
logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
|
||||
$u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
|
||||
intval($arr['uid']));
|
||||
|
||||
$item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
|
||||
intval($current_post),
|
||||
intval($arr['uid'])
|
||||
);
|
||||
|
||||
$a = get_app();
|
||||
|
||||
require_once('include/enotify.php');
|
||||
notification(array(
|
||||
'type' => NOTIFY_SHARE,
|
||||
'notify_flags' => $u[0]['notify-flags'],
|
||||
'language' => $u[0]['language'],
|
||||
'to_name' => $u[0]['username'],
|
||||
'to_email' => $u[0]['email'],
|
||||
'uid' => $u[0]['uid'],
|
||||
'item' => $item[0],
|
||||
'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
|
||||
'source_name' => $item[0]['author-name'],
|
||||
'source_link' => $item[0]['author-link'],
|
||||
'source_photo' => $item[0]['author-avatar'],
|
||||
'verb' => ACTIVITY_TAG,
|
||||
'otype' => 'item',
|
||||
'parent' => $arr['parent']
|
||||
));
|
||||
logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
|
||||
}
|
||||
} else {
|
||||
else {
|
||||
update_thread($parent_id);
|
||||
add_shadow_entry($arr);
|
||||
}
|
||||
|
||||
check_item_notification($current_post, $uid);
|
||||
|
||||
if ($notify)
|
||||
proc_run('php', "include/notifier.php", $notify_type, $current_post);
|
||||
|
||||
|
|
@ -1909,37 +1099,6 @@ function tag_deliver($uid,$item_id) {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
// send a notification
|
||||
|
||||
// use a local photo if we have one
|
||||
|
||||
$r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
|
||||
intval($u[0]['uid']),
|
||||
dbesc(normalise_link($item['author-link']))
|
||||
);
|
||||
$photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
|
||||
|
||||
|
||||
require_once('include/enotify.php');
|
||||
notification(array(
|
||||
'type' => NOTIFY_TAGSELF,
|
||||
'notify_flags' => $u[0]['notify-flags'],
|
||||
'language' => $u[0]['language'],
|
||||
'to_name' => $u[0]['username'],
|
||||
'to_email' => $u[0]['email'],
|
||||
'uid' => $u[0]['uid'],
|
||||
'item' => $item,
|
||||
'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
|
||||
'source_name' => $item['author-name'],
|
||||
'source_link' => $item['author-link'],
|
||||
'source_photo' => $photo,
|
||||
'verb' => ACTIVITY_TAG,
|
||||
'otype' => 'item',
|
||||
'parent' => $item['parent']
|
||||
));
|
||||
|
||||
|
||||
$arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
|
||||
|
||||
call_hooks('tagged', $arr);
|
||||
|
|
@ -2036,245 +1195,9 @@ function tgroup_check($uid,$item) {
|
|||
if((! $community_page) && (! $prvgroup))
|
||||
return false;
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
|
||||
|
||||
$a = get_app();
|
||||
|
||||
$idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
|
||||
|
||||
if($contact['duplex'] && $contact['dfrn-id'])
|
||||
$idtosend = '0:' . $orig_id;
|
||||
if($contact['duplex'] && $contact['issued-id'])
|
||||
$idtosend = '1:' . $orig_id;
|
||||
|
||||
|
||||
$rino = get_config('system','rino_encrypt');
|
||||
$rino = intval($rino);
|
||||
// use RINO1 if mcrypt isn't installed and RINO2 was selected
|
||||
if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
|
||||
|
||||
logger("Local rino version: ". $rino, LOGGER_DEBUG);
|
||||
|
||||
$ssl_val = intval(get_config('system','ssl_policy'));
|
||||
$ssl_policy = '';
|
||||
|
||||
switch($ssl_val){
|
||||
case SSL_POLICY_FULL:
|
||||
$ssl_policy = 'full';
|
||||
break;
|
||||
case SSL_POLICY_SELFSIGN:
|
||||
$ssl_policy = 'self';
|
||||
break;
|
||||
case SSL_POLICY_NONE:
|
||||
default:
|
||||
$ssl_policy = 'none';
|
||||
break;
|
||||
}
|
||||
|
||||
$url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
|
||||
|
||||
logger('dfrn_deliver: ' . $url);
|
||||
|
||||
$xml = fetch_url($url);
|
||||
|
||||
$curl_stat = $a->get_curl_code();
|
||||
if(! $curl_stat)
|
||||
return(-1); // timed out
|
||||
|
||||
logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
|
||||
|
||||
if(! $xml)
|
||||
return 3;
|
||||
|
||||
if(strpos($xml,'<?xml') === false) {
|
||||
logger('dfrn_deliver: no valid XML returned');
|
||||
logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
|
||||
return 3;
|
||||
}
|
||||
|
||||
$res = parse_xml_string($xml);
|
||||
|
||||
if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
|
||||
return (($res->status) ? $res->status : 3);
|
||||
|
||||
$postvars = array();
|
||||
$sent_dfrn_id = hex2bin((string) $res->dfrn_id);
|
||||
$challenge = hex2bin((string) $res->challenge);
|
||||
$perm = (($res->perm) ? $res->perm : null);
|
||||
$dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
|
||||
$rino_remote_version = intval($res->rino);
|
||||
$page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
|
||||
|
||||
logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
|
||||
|
||||
if($owner['page-flags'] == PAGE_PRVGROUP)
|
||||
$page = 2;
|
||||
|
||||
$final_dfrn_id = '';
|
||||
|
||||
if($perm) {
|
||||
if((($perm == 'rw') && (! intval($contact['writable'])))
|
||||
|| (($perm == 'r') && (intval($contact['writable'])))) {
|
||||
q("update contact set writable = %d where id = %d",
|
||||
intval(($perm == 'rw') ? 1 : 0),
|
||||
intval($contact['id'])
|
||||
);
|
||||
$contact['writable'] = (string) 1 - intval($contact['writable']);
|
||||
}
|
||||
}
|
||||
|
||||
if(($contact['duplex'] && strlen($contact['pubkey']))
|
||||
|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
|
||||
|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
|
||||
openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
|
||||
openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
|
||||
}
|
||||
else {
|
||||
openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
|
||||
openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
|
||||
}
|
||||
|
||||
$final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
|
||||
|
||||
if(strpos($final_dfrn_id,':') == 1)
|
||||
$final_dfrn_id = substr($final_dfrn_id,2);
|
||||
|
||||
if($final_dfrn_id != $orig_id) {
|
||||
logger('dfrn_deliver: wrong dfrn_id.');
|
||||
// did not decode properly - cannot trust this site
|
||||
return 3;
|
||||
}
|
||||
|
||||
$postvars['dfrn_id'] = $idtosend;
|
||||
$postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
|
||||
if($dissolve)
|
||||
$postvars['dissolve'] = '1';
|
||||
|
||||
|
||||
if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
|
||||
$postvars['data'] = $atom;
|
||||
$postvars['perm'] = 'rw';
|
||||
}
|
||||
else {
|
||||
$postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
|
||||
$postvars['perm'] = 'r';
|
||||
}
|
||||
|
||||
$postvars['ssl_policy'] = $ssl_policy;
|
||||
|
||||
if($page)
|
||||
$postvars['page'] = $page;
|
||||
|
||||
|
||||
if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
|
||||
logger('rino version: '. $rino_remote_version);
|
||||
|
||||
switch($rino_remote_version) {
|
||||
case 1:
|
||||
// Deprecated rino version!
|
||||
$key = substr(random_string(),0,16);
|
||||
$data = aes_encrypt($postvars['data'],$key);
|
||||
break;
|
||||
case 2:
|
||||
// RINO 2 based on php-encryption
|
||||
try {
|
||||
$key = Crypto::createNewRandomKey();
|
||||
} catch (CryptoTestFailed $ex) {
|
||||
logger('Cannot safely create a key');
|
||||
return -1;
|
||||
} catch (CannotPerformOperation $ex) {
|
||||
logger('Cannot safely create a key');
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
$data = Crypto::encrypt($postvars['data'], $key);
|
||||
} catch (CryptoTestFailed $ex) {
|
||||
logger('Cannot safely perform encryption');
|
||||
return -1;
|
||||
} catch (CannotPerformOperation $ex) {
|
||||
logger('Cannot safely perform encryption');
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger("rino: invalid requested verision '$rino_remote_version'");
|
||||
return -1;
|
||||
}
|
||||
|
||||
$postvars['rino'] = $rino_remote_version;
|
||||
$postvars['data'] = bin2hex($data);
|
||||
|
||||
#logger('rino: sent key = ' . $key, LOGGER_DEBUG);
|
||||
|
||||
|
||||
if($dfrn_version >= 2.1) {
|
||||
if(($contact['duplex'] && strlen($contact['pubkey']))
|
||||
|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
|
||||
|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
|
||||
|
||||
openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
|
||||
}
|
||||
else {
|
||||
openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
|
||||
openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
|
||||
}
|
||||
else {
|
||||
openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
|
||||
}
|
||||
}
|
||||
|
||||
logger('md5 rawkey ' . md5($postvars['key']));
|
||||
|
||||
$postvars['key'] = bin2hex($postvars['key']);
|
||||
}
|
||||
|
||||
|
||||
logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
|
||||
|
||||
$xml = post_url($contact['notify'],$postvars);
|
||||
|
||||
logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
|
||||
|
||||
$curl_stat = $a->get_curl_code();
|
||||
if((! $curl_stat) || (! strlen($xml)))
|
||||
return(-1); // timed out
|
||||
|
||||
if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
|
||||
return(-1);
|
||||
|
||||
if(strpos($xml,'<?xml') === false) {
|
||||
logger('dfrn_deliver: phase 2: no valid XML returned');
|
||||
logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
|
||||
return 3;
|
||||
}
|
||||
|
||||
if($contact['term-date'] != '0000-00-00 00:00:00') {
|
||||
logger("dfrn_deliver: $url back from the dead - removing mark for death");
|
||||
require_once('include/Contact.php');
|
||||
unmark_for_death($contact);
|
||||
}
|
||||
|
||||
$res = parse_xml_string($xml);
|
||||
|
||||
return $res->status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
This function returns true if $update has an edited timestamp newer
|
||||
than $existing, i.e. $update contains new data which should override
|
||||
|
|
@ -2342,639 +1265,25 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0)
|
|||
return;
|
||||
}
|
||||
|
||||
require_once('library/simplepie/simplepie.inc');
|
||||
require_once('include/contact_selectors.php');
|
||||
|
||||
if(! strlen($xml)) {
|
||||
logger('consume_feed: empty input');
|
||||
return;
|
||||
}
|
||||
|
||||
$feed = new SimplePie();
|
||||
$feed->set_raw_data($xml);
|
||||
if($datedir)
|
||||
$feed->enable_order_by_date(true);
|
||||
else
|
||||
$feed->enable_order_by_date(false);
|
||||
$feed->init();
|
||||
|
||||
if($feed->error())
|
||||
logger('consume_feed: Error parsing XML: ' . $feed->error());
|
||||
|
||||
$permalink = $feed->get_permalink();
|
||||
|
||||
// Check at the feed level for updated contact name and/or photo
|
||||
|
||||
$name_updated = '';
|
||||
$new_name = '';
|
||||
$photo_timestamp = '';
|
||||
$photo_url = '';
|
||||
$birthday = '';
|
||||
$contact_updated = '';
|
||||
|
||||
$hubs = $feed->get_links('hub');
|
||||
logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
|
||||
|
||||
if(count($hubs))
|
||||
$hub = implode(',', $hubs);
|
||||
|
||||
$rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
|
||||
if(! $rawtags)
|
||||
$rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
|
||||
if($rawtags) {
|
||||
$elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
|
||||
if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
|
||||
$name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
|
||||
$new_name = $elems['name'][0]['data'];
|
||||
|
||||
// Manually checking for changed contact names
|
||||
if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
|
||||
$name_updated = date("c");
|
||||
$photo_timestamp = date("c");
|
||||
}
|
||||
}
|
||||
if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
|
||||
if ($photo_timestamp == "")
|
||||
$photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
|
||||
$photo_url = $elems['link'][0]['attribs']['']['href'];
|
||||
}
|
||||
|
||||
if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
|
||||
$birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
|
||||
}
|
||||
}
|
||||
|
||||
if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
|
||||
logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
|
||||
|
||||
$contact_updated = $photo_timestamp;
|
||||
|
||||
require_once("include/Photo.php");
|
||||
$photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
|
||||
|
||||
q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
|
||||
WHERE `uid` = %d AND `id` = %d AND NOT `self`",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($photos[0]),
|
||||
dbesc($photos[1]),
|
||||
dbesc($photos[2]),
|
||||
intval($contact['uid']),
|
||||
intval($contact['id'])
|
||||
);
|
||||
}
|
||||
|
||||
if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
|
||||
if ($name_updated > $contact_updated)
|
||||
$contact_updated = $name_updated;
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
|
||||
intval($contact['uid']),
|
||||
intval($contact['id'])
|
||||
);
|
||||
|
||||
$x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
|
||||
dbesc(notags(trim($new_name))),
|
||||
dbesc(datetime_convert()),
|
||||
intval($contact['uid']),
|
||||
intval($contact['id']),
|
||||
dbesc(notags(trim($new_name)))
|
||||
);
|
||||
|
||||
// do our best to update the name on content items
|
||||
|
||||
if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
|
||||
q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
|
||||
dbesc(notags(trim($new_name))),
|
||||
dbesc($r[0]['name']),
|
||||
dbesc($r[0]['url']),
|
||||
intval($contact['uid']),
|
||||
dbesc(notags(trim($new_name)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($contact_updated AND $new_name AND $photo_url)
|
||||
poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
|
||||
|
||||
if(strlen($birthday)) {
|
||||
if(substr($birthday,0,4) != $contact['bdyear']) {
|
||||
logger('consume_feed: updating birthday: ' . $birthday);
|
||||
|
||||
/**
|
||||
*
|
||||
* Add new birthday event for this person
|
||||
*
|
||||
* $bdtext is just a readable placeholder in case the event is shared
|
||||
* with others. We will replace it during presentation to our $importer
|
||||
* to contain a sparkle link and perhaps a photo.
|
||||
*
|
||||
*/
|
||||
|
||||
$bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
|
||||
$bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
|
||||
|
||||
|
||||
$r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
|
||||
VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
|
||||
intval($contact['uid']),
|
||||
intval($contact['id']),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert('UTC','UTC', $birthday)),
|
||||
dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
|
||||
dbesc($bdtext),
|
||||
dbesc($bdtext2),
|
||||
dbesc('birthday')
|
||||
);
|
||||
|
||||
|
||||
// update bdyear
|
||||
|
||||
q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
|
||||
dbesc(substr($birthday,0,4)),
|
||||
intval($contact['uid']),
|
||||
intval($contact['id'])
|
||||
);
|
||||
|
||||
// This function is called twice without reloading the contact
|
||||
// Make sure we only create one event. This is why &$contact
|
||||
// is a reference var in this function
|
||||
|
||||
$contact['bdyear'] = substr($birthday,0,4);
|
||||
}
|
||||
}
|
||||
|
||||
$community_page = 0;
|
||||
$rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
|
||||
if($rawtags) {
|
||||
$community_page = intval($rawtags[0]['data']);
|
||||
}
|
||||
if(is_array($contact) && intval($contact['forum']) != $community_page) {
|
||||
q("update contact set forum = %d where id = %d",
|
||||
intval($community_page),
|
||||
intval($contact['id'])
|
||||
);
|
||||
$contact['forum'] = (string) $community_page;
|
||||
}
|
||||
|
||||
|
||||
// process any deleted entries
|
||||
|
||||
$del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
|
||||
if(is_array($del_entries) && count($del_entries) && $pass != 2) {
|
||||
foreach($del_entries as $dentry) {
|
||||
$deleted = false;
|
||||
if(isset($dentry['attribs']['']['ref'])) {
|
||||
$uri = $dentry['attribs']['']['ref'];
|
||||
$deleted = true;
|
||||
if(isset($dentry['attribs']['']['when'])) {
|
||||
$when = $dentry['attribs']['']['when'];
|
||||
$when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
|
||||
}
|
||||
else
|
||||
$when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
|
||||
}
|
||||
if($deleted && is_array($contact)) {
|
||||
$r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
|
||||
WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
|
||||
dbesc($uri),
|
||||
intval($importer['uid']),
|
||||
intval($contact['id'])
|
||||
);
|
||||
if(count($r)) {
|
||||
$item = $r[0];
|
||||
|
||||
if(! $item['deleted'])
|
||||
logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
|
||||
|
||||
if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
|
||||
logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
|
||||
event_delete($item['event-id']);
|
||||
}
|
||||
|
||||
if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
|
||||
$xo = parse_xml_string($item['object'],false);
|
||||
$xt = parse_xml_string($item['target'],false);
|
||||
if($xt->type === ACTIVITY_OBJ_NOTE) {
|
||||
$i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
|
||||
dbesc($xt->id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(count($i)) {
|
||||
|
||||
// For tags, the owner cannot remove the tag on the author's copy of the post.
|
||||
|
||||
$owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
|
||||
$author_remove = (($item['origin'] && $item['self']) ? true : false);
|
||||
$author_copy = (($item['origin']) ? true : false);
|
||||
|
||||
if($owner_remove && $author_copy)
|
||||
continue;
|
||||
if($author_remove || $owner_remove) {
|
||||
$tags = explode(',',$i[0]['tag']);
|
||||
$newtags = array();
|
||||
if(count($tags)) {
|
||||
foreach($tags as $tag)
|
||||
if(trim($tag) !== trim($xo->body))
|
||||
$newtags[] = trim($tag);
|
||||
}
|
||||
q("update item set tag = '%s' where id = %d",
|
||||
dbesc(implode(',',$newtags)),
|
||||
intval($i[0]['id'])
|
||||
);
|
||||
create_tags_from_item($i[0]['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($item['uri'] == $item['parent-uri']) {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `parent-uri` = '%s' AND `uid` = %d",
|
||||
dbesc($when),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item['uri']),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
create_tags_from_itemuri($item['uri'], $importer['uid']);
|
||||
create_files_from_itemuri($item['uri'], $importer['uid']);
|
||||
update_thread_uri($item['uri'], $importer['uid']);
|
||||
}
|
||||
else {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($when),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($uri),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
create_tags_from_itemuri($uri, $importer['uid']);
|
||||
create_files_from_itemuri($uri, $importer['uid']);
|
||||
if($item['last-child']) {
|
||||
// ensure that last-child is set in case the comment that had it just got wiped.
|
||||
q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item['parent-uri']),
|
||||
intval($item['uid'])
|
||||
);
|
||||
// who is the last child now?
|
||||
$r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
|
||||
ORDER BY `created` DESC LIMIT 1",
|
||||
dbesc($item['parent-uri']),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
if(count($r)) {
|
||||
q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
|
||||
intval($r[0]['id'])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now process the feed
|
||||
|
||||
if($feed->get_item_quantity()) {
|
||||
|
||||
logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
|
||||
|
||||
// in inverse date order
|
||||
if ($datedir)
|
||||
$items = array_reverse($feed->get_items());
|
||||
else
|
||||
$items = $feed->get_items();
|
||||
|
||||
|
||||
foreach($items as $item) {
|
||||
|
||||
$is_reply = false;
|
||||
$item_id = $item->get_id();
|
||||
$rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
|
||||
if(isset($rawthread[0]['attribs']['']['ref'])) {
|
||||
$is_reply = true;
|
||||
$parent_uri = $rawthread[0]['attribs']['']['ref'];
|
||||
}
|
||||
|
||||
if(($is_reply) && is_array($contact)) {
|
||||
|
||||
if($pass == 1)
|
||||
continue;
|
||||
|
||||
// not allowed to post
|
||||
|
||||
if($contact['rel'] == CONTACT_IS_FOLLOWER)
|
||||
continue;
|
||||
|
||||
|
||||
// Have we seen it? If not, import it.
|
||||
|
||||
$item_id = $item->get_id();
|
||||
$datarray = get_atom_elements($feed, $item, $contact);
|
||||
|
||||
if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
|
||||
$datarray['author-name'] = $contact['name'];
|
||||
if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
|
||||
$datarray['author-link'] = $contact['url'];
|
||||
if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
|
||||
$datarray['author-avatar'] = $contact['thumb'];
|
||||
|
||||
if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
|
||||
logger('consume_feed: no author information! ' . print_r($datarray,true));
|
||||
continue;
|
||||
}
|
||||
|
||||
$force_parent = false;
|
||||
if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
|
||||
if($contact['network'] === NETWORK_OSTATUS)
|
||||
$force_parent = true;
|
||||
if(strlen($datarray['title']))
|
||||
unset($datarray['title']);
|
||||
$r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($parent_uri),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
$datarray['last-child'] = 1;
|
||||
update_thread_uri($parent_uri, $importer['uid']);
|
||||
}
|
||||
|
||||
|
||||
$r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
|
||||
// Update content if 'updated' changes
|
||||
|
||||
if(count($r)) {
|
||||
if (edited_timestamp_is_newer($r[0], $datarray)) {
|
||||
|
||||
// do not accept (ignore) an earlier edit than one we currently have.
|
||||
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
|
||||
continue;
|
||||
|
||||
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($datarray['title']),
|
||||
dbesc($datarray['body']),
|
||||
dbesc($datarray['tag']),
|
||||
dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
create_tags_from_itemuri($item_id, $importer['uid']);
|
||||
update_thread_uri($item_id, $importer['uid']);
|
||||
}
|
||||
|
||||
// update last-child if it changes
|
||||
|
||||
$allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
|
||||
if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
|
||||
$r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($parent_uri),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
$r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
intval($allow[0]['data']),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
update_thread_uri($item_id, $importer['uid']);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
|
||||
// one way feed - no remote comment ability
|
||||
$datarray['last-child'] = 0;
|
||||
}
|
||||
$datarray['parent-uri'] = $parent_uri;
|
||||
$datarray['uid'] = $importer['uid'];
|
||||
$datarray['contact-id'] = $contact['id'];
|
||||
if(($datarray['verb'] === ACTIVITY_LIKE)
|
||||
|| ($datarray['verb'] === ACTIVITY_DISLIKE)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTEND)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTENDNO)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
|
||||
$datarray['type'] = 'activity';
|
||||
$datarray['gravity'] = GRAVITY_LIKE;
|
||||
// only one like or dislike per person
|
||||
// splitted into two queries for performance issues
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($datarray['uid']),
|
||||
dbesc($datarray['author-link']),
|
||||
dbesc($datarray['verb']),
|
||||
dbesc($datarray['parent-uri'])
|
||||
);
|
||||
if($r && count($r))
|
||||
continue;
|
||||
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($datarray['uid']),
|
||||
dbesc($datarray['author-link']),
|
||||
dbesc($datarray['verb']),
|
||||
dbesc($datarray['parent-uri'])
|
||||
);
|
||||
if($r && count($r))
|
||||
continue;
|
||||
}
|
||||
|
||||
if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
|
||||
$xo = parse_xml_string($datarray['object'],false);
|
||||
$xt = parse_xml_string($datarray['target'],false);
|
||||
|
||||
if($xt->type == ACTIVITY_OBJ_NOTE) {
|
||||
$r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
|
||||
dbesc($xt->id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(! count($r))
|
||||
continue;
|
||||
|
||||
// extract tag, if not duplicate, add to parent item
|
||||
if($xo->id && $xo->content) {
|
||||
$newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
|
||||
if(! (stristr($r[0]['tag'],$newtag))) {
|
||||
q("UPDATE item SET tag = '%s' WHERE id = %d",
|
||||
dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
|
||||
intval($r[0]['id'])
|
||||
);
|
||||
create_tags_from_item($r[0]['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$r = item_store($datarray,$force_parent);
|
||||
continue;
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
// Head post of a conversation. Have we seen it? If not, import it.
|
||||
|
||||
$item_id = $item->get_id();
|
||||
|
||||
$datarray = get_atom_elements($feed, $item, $contact);
|
||||
|
||||
if(is_array($contact)) {
|
||||
if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
|
||||
$datarray['author-name'] = $contact['name'];
|
||||
if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
|
||||
$datarray['author-link'] = $contact['url'];
|
||||
if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
|
||||
$datarray['author-avatar'] = $contact['thumb'];
|
||||
}
|
||||
|
||||
if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
|
||||
logger('consume_feed: no author information! ' . print_r($datarray,true));
|
||||
continue;
|
||||
}
|
||||
|
||||
// special handling for events
|
||||
|
||||
if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
|
||||
$ev = bbtoevent($datarray['body']);
|
||||
if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
|
||||
$ev['uid'] = $importer['uid'];
|
||||
$ev['uri'] = $item_id;
|
||||
$ev['edited'] = $datarray['edited'];
|
||||
$ev['private'] = $datarray['private'];
|
||||
$ev['guid'] = $datarray['guid'];
|
||||
|
||||
if(is_array($contact))
|
||||
$ev['cid'] = $contact['id'];
|
||||
$r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
if(count($r))
|
||||
$ev['id'] = $r[0]['id'];
|
||||
$xyz = event_store($ev);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
|
||||
if(strlen($datarray['title']))
|
||||
unset($datarray['title']);
|
||||
$datarray['last-child'] = 1;
|
||||
}
|
||||
|
||||
|
||||
$r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
|
||||
// Update content if 'updated' changes
|
||||
|
||||
if(count($r)) {
|
||||
if (edited_timestamp_is_newer($r[0], $datarray)) {
|
||||
|
||||
// do not accept (ignore) an earlier edit than one we currently have.
|
||||
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
|
||||
continue;
|
||||
|
||||
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($datarray['title']),
|
||||
dbesc($datarray['body']),
|
||||
dbesc($datarray['tag']),
|
||||
dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
create_tags_from_itemuri($item_id, $importer['uid']);
|
||||
update_thread_uri($item_id, $importer['uid']);
|
||||
}
|
||||
|
||||
// update last-child if it changes
|
||||
|
||||
$allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
|
||||
if($allow && $allow[0]['data'] != $r[0]['last-child']) {
|
||||
$r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
intval($allow[0]['data']),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
update_thread_uri($item_id, $importer['uid']);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
|
||||
logger('consume-feed: New follower');
|
||||
new_follower($importer,$contact,$datarray,$item);
|
||||
return;
|
||||
}
|
||||
if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
|
||||
lose_follower($importer,$contact,$datarray,$item);
|
||||
return;
|
||||
}
|
||||
|
||||
if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
|
||||
logger('consume-feed: New friend request');
|
||||
new_follower($importer,$contact,$datarray,$item,true);
|
||||
return;
|
||||
}
|
||||
if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
|
||||
lose_sharer($importer,$contact,$datarray,$item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(! is_array($contact))
|
||||
return;
|
||||
|
||||
|
||||
if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
|
||||
// one way feed - no remote comment ability
|
||||
$datarray['last-child'] = 0;
|
||||
}
|
||||
if($contact['network'] === NETWORK_FEED)
|
||||
$datarray['private'] = 2;
|
||||
|
||||
$datarray['parent-uri'] = $item_id;
|
||||
$datarray['uid'] = $importer['uid'];
|
||||
$datarray['contact-id'] = $contact['id'];
|
||||
|
||||
if(! link_compare($datarray['owner-link'],$contact['url'])) {
|
||||
// The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
|
||||
// but otherwise there's a possible data mixup on the sender's system.
|
||||
// the tgroup delivery code called from item_store will correct it if it's a forum,
|
||||
// but we're going to unconditionally correct it here so that the post will always be owned by our contact.
|
||||
logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
|
||||
$datarray['owner-name'] = $contact['name'];
|
||||
$datarray['owner-link'] = $contact['url'];
|
||||
$datarray['owner-avatar'] = $contact['thumb'];
|
||||
}
|
||||
|
||||
// We've allowed "followers" to reach this point so we can decide if they are
|
||||
// posting an @-tag delivery, which followers are allowed to do for certain
|
||||
// page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
|
||||
|
||||
if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
|
||||
continue;
|
||||
|
||||
// This is my contact on another system, but it's really me.
|
||||
// Turn this into a wall post.
|
||||
$notify = item_is_remote_self($contact, $datarray);
|
||||
|
||||
$r = item_store($datarray, false, $notify);
|
||||
logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
|
||||
continue;
|
||||
|
||||
}
|
||||
if ($contact['network'] === NETWORK_DFRN) {
|
||||
logger("Consume DFRN messages", LOGGER_DEBUG);
|
||||
|
||||
$r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
|
||||
`contact`.`pubkey` AS `cpubkey`,
|
||||
`contact`.`prvkey` AS `cprvkey`,
|
||||
`contact`.`thumb` AS `thumb`,
|
||||
`contact`.`url` as `url`,
|
||||
`contact`.`name` as `senderName`,
|
||||
`user`.*
|
||||
FROM `contact`
|
||||
LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
|
||||
WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
|
||||
dbesc($contact["id"]), dbesc($importer["uid"])
|
||||
);
|
||||
if ($r) {
|
||||
logger("Now import the DFRN feed");
|
||||
dfrn::import($xml,$r[0], true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3039,1144 +1348,6 @@ function item_is_remote_self($contact, &$datarray) {
|
|||
return true;
|
||||
}
|
||||
|
||||
function local_delivery($importer,$data) {
|
||||
$a = get_app();
|
||||
|
||||
logger(__function__, LOGGER_TRACE);
|
||||
|
||||
if($importer['readonly']) {
|
||||
// We aren't receiving stuff from this person. But we will quietly ignore them
|
||||
// rather than a blatant "go away" message.
|
||||
logger('local_delivery: ignoring');
|
||||
return 0;
|
||||
//NOTREACHED
|
||||
}
|
||||
|
||||
// Consume notification feed. This may differ from consuming a public feed in several ways
|
||||
// - might contain email or friend suggestions
|
||||
// - might contain remote followup to our message
|
||||
// - in which case we need to accept it and then notify other conversants
|
||||
// - we may need to send various email notifications
|
||||
|
||||
$feed = new SimplePie();
|
||||
$feed->set_raw_data($data);
|
||||
$feed->enable_order_by_date(false);
|
||||
$feed->init();
|
||||
|
||||
|
||||
if($feed->error())
|
||||
logger('local_delivery: Error parsing XML: ' . $feed->error());
|
||||
|
||||
|
||||
// Check at the feed level for updated contact name and/or photo
|
||||
|
||||
$name_updated = '';
|
||||
$new_name = '';
|
||||
$photo_timestamp = '';
|
||||
$photo_url = '';
|
||||
$contact_updated = '';
|
||||
|
||||
|
||||
$rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
|
||||
|
||||
// Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
|
||||
// if(! $rawtags)
|
||||
// $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
|
||||
|
||||
if($rawtags) {
|
||||
$elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
|
||||
if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
|
||||
$name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
|
||||
$new_name = $elems['name'][0]['data'];
|
||||
|
||||
// Manually checking for changed contact names
|
||||
if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
|
||||
$name_updated = date("c");
|
||||
$photo_timestamp = date("c");
|
||||
}
|
||||
}
|
||||
if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
|
||||
if ($photo_timestamp == "")
|
||||
$photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
|
||||
$photo_url = $elems['link'][0]['attribs']['']['href'];
|
||||
}
|
||||
}
|
||||
|
||||
if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
|
||||
|
||||
$contact_updated = $photo_timestamp;
|
||||
|
||||
logger('local_delivery: Updating photo for ' . $importer['name']);
|
||||
require_once("include/Photo.php");
|
||||
|
||||
$photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
|
||||
|
||||
q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
|
||||
WHERE `uid` = %d AND `id` = %d AND NOT `self`",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($photos[0]),
|
||||
dbesc($photos[1]),
|
||||
dbesc($photos[2]),
|
||||
intval($importer['importer_uid']),
|
||||
intval($importer['id'])
|
||||
);
|
||||
}
|
||||
|
||||
if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
|
||||
if ($name_updated > $contact_updated)
|
||||
$contact_updated = $name_updated;
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
|
||||
intval($importer['importer_uid']),
|
||||
intval($importer['id'])
|
||||
);
|
||||
|
||||
$x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
|
||||
dbesc(notags(trim($new_name))),
|
||||
dbesc(datetime_convert()),
|
||||
intval($importer['importer_uid']),
|
||||
intval($importer['id']),
|
||||
dbesc(notags(trim($new_name)))
|
||||
);
|
||||
|
||||
// do our best to update the name on content items
|
||||
|
||||
if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
|
||||
q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
|
||||
dbesc(notags(trim($new_name))),
|
||||
dbesc($r[0]['name']),
|
||||
dbesc($r[0]['url']),
|
||||
intval($importer['importer_uid']),
|
||||
dbesc(notags(trim($new_name)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($contact_updated AND $new_name AND $photo_url)
|
||||
poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
|
||||
|
||||
// Currently unsupported - needs a lot of work
|
||||
$reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
|
||||
if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
|
||||
$base = $reloc[0]['child'][NAMESPACE_DFRN];
|
||||
$newloc = array();
|
||||
$newloc['uid'] = $importer['importer_uid'];
|
||||
$newloc['cid'] = $importer['id'];
|
||||
$newloc['name'] = notags(unxmlify($base['name'][0]['data']));
|
||||
$newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
|
||||
$newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
|
||||
$newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
|
||||
$newloc['url'] = notags(unxmlify($base['url'][0]['data']));
|
||||
$newloc['request'] = notags(unxmlify($base['request'][0]['data']));
|
||||
$newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
|
||||
$newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
|
||||
$newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
|
||||
$newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
|
||||
/** relocated user must have original key pair */
|
||||
/*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
|
||||
$newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
|
||||
|
||||
logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
|
||||
|
||||
// update contact
|
||||
$r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
|
||||
intval($importer['id']),
|
||||
intval($importer['importer_uid']));
|
||||
if ($r === false)
|
||||
return 1;
|
||||
$old = $r[0];
|
||||
|
||||
$x = q("UPDATE contact SET
|
||||
name = '%s',
|
||||
photo = '%s',
|
||||
thumb = '%s',
|
||||
micro = '%s',
|
||||
url = '%s',
|
||||
nurl = '%s',
|
||||
request = '%s',
|
||||
confirm = '%s',
|
||||
notify = '%s',
|
||||
poll = '%s',
|
||||
`site-pubkey` = '%s'
|
||||
WHERE id=%d AND uid=%d;",
|
||||
dbesc($newloc['name']),
|
||||
dbesc($newloc['photo']),
|
||||
dbesc($newloc['thumb']),
|
||||
dbesc($newloc['micro']),
|
||||
dbesc($newloc['url']),
|
||||
dbesc(normalise_link($newloc['url'])),
|
||||
dbesc($newloc['request']),
|
||||
dbesc($newloc['confirm']),
|
||||
dbesc($newloc['notify']),
|
||||
dbesc($newloc['poll']),
|
||||
dbesc($newloc['sitepubkey']),
|
||||
intval($importer['id']),
|
||||
intval($importer['importer_uid']));
|
||||
|
||||
if ($x === false)
|
||||
return 1;
|
||||
// update items
|
||||
$fields = array(
|
||||
'owner-link' => array($old['url'], $newloc['url']),
|
||||
'author-link' => array($old['url'], $newloc['url']),
|
||||
'owner-avatar' => array($old['photo'], $newloc['photo']),
|
||||
'author-avatar' => array($old['photo'], $newloc['photo']),
|
||||
);
|
||||
foreach ($fields as $n=>$f){
|
||||
$x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
|
||||
$n, dbesc($f[1]),
|
||||
$n, dbesc($f[0]),
|
||||
intval($importer['importer_uid']));
|
||||
if ($x === false)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// @TODO
|
||||
/// merge with current record, current contents have priority
|
||||
/// update record, set url-updated
|
||||
/// update profile photos
|
||||
/// schedule a scan?
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// handle friend suggestion notification
|
||||
|
||||
$sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
|
||||
if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
|
||||
$base = $sugg[0]['child'][NAMESPACE_DFRN];
|
||||
$fsugg = array();
|
||||
$fsugg['uid'] = $importer['importer_uid'];
|
||||
$fsugg['cid'] = $importer['id'];
|
||||
$fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
|
||||
$fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
|
||||
$fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
|
||||
$fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
|
||||
$fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
|
||||
|
||||
// Does our member already have a friend matching this description?
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($fsugg['name']),
|
||||
dbesc(normalise_link($fsugg['url'])),
|
||||
intval($fsugg['uid'])
|
||||
);
|
||||
if(count($r))
|
||||
return 0;
|
||||
|
||||
// Do we already have an fcontact record for this person?
|
||||
|
||||
$fid = 0;
|
||||
$r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
|
||||
dbesc($fsugg['url']),
|
||||
dbesc($fsugg['name']),
|
||||
dbesc($fsugg['request'])
|
||||
);
|
||||
if(count($r)) {
|
||||
$fid = $r[0]['id'];
|
||||
|
||||
// OK, we do. Do we already have an introduction for this person ?
|
||||
$r = q("select id from intro where uid = %d and fid = %d limit 1",
|
||||
intval($fsugg['uid']),
|
||||
intval($fid)
|
||||
);
|
||||
if(count($r))
|
||||
return 0;
|
||||
}
|
||||
if(! $fid)
|
||||
$r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
|
||||
dbesc($fsugg['name']),
|
||||
dbesc($fsugg['url']),
|
||||
dbesc($fsugg['photo']),
|
||||
dbesc($fsugg['request'])
|
||||
);
|
||||
$r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
|
||||
dbesc($fsugg['url']),
|
||||
dbesc($fsugg['name']),
|
||||
dbesc($fsugg['request'])
|
||||
);
|
||||
if(count($r)) {
|
||||
$fid = $r[0]['id'];
|
||||
}
|
||||
// database record did not get created. Quietly give up.
|
||||
else
|
||||
return 0;
|
||||
|
||||
|
||||
$hash = random_string();
|
||||
|
||||
$r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
|
||||
VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
|
||||
intval($fsugg['uid']),
|
||||
intval($fid),
|
||||
intval($fsugg['cid']),
|
||||
dbesc($fsugg['body']),
|
||||
dbesc($hash),
|
||||
dbesc(datetime_convert()),
|
||||
intval(0)
|
||||
);
|
||||
|
||||
notification(array(
|
||||
'type' => NOTIFY_SUGGEST,
|
||||
'notify_flags' => $importer['notify-flags'],
|
||||
'language' => $importer['language'],
|
||||
'to_name' => $importer['username'],
|
||||
'to_email' => $importer['email'],
|
||||
'uid' => $importer['importer_uid'],
|
||||
'item' => $fsugg,
|
||||
'link' => $a->get_baseurl() . '/notifications/intros',
|
||||
'source_name' => $importer['name'],
|
||||
'source_link' => $importer['url'],
|
||||
'source_photo' => $importer['photo'],
|
||||
'verb' => ACTIVITY_REQ_FRIEND,
|
||||
'otype' => 'intro'
|
||||
));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$ismail = false;
|
||||
|
||||
$rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
|
||||
if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
|
||||
|
||||
logger('local_delivery: private message received');
|
||||
|
||||
$ismail = true;
|
||||
$base = $rawmail[0]['child'][NAMESPACE_DFRN];
|
||||
|
||||
$msg = array();
|
||||
$msg['uid'] = $importer['importer_uid'];
|
||||
$msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
|
||||
$msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
|
||||
$msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
|
||||
$msg['contact-id'] = $importer['id'];
|
||||
$msg['title'] = notags(unxmlify($base['subject'][0]['data']));
|
||||
$msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
|
||||
$msg['seen'] = 0;
|
||||
$msg['replied'] = 0;
|
||||
$msg['uri'] = notags(unxmlify($base['id'][0]['data']));
|
||||
$msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
|
||||
$msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
|
||||
|
||||
dbesc_array($msg);
|
||||
|
||||
$r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
|
||||
. "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
|
||||
|
||||
// send notifications.
|
||||
|
||||
require_once('include/enotify.php');
|
||||
|
||||
$notif_params = array(
|
||||
'type' => NOTIFY_MAIL,
|
||||
'notify_flags' => $importer['notify-flags'],
|
||||
'language' => $importer['language'],
|
||||
'to_name' => $importer['username'],
|
||||
'to_email' => $importer['email'],
|
||||
'uid' => $importer['importer_uid'],
|
||||
'item' => $msg,
|
||||
'source_name' => $msg['from-name'],
|
||||
'source_link' => $importer['url'],
|
||||
'source_photo' => $importer['thumb'],
|
||||
'verb' => ACTIVITY_POST,
|
||||
'otype' => 'mail'
|
||||
);
|
||||
|
||||
notification($notif_params);
|
||||
return 0;
|
||||
|
||||
// NOTREACHED
|
||||
}
|
||||
|
||||
$community_page = 0;
|
||||
$rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
|
||||
if($rawtags) {
|
||||
$community_page = intval($rawtags[0]['data']);
|
||||
}
|
||||
if(intval($importer['forum']) != $community_page) {
|
||||
q("update contact set forum = %d where id = %d",
|
||||
intval($community_page),
|
||||
intval($importer['id'])
|
||||
);
|
||||
$importer['forum'] = (string) $community_page;
|
||||
}
|
||||
|
||||
logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
|
||||
|
||||
// process any deleted entries
|
||||
|
||||
$del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
|
||||
if(is_array($del_entries) && count($del_entries)) {
|
||||
foreach($del_entries as $dentry) {
|
||||
$deleted = false;
|
||||
if(isset($dentry['attribs']['']['ref'])) {
|
||||
$uri = $dentry['attribs']['']['ref'];
|
||||
$deleted = true;
|
||||
if(isset($dentry['attribs']['']['when'])) {
|
||||
$when = $dentry['attribs']['']['when'];
|
||||
$when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
|
||||
}
|
||||
else
|
||||
$when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
|
||||
}
|
||||
if($deleted) {
|
||||
|
||||
// check for relayed deletes to our conversation
|
||||
|
||||
$is_reply = false;
|
||||
$r = q("select * from item where uri = '%s' and uid = %d limit 1",
|
||||
dbesc($uri),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(count($r)) {
|
||||
$parent_uri = $r[0]['parent-uri'];
|
||||
if($r[0]['id'] != $r[0]['parent'])
|
||||
$is_reply = true;
|
||||
}
|
||||
|
||||
if($is_reply) {
|
||||
$community = false;
|
||||
|
||||
if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
|
||||
$sql_extra = '';
|
||||
$community = true;
|
||||
logger('local_delivery: possible community delete');
|
||||
}
|
||||
else
|
||||
$sql_extra = " and contact.self = 1 and item.wall = 1 ";
|
||||
|
||||
// was the top-level post for this reply written by somebody on this site?
|
||||
// Specifically, the recipient?
|
||||
|
||||
$is_a_remote_delete = false;
|
||||
|
||||
// POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
|
||||
$r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
|
||||
`contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
|
||||
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
||||
WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
|
||||
AND `item`.`uid` = %d
|
||||
$sql_extra
|
||||
LIMIT 1",
|
||||
dbesc($parent_uri),
|
||||
dbesc($parent_uri),
|
||||
dbesc($parent_uri),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if($r && count($r))
|
||||
$is_a_remote_delete = true;
|
||||
|
||||
// Does this have the characteristics of a community or private group comment?
|
||||
// If it's a reply to a wall post on a community/prvgroup page it's a
|
||||
// valid community comment. Also forum_mode makes it valid for sure.
|
||||
// If neither, it's not.
|
||||
|
||||
if($is_a_remote_delete && $community) {
|
||||
if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
|
||||
$is_a_remote_delete = false;
|
||||
logger('local_delivery: not a community delete');
|
||||
}
|
||||
}
|
||||
|
||||
if($is_a_remote_delete) {
|
||||
logger('local_delivery: received remote delete');
|
||||
}
|
||||
}
|
||||
|
||||
$r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
|
||||
WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
|
||||
dbesc($uri),
|
||||
intval($importer['importer_uid']),
|
||||
intval($importer['id'])
|
||||
);
|
||||
|
||||
if(count($r)) {
|
||||
$item = $r[0];
|
||||
|
||||
if($item['deleted'])
|
||||
continue;
|
||||
|
||||
logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
|
||||
|
||||
if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
|
||||
logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
|
||||
event_delete($item['event-id']);
|
||||
}
|
||||
|
||||
if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
|
||||
$xo = parse_xml_string($item['object'],false);
|
||||
$xt = parse_xml_string($item['target'],false);
|
||||
|
||||
if($xt->type === ACTIVITY_OBJ_NOTE) {
|
||||
$i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
|
||||
dbesc($xt->id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(count($i)) {
|
||||
|
||||
// For tags, the owner cannot remove the tag on the author's copy of the post.
|
||||
|
||||
$owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
|
||||
$author_remove = (($item['origin'] && $item['self']) ? true : false);
|
||||
$author_copy = (($item['origin']) ? true : false);
|
||||
|
||||
if($owner_remove && $author_copy)
|
||||
continue;
|
||||
if($author_remove || $owner_remove) {
|
||||
$tags = explode(',',$i[0]['tag']);
|
||||
$newtags = array();
|
||||
if(count($tags)) {
|
||||
foreach($tags as $tag)
|
||||
if(trim($tag) !== trim($xo->body))
|
||||
$newtags[] = trim($tag);
|
||||
}
|
||||
q("update item set tag = '%s' where id = %d",
|
||||
dbesc(implode(',',$newtags)),
|
||||
intval($i[0]['id'])
|
||||
);
|
||||
create_tags_from_item($i[0]['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($item['uri'] == $item['parent-uri']) {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `parent-uri` = '%s' AND `uid` = %d",
|
||||
dbesc($when),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item['uri']),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
|
||||
create_files_from_itemuri($item['uri'], $importer['importer_uid']);
|
||||
update_thread_uri($item['uri'], $importer['importer_uid']);
|
||||
}
|
||||
else {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($when),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($uri),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
create_tags_from_itemuri($uri, $importer['importer_uid']);
|
||||
create_files_from_itemuri($uri, $importer['importer_uid']);
|
||||
update_thread_uri($uri, $importer['importer_uid']);
|
||||
if($item['last-child']) {
|
||||
// ensure that last-child is set in case the comment that had it just got wiped.
|
||||
q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item['parent-uri']),
|
||||
intval($item['uid'])
|
||||
);
|
||||
// who is the last child now?
|
||||
$r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
|
||||
ORDER BY `created` DESC LIMIT 1",
|
||||
dbesc($item['parent-uri']),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(count($r)) {
|
||||
q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
|
||||
intval($r[0]['id'])
|
||||
);
|
||||
}
|
||||
}
|
||||
// if this is a relayed delete, propagate it to other recipients
|
||||
|
||||
if($is_a_remote_delete)
|
||||
proc_run('php',"include/notifier.php","drop",$item['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach($feed->get_items() as $item) {
|
||||
|
||||
$is_reply = false;
|
||||
$item_id = $item->get_id();
|
||||
$rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
|
||||
if(isset($rawthread[0]['attribs']['']['ref'])) {
|
||||
$is_reply = true;
|
||||
$parent_uri = $rawthread[0]['attribs']['']['ref'];
|
||||
}
|
||||
|
||||
if($is_reply) {
|
||||
$community = false;
|
||||
|
||||
if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
|
||||
$sql_extra = '';
|
||||
$community = true;
|
||||
logger('local_delivery: possible community reply');
|
||||
}
|
||||
else
|
||||
$sql_extra = " and contact.self = 1 and item.wall = 1 ";
|
||||
|
||||
// was the top-level post for this reply written by somebody on this site?
|
||||
// Specifically, the recipient?
|
||||
|
||||
$is_a_remote_comment = false;
|
||||
$top_uri = $parent_uri;
|
||||
|
||||
$r = q("select `item`.`parent-uri` from `item`
|
||||
WHERE `item`.`uri` = '%s'
|
||||
LIMIT 1",
|
||||
dbesc($parent_uri)
|
||||
);
|
||||
if($r && count($r)) {
|
||||
$top_uri = $r[0]['parent-uri'];
|
||||
|
||||
// POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
|
||||
$r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
|
||||
`contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
|
||||
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
||||
WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
|
||||
AND `item`.`uid` = %d
|
||||
$sql_extra
|
||||
LIMIT 1",
|
||||
dbesc($top_uri),
|
||||
dbesc($top_uri),
|
||||
dbesc($top_uri),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if($r && count($r))
|
||||
$is_a_remote_comment = true;
|
||||
}
|
||||
|
||||
// Does this have the characteristics of a community or private group comment?
|
||||
// If it's a reply to a wall post on a community/prvgroup page it's a
|
||||
// valid community comment. Also forum_mode makes it valid for sure.
|
||||
// If neither, it's not.
|
||||
|
||||
if($is_a_remote_comment && $community) {
|
||||
if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
|
||||
$is_a_remote_comment = false;
|
||||
logger('local_delivery: not a community reply');
|
||||
}
|
||||
}
|
||||
|
||||
if($is_a_remote_comment) {
|
||||
logger('local_delivery: received remote comment');
|
||||
$is_like = false;
|
||||
// remote reply to our post. Import and then notify everybody else.
|
||||
|
||||
$datarray = get_atom_elements($feed, $item);
|
||||
|
||||
$r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
|
||||
// Update content if 'updated' changes
|
||||
|
||||
if(count($r)) {
|
||||
$iid = $r[0]['id'];
|
||||
if (edited_timestamp_is_newer($r[0], $datarray)) {
|
||||
|
||||
// do not accept (ignore) an earlier edit than one we currently have.
|
||||
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
|
||||
continue;
|
||||
|
||||
logger('received updated comment' , LOGGER_DEBUG);
|
||||
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($datarray['title']),
|
||||
dbesc($datarray['body']),
|
||||
dbesc($datarray['tag']),
|
||||
dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
create_tags_from_itemuri($item_id, $importer['importer_uid']);
|
||||
|
||||
proc_run('php',"include/notifier.php","comment-import",$iid);
|
||||
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
|
||||
|
||||
$datarray['type'] = 'remote-comment';
|
||||
$datarray['wall'] = 1;
|
||||
$datarray['parent-uri'] = $parent_uri;
|
||||
$datarray['uid'] = $importer['importer_uid'];
|
||||
$datarray['owner-name'] = $own[0]['name'];
|
||||
$datarray['owner-link'] = $own[0]['url'];
|
||||
$datarray['owner-avatar'] = $own[0]['thumb'];
|
||||
$datarray['contact-id'] = $importer['id'];
|
||||
|
||||
if(($datarray['verb'] === ACTIVITY_LIKE)
|
||||
|| ($datarray['verb'] === ACTIVITY_DISLIKE)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTEND)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTENDNO)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
|
||||
$is_like = true;
|
||||
$datarray['type'] = 'activity';
|
||||
$datarray['gravity'] = GRAVITY_LIKE;
|
||||
$datarray['last-child'] = 0;
|
||||
// only one like or dislike per person
|
||||
// splitted into two queries for performance issues
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($datarray['uid']),
|
||||
dbesc($datarray['author-link']),
|
||||
dbesc($datarray['verb']),
|
||||
dbesc($datarray['parent-uri'])
|
||||
);
|
||||
if($r && count($r))
|
||||
continue;
|
||||
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($datarray['uid']),
|
||||
dbesc($datarray['author-link']),
|
||||
dbesc($datarray['verb']),
|
||||
dbesc($datarray['parent-uri'])
|
||||
|
||||
);
|
||||
if($r && count($r))
|
||||
continue;
|
||||
}
|
||||
|
||||
if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
|
||||
|
||||
$xo = parse_xml_string($datarray['object'],false);
|
||||
$xt = parse_xml_string($datarray['target'],false);
|
||||
|
||||
if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
|
||||
|
||||
// fetch the parent item
|
||||
|
||||
$tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
|
||||
dbesc($xt->id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(! count($tagp))
|
||||
continue;
|
||||
|
||||
// extract tag, if not duplicate, and this user allows tags, add to parent item
|
||||
|
||||
if($xo->id && $xo->content) {
|
||||
$newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
|
||||
if(! (stristr($tagp[0]['tag'],$newtag))) {
|
||||
$i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(count($i) && ! intval($i[0]['blocktags'])) {
|
||||
q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
|
||||
dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
|
||||
intval($tagp[0]['id']),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert())
|
||||
);
|
||||
create_tags_from_item($tagp[0]['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$posted_id = item_store($datarray);
|
||||
$parent = 0;
|
||||
|
||||
if($posted_id) {
|
||||
|
||||
$datarray["id"] = $posted_id;
|
||||
|
||||
$r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
|
||||
intval($posted_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(count($r)) {
|
||||
$parent = $r[0]['parent'];
|
||||
$parent_uri = $r[0]['parent-uri'];
|
||||
}
|
||||
|
||||
if(! $is_like) {
|
||||
$r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
intval($importer['importer_uid']),
|
||||
intval($r[0]['parent'])
|
||||
);
|
||||
|
||||
$r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
intval($importer['importer_uid']),
|
||||
intval($posted_id)
|
||||
);
|
||||
}
|
||||
|
||||
if($posted_id && $parent) {
|
||||
|
||||
proc_run('php',"include/notifier.php","comment-import","$posted_id");
|
||||
|
||||
if((! $is_like) && (! $importer['self'])) {
|
||||
|
||||
require_once('include/enotify.php');
|
||||
|
||||
notification(array(
|
||||
'type' => NOTIFY_COMMENT,
|
||||
'notify_flags' => $importer['notify-flags'],
|
||||
'language' => $importer['language'],
|
||||
'to_name' => $importer['username'],
|
||||
'to_email' => $importer['email'],
|
||||
'uid' => $importer['importer_uid'],
|
||||
'item' => $datarray,
|
||||
'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
|
||||
'source_name' => stripslashes($datarray['author-name']),
|
||||
'source_link' => $datarray['author-link'],
|
||||
'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
|
||||
? $importer['thumb'] : $datarray['author-avatar']),
|
||||
'verb' => ACTIVITY_POST,
|
||||
'otype' => 'item',
|
||||
'parent' => $parent,
|
||||
'parent_uri' => $parent_uri,
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
// NOTREACHED
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
// regular comment that is part of this total conversation. Have we seen it? If not, import it.
|
||||
|
||||
$item_id = $item->get_id();
|
||||
$datarray = get_atom_elements($feed,$item);
|
||||
|
||||
if($importer['rel'] == CONTACT_IS_FOLLOWER)
|
||||
continue;
|
||||
|
||||
$r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
|
||||
// Update content if 'updated' changes
|
||||
|
||||
if(count($r)) {
|
||||
if (edited_timestamp_is_newer($r[0], $datarray)) {
|
||||
|
||||
// do not accept (ignore) an earlier edit than one we currently have.
|
||||
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
|
||||
continue;
|
||||
|
||||
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($datarray['title']),
|
||||
dbesc($datarray['body']),
|
||||
dbesc($datarray['tag']),
|
||||
dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
create_tags_from_itemuri($item_id, $importer['importer_uid']);
|
||||
}
|
||||
|
||||
// update last-child if it changes
|
||||
|
||||
$allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
|
||||
if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
|
||||
$r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($parent_uri),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
$r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
intval($allow[0]['data']),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$datarray['parent-uri'] = $parent_uri;
|
||||
$datarray['uid'] = $importer['importer_uid'];
|
||||
$datarray['contact-id'] = $importer['id'];
|
||||
if(($datarray['verb'] === ACTIVITY_LIKE)
|
||||
|| ($datarray['verb'] === ACTIVITY_DISLIKE)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTEND)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTENDNO)
|
||||
|| ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
|
||||
$datarray['type'] = 'activity';
|
||||
$datarray['gravity'] = GRAVITY_LIKE;
|
||||
// only one like or dislike per person
|
||||
// splitted into two queries for performance issues
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($datarray['uid']),
|
||||
dbesc($datarray['author-link']),
|
||||
dbesc($datarray['verb']),
|
||||
dbesc($datarray['parent-uri'])
|
||||
);
|
||||
if($r && count($r))
|
||||
continue;
|
||||
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
|
||||
intval($datarray['uid']),
|
||||
dbesc($datarray['author-link']),
|
||||
dbesc($datarray['verb']),
|
||||
dbesc($datarray['parent-uri'])
|
||||
);
|
||||
if($r && count($r))
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
|
||||
|
||||
$xo = parse_xml_string($datarray['object'],false);
|
||||
$xt = parse_xml_string($datarray['target'],false);
|
||||
|
||||
if($xt->type == ACTIVITY_OBJ_NOTE) {
|
||||
$r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
|
||||
dbesc($xt->id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
if(! count($r))
|
||||
continue;
|
||||
|
||||
// extract tag, if not duplicate, add to parent item
|
||||
if($xo->content) {
|
||||
if(! (stristr($r[0]['tag'],trim($xo->content)))) {
|
||||
q("UPDATE item SET tag = '%s' WHERE id = %d",
|
||||
dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
|
||||
intval($r[0]['id'])
|
||||
);
|
||||
create_tags_from_item($r[0]['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$posted_id = item_store($datarray);
|
||||
|
||||
// find out if our user is involved in this conversation and wants to be notified.
|
||||
|
||||
if(!x($datarray['type']) || $datarray['type'] != 'activity') {
|
||||
|
||||
$myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
|
||||
dbesc($top_uri),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
|
||||
if(count($myconv)) {
|
||||
$importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
|
||||
|
||||
// first make sure this isn't our own post coming back to us from a wall-to-wall event
|
||||
if(! link_compare($datarray['author-link'],$importer_url)) {
|
||||
|
||||
|
||||
foreach($myconv as $conv) {
|
||||
|
||||
// now if we find a match, it means we're in this conversation
|
||||
|
||||
if(! link_compare($conv['author-link'],$importer_url))
|
||||
continue;
|
||||
|
||||
require_once('include/enotify.php');
|
||||
|
||||
$conv_parent = $conv['parent'];
|
||||
|
||||
notification(array(
|
||||
'type' => NOTIFY_COMMENT,
|
||||
'notify_flags' => $importer['notify-flags'],
|
||||
'language' => $importer['language'],
|
||||
'to_name' => $importer['username'],
|
||||
'to_email' => $importer['email'],
|
||||
'uid' => $importer['importer_uid'],
|
||||
'item' => $datarray,
|
||||
'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
|
||||
'source_name' => stripslashes($datarray['author-name']),
|
||||
'source_link' => $datarray['author-link'],
|
||||
'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
|
||||
? $importer['thumb'] : $datarray['author-avatar']),
|
||||
'verb' => ACTIVITY_POST,
|
||||
'otype' => 'item',
|
||||
'parent' => $conv_parent,
|
||||
'parent_uri' => $parent_uri
|
||||
|
||||
));
|
||||
|
||||
// only send one notification
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
// Head post of a conversation. Have we seen it? If not, import it.
|
||||
|
||||
|
||||
$item_id = $item->get_id();
|
||||
$datarray = get_atom_elements($feed,$item);
|
||||
|
||||
if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
|
||||
$ev = bbtoevent($datarray['body']);
|
||||
if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
|
||||
$ev['cid'] = $importer['id'];
|
||||
$ev['uid'] = $importer['uid'];
|
||||
$ev['uri'] = $item_id;
|
||||
$ev['edited'] = $datarray['edited'];
|
||||
$ev['private'] = $datarray['private'];
|
||||
$ev['guid'] = $datarray['guid'];
|
||||
|
||||
$r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item_id),
|
||||
intval($importer['uid'])
|
||||
);
|
||||
if(count($r))
|
||||
$ev['id'] = $r[0]['id'];
|
||||
$xyz = event_store($ev);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
|
||||
// Update content if 'updated' changes
|
||||
|
||||
if(count($r)) {
|
||||
if (edited_timestamp_is_newer($r[0], $datarray)) {
|
||||
|
||||
// do not accept (ignore) an earlier edit than one we currently have.
|
||||
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
|
||||
continue;
|
||||
|
||||
$r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
dbesc($datarray['title']),
|
||||
dbesc($datarray['body']),
|
||||
dbesc($datarray['tag']),
|
||||
dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
create_tags_from_itemuri($item_id, $importer['importer_uid']);
|
||||
update_thread_uri($item_id, $importer['importer_uid']);
|
||||
}
|
||||
|
||||
// update last-child if it changes
|
||||
|
||||
$allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
|
||||
if($allow && $allow[0]['data'] != $r[0]['last-child']) {
|
||||
$r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
|
||||
intval($allow[0]['data']),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($item_id),
|
||||
intval($importer['importer_uid'])
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$datarray['parent-uri'] = $item_id;
|
||||
$datarray['uid'] = $importer['importer_uid'];
|
||||
$datarray['contact-id'] = $importer['id'];
|
||||
|
||||
|
||||
if(! link_compare($datarray['owner-link'],$importer['url'])) {
|
||||
// The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
|
||||
// but otherwise there's a possible data mixup on the sender's system.
|
||||
// the tgroup delivery code called from item_store will correct it if it's a forum,
|
||||
// but we're going to unconditionally correct it here so that the post will always be owned by our contact.
|
||||
logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
|
||||
$datarray['owner-name'] = $importer['senderName'];
|
||||
$datarray['owner-link'] = $importer['url'];
|
||||
$datarray['owner-avatar'] = $importer['thumb'];
|
||||
}
|
||||
|
||||
if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
|
||||
continue;
|
||||
|
||||
// This is my contact on another system, but it's really me.
|
||||
// Turn this into a wall post.
|
||||
$notify = item_is_remote_self($importer, $datarray);
|
||||
|
||||
$posted_id = item_store($datarray, false, $notify);
|
||||
|
||||
if(stristr($datarray['verb'],ACTIVITY_POKE)) {
|
||||
$verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
|
||||
if(! $verb)
|
||||
continue;
|
||||
$xo = parse_xml_string($datarray['object'],false);
|
||||
|
||||
if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
|
||||
|
||||
// somebody was poked/prodded. Was it me?
|
||||
|
||||
$links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
|
||||
|
||||
foreach($links->link as $l) {
|
||||
$atts = $l->attributes();
|
||||
switch($atts['rel']) {
|
||||
case "alternate":
|
||||
$Blink = $atts['href'];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
|
||||
|
||||
// send a notification
|
||||
require_once('include/enotify.php');
|
||||
|
||||
notification(array(
|
||||
'type' => NOTIFY_POKE,
|
||||
'notify_flags' => $importer['notify-flags'],
|
||||
'language' => $importer['language'],
|
||||
'to_name' => $importer['username'],
|
||||
'to_email' => $importer['email'],
|
||||
'uid' => $importer['importer_uid'],
|
||||
'item' => $datarray,
|
||||
'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
|
||||
'source_name' => stripslashes($datarray['author-name']),
|
||||
'source_link' => $datarray['author-link'],
|
||||
'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
|
||||
? $importer['thumb'] : $datarray['author-avatar']),
|
||||
'verb' => $datarray['verb'],
|
||||
'otype' => 'person',
|
||||
'activity' => $verb,
|
||||
'parent' => $datarray['parent']
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
// NOTREACHED
|
||||
|
||||
}
|
||||
|
||||
|
||||
function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
|
||||
$url = notags(trim($datarray['author-link']));
|
||||
$name = notags(trim($datarray['author-name']));
|
||||
|
|
@ -4287,7 +1458,7 @@ function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
|
|||
}
|
||||
}
|
||||
|
||||
function lose_follower($importer,$contact,$datarray,$item) {
|
||||
function lose_follower($importer,$contact,$datarray = array(),$item = "") {
|
||||
|
||||
if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
|
||||
q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
|
||||
|
|
@ -4300,7 +1471,7 @@ function lose_follower($importer,$contact,$datarray,$item) {
|
|||
}
|
||||
}
|
||||
|
||||
function lose_sharer($importer,$contact,$datarray,$item) {
|
||||
function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
|
||||
|
||||
if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
|
||||
q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
|
||||
|
|
@ -4313,7 +1484,6 @@ function lose_sharer($importer,$contact,$datarray,$item) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
|
||||
|
||||
$a = get_app();
|
||||
|
|
@ -4356,189 +1526,6 @@ function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
|
|||
|
||||
}
|
||||
|
||||
|
||||
function atom_author($tag,$name,$uri,$h,$w,$photo,$geo) {
|
||||
$o = '';
|
||||
if(! $tag)
|
||||
return $o;
|
||||
$name = xmlify($name);
|
||||
$uri = xmlify($uri);
|
||||
$h = intval($h);
|
||||
$w = intval($w);
|
||||
$photo = xmlify($photo);
|
||||
|
||||
|
||||
$o .= "<$tag>\r\n";
|
||||
$o .= "\t<name>$name</name>\r\n";
|
||||
$o .= "\t<uri>$uri</uri>\r\n";
|
||||
$o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
|
||||
$o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
|
||||
|
||||
if ($tag == "author") {
|
||||
|
||||
if($geo)
|
||||
$o .= '<georss:point>'.xmlify($geo).'</georss:point>'."\r\n";
|
||||
|
||||
$r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
|
||||
`profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
|
||||
`profile`.`homepage`,`contact`.`nick` FROM `profile`
|
||||
INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
|
||||
INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
|
||||
WHERE `profile`.`is-default` AND `contact`.`self` AND
|
||||
NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
|
||||
dbesc(normalise_link($uri)));
|
||||
if ($r) {
|
||||
$location = '';
|
||||
if($r[0]['locality'])
|
||||
$location .= $r[0]['locality'];
|
||||
if($r[0]['region']) {
|
||||
if($location)
|
||||
$location .= ', ';
|
||||
$location .= $r[0]['region'];
|
||||
}
|
||||
if($r[0]['country-name']) {
|
||||
if($location)
|
||||
$location .= ', ';
|
||||
$location .= $r[0]['country-name'];
|
||||
}
|
||||
|
||||
$o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
|
||||
$o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
|
||||
$o .= "\t<poco:note>".xmlify(bbcode($r[0]["about"]))."</poco:note>\r\n";
|
||||
$o .= "\t<poco:address>\r\n";
|
||||
$o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
|
||||
$o .= "\t</poco:address>\r\n";
|
||||
$o .= "\t<poco:urls>\r\n";
|
||||
$o .= "\t<poco:type>homepage</poco:type>\r\n";
|
||||
$o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
|
||||
$o .= "\t\t<poco:primary>true</poco:primary>\r\n";
|
||||
$o .= "\t</poco:urls>\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
call_hooks('atom_author', $o);
|
||||
|
||||
$o .= "</$tag>\r\n";
|
||||
return $o;
|
||||
}
|
||||
|
||||
function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
|
||||
|
||||
$a = get_app();
|
||||
|
||||
if(! $item['parent'])
|
||||
return;
|
||||
|
||||
if($item['deleted'])
|
||||
return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
|
||||
|
||||
|
||||
if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
|
||||
$body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
|
||||
else
|
||||
$body = $item['body'];
|
||||
|
||||
|
||||
$o = "\r\n\r\n<entry>\r\n";
|
||||
|
||||
if(is_array($author))
|
||||
$o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb'], $item['coord']);
|
||||
else
|
||||
$o .= atom_author('author',(($item['author-name']) ? $item['author-name'] : $item['name']),(($item['author-link']) ? $item['author-link'] : $item['url']),80,80,(($item['author-avatar']) ? $item['author-avatar'] : $item['thumb']), $item['coord']);
|
||||
if(strlen($item['owner-name']))
|
||||
$o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar'], $item['coord']);
|
||||
|
||||
if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
|
||||
$parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
|
||||
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
|
||||
$o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
|
||||
}
|
||||
|
||||
$htmlbody = $body;
|
||||
|
||||
if ($item['title'] != "")
|
||||
$htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
|
||||
|
||||
$htmlbody = bbcode($htmlbody, false, false, 7);
|
||||
|
||||
$o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
|
||||
$o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
|
||||
$o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
|
||||
$o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
|
||||
$o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
|
||||
$o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
|
||||
$o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
|
||||
|
||||
$o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
|
||||
|
||||
if($comment)
|
||||
$o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
|
||||
|
||||
if($item['location']) {
|
||||
$o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
|
||||
$o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
|
||||
}
|
||||
|
||||
if($item['coord'])
|
||||
$o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
|
||||
|
||||
if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
|
||||
$o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
|
||||
|
||||
if($item['extid'])
|
||||
$o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
|
||||
if($item['bookmark'])
|
||||
$o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
|
||||
|
||||
if($item['app'])
|
||||
$o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
|
||||
|
||||
if($item['guid'])
|
||||
$o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
|
||||
|
||||
if($item['signed_text']) {
|
||||
$sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
|
||||
$o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
|
||||
}
|
||||
|
||||
$verb = construct_verb($item);
|
||||
$o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
|
||||
$actobj = construct_activity_object($item);
|
||||
if(strlen($actobj))
|
||||
$o .= $actobj;
|
||||
$actarg = construct_activity_target($item);
|
||||
if(strlen($actarg))
|
||||
$o .= $actarg;
|
||||
|
||||
$tags = item_getfeedtags($item);
|
||||
if(count($tags)) {
|
||||
foreach($tags as $t)
|
||||
if (($type != 'html') OR ($t[0] != "@"))
|
||||
$o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
|
||||
}
|
||||
|
||||
/// @TODO
|
||||
/// To support these elements, the API needs to be enhanced
|
||||
/// $o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
|
||||
/// $o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
|
||||
/// $o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
|
||||
|
||||
// Deactivated since it was meant only for OStatus
|
||||
//$o .= item_get_attachment($item);
|
||||
|
||||
$o .= item_getfeedattach($item);
|
||||
|
||||
$mentioned = get_mentions($item);
|
||||
if($mentioned)
|
||||
$o .= $mentioned;
|
||||
|
||||
call_hooks('atom_entry', $o);
|
||||
|
||||
$o .= '</entry>' . "\r\n";
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
function fix_private_photos($s, $uid, $item = null, $cid = 0) {
|
||||
|
||||
if(get_config('system','disable_embedded'))
|
||||
|
|
@ -4642,7 +1629,6 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) {
|
|||
return($new_body);
|
||||
}
|
||||
|
||||
|
||||
function has_permissions($obj) {
|
||||
if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
|
||||
return true;
|
||||
|
|
@ -4703,50 +1689,6 @@ function item_getfeedtags($item) {
|
|||
return $ret;
|
||||
}
|
||||
|
||||
function item_get_attachment($item) {
|
||||
$o = "";
|
||||
$siteinfo = get_attached_data($item["body"]);
|
||||
|
||||
switch($siteinfo["type"]) {
|
||||
case 'link':
|
||||
$o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
|
||||
break;
|
||||
case 'photo':
|
||||
$imgdata = get_photo_info($siteinfo["image"]);
|
||||
$o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
|
||||
break;
|
||||
case 'video':
|
||||
$o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
function item_getfeedattach($item) {
|
||||
$ret = '';
|
||||
$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) {
|
||||
$ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
|
||||
if(intval($matches[2]))
|
||||
$ret .= 'length="' . intval($matches[2]) . '" ';
|
||||
if($matches[4] !== ' ')
|
||||
$ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
|
||||
$ret .= ' />' . "\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function item_expire($uid, $days, $network = "", $force = false) {
|
||||
|
||||
if((! $uid) || ($days < 1))
|
||||
|
|
|
|||
|
|
@ -279,6 +279,9 @@ function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact)
|
|||
$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'])
|
||||
|
|
@ -289,9 +292,15 @@ function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact)
|
|||
$r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
|
||||
intval($contact_uid)
|
||||
);
|
||||
*/
|
||||
// Is the contact the owner? Then fetch the private key
|
||||
if ($contact['self'] AND ($contact['uid'] > 0)) {
|
||||
$r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
|
||||
intval($contact['uid'])
|
||||
);
|
||||
|
||||
if( $r)
|
||||
$authorsig = base64_encode(rsa_sign($signed_text,$r['prvkey'],'sha256'));
|
||||
if($r)
|
||||
$authorsig = base64_encode(rsa_sign($signed_text,$r[0]['prvkey'],'sha256'));
|
||||
}
|
||||
|
||||
if(! isset($authorsig))
|
||||
|
|
@ -329,6 +338,10 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
|
|||
$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'])
|
||||
|
|
@ -343,6 +356,17 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
|
|||
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)
|
||||
|
|
@ -353,7 +377,7 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
|
|||
intval($r[0]['parent'])
|
||||
);
|
||||
if( $p) {
|
||||
$signed_text = $r[0]['guid'] . ';Post;' . $p[0]['guid'] . ';true;' . $diaspora_handle;
|
||||
$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'));
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ if(!function_exists('z_fetch_url')){
|
|||
* @return array an assoziative array with:
|
||||
* * \e int \b return_code => HTTP return code or 0 if timeout or failure
|
||||
* * \e boolean \b success => boolean true (if HTTP 2xx result) or false
|
||||
* * \e string \b redirect_url => in case of redirect, content was finally retrieved from this URL
|
||||
* * \e string \b header => HTTP headers
|
||||
* * \e string \b body => fetched content
|
||||
*/
|
||||
|
|
@ -116,6 +117,9 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
|
|||
// if it throws any errors.
|
||||
|
||||
$s = @curl_exec($ch);
|
||||
if (curl_errno($ch) !== CURLE_OK) {
|
||||
logger('fetch_url error fetching '.$url.': '.curl_error($ch), LOGGER_NORMAL);
|
||||
}
|
||||
|
||||
$base = $s;
|
||||
$curl_info = @curl_getinfo($ch);
|
||||
|
|
@ -133,6 +137,10 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
|
|||
$base = substr($base,strlen($chunk));
|
||||
}
|
||||
|
||||
$a->set_curl_code($http_code);
|
||||
$a->set_curl_content_type($curl_info['content_type']);
|
||||
$a->set_curl_headers($header);
|
||||
|
||||
if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
|
||||
$new_location_info = @parse_url($curl_info["redirect_url"]);
|
||||
$old_location_info = @parse_url($curl_info["url"]);
|
||||
|
|
@ -160,13 +168,13 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
|
|||
$a->set_curl_content_type($curl_info['content_type']);
|
||||
|
||||
$body = substr($s,strlen($header));
|
||||
$a->set_curl_headers($header);
|
||||
|
||||
|
||||
|
||||
$rc = intval($http_code);
|
||||
$ret['return_code'] = $rc;
|
||||
$ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
|
||||
$ret['redirect_url'] = $url;
|
||||
if(! $ret['success']) {
|
||||
$ret['error'] = curl_error($ch);
|
||||
$ret['debug'] = $curl_info;
|
||||
|
|
@ -1246,6 +1254,9 @@ function original_url($url, $depth=1, $fetchbody = false) {
|
|||
|
||||
$a->save_timestamp($stamp1, "network");
|
||||
|
||||
if ($http_code == 0)
|
||||
return($url);
|
||||
|
||||
if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302"))
|
||||
AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) {
|
||||
if ($curl_info['redirect_url'] != "")
|
||||
|
|
|
|||
|
|
@ -535,7 +535,7 @@ function notifier_run(&$argv, &$argc){
|
|||
|
||||
if($public_message) {
|
||||
|
||||
if (!$followup)
|
||||
if (!$followup AND $top_level)
|
||||
$r0 = diaspora_fetch_relay();
|
||||
else
|
||||
$r0 = array();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,13 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
|
|||
|
||||
$a = get_app();
|
||||
|
||||
$txt = Cache::get($a->videowidth . $embedurl);
|
||||
$r = q("SELECT * FROM `oembed` WHERE `url` = '%s'",
|
||||
dbesc(normalise_link($embedurl)));
|
||||
|
||||
if ($r)
|
||||
$txt = $r[0]["content"];
|
||||
else
|
||||
$txt = Cache::get($a->videowidth . $embedurl);
|
||||
|
||||
// These media files should now be caught in bbcode.php
|
||||
// left here as a fallback in case this is called from another source
|
||||
|
|
@ -66,8 +72,14 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
|
|||
|
||||
if ($txt[0]!="{")
|
||||
$txt='{"type":"error"}';
|
||||
else //save in cache
|
||||
else { //save in cache
|
||||
$j = json_decode($txt);
|
||||
if ($j->type != "error")
|
||||
q("INSERT INTO `oembed` (`url`, `content`, `created`) VALUES ('%s', '%s', '%s')",
|
||||
dbesc(normalise_link($embedurl)), dbesc($txt), dbesc(datetime_convert()));
|
||||
|
||||
Cache::set($a->videowidth . $embedurl,$txt, CACHE_DAY);
|
||||
}
|
||||
}
|
||||
|
||||
$j = json_decode($txt);
|
||||
|
|
@ -85,7 +97,7 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
|
|||
// If fetching information doesn't work, then improve via internal functions
|
||||
if (($j->type == "error") OR ($no_rich_type AND ($j->type == "rich"))) {
|
||||
require_once("mod/parse_url.php");
|
||||
$data = parseurl_getsiteinfo($embedurl, true, false);
|
||||
$data = parseurl_getsiteinfo_cached($embedurl, true, false);
|
||||
$j->type = $data["type"];
|
||||
|
||||
if ($j->type == "photo") {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ function onepoll_run(&$argv, &$argc){
|
|||
|
||||
require_once('include/session.php');
|
||||
require_once('include/datetime.php');
|
||||
require_once('library/simplepie/simplepie.inc');
|
||||
require_once('include/items.php');
|
||||
require_once('include/Contact.php');
|
||||
require_once('include/email.php');
|
||||
|
|
@ -335,7 +334,9 @@ function onepoll_run(&$argv, &$argc){
|
|||
if($contact['rel'] == CONTACT_IS_FOLLOWER || $contact['blocked'] || $contact['readonly'])
|
||||
return;
|
||||
|
||||
$xml = fetch_url($contact['poll']);
|
||||
$cookiejar = tempnam(get_temppath(), 'cookiejar-onepoll-');
|
||||
$xml = fetch_url($contact['poll'], false, $redirects, 0, Null, $cookiejar);
|
||||
unlink($cookiejar);
|
||||
}
|
||||
elseif($contact['network'] === NETWORK_MAIL || $contact['network'] === NETWORK_MAIL2) {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,15 +17,6 @@ define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes
|
|||
define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes
|
||||
define('OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS', 14400); // given in minutes
|
||||
|
||||
define("NS_ATOM", "http://www.w3.org/2005/Atom");
|
||||
define("NS_THR", "http://purl.org/syndication/thread/1.0");
|
||||
define("NS_GEORSS", "http://www.georss.org/georss");
|
||||
define("NS_ACTIVITY", "http://activitystrea.ms/spec/1.0/");
|
||||
define("NS_MEDIA", "http://purl.org/syndication/atommedia");
|
||||
define("NS_POCO", "http://portablecontacts.net/spec/1.0");
|
||||
define("NS_OSTATUS", "http://ostatus.org/schema/1.0");
|
||||
define("NS_STATUSNET", "http://status.net/schema/api/1/");
|
||||
|
||||
function ostatus_check_follow_friends() {
|
||||
$r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''");
|
||||
|
||||
|
|
@ -127,47 +118,57 @@ function ostatus_fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch)
|
|||
$author["owner-link"] = $author["author-link"];
|
||||
$author["owner-avatar"] = $author["author-avatar"];
|
||||
|
||||
if ($r AND !$onlyfetch) {
|
||||
// Only update the contacts if it is an OStatus contact
|
||||
if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
|
||||
// Update contact data
|
||||
$update_contact = ($r[0]['name-date'] < datetime_convert('','','now -12 hours'));
|
||||
if ($update_contact) {
|
||||
|
||||
$value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["notify"] = $value;
|
||||
|
||||
$value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["alias"] = $value;
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["name"] = $value;
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["nick"] = $value;
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["about"] = html2bbcode($value);
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["location"] = $value;
|
||||
|
||||
if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) {
|
||||
|
||||
logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["name"] = $value;
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["nick"] = $value;
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["about"] = html2bbcode($value);
|
||||
|
||||
$value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue;
|
||||
if ($value != "")
|
||||
$contact["location"] = $value;
|
||||
|
||||
q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d AND `network` = '%s'",
|
||||
q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d",
|
||||
dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]),
|
||||
dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS));
|
||||
dbesc(datetime_convert()), intval($contact["id"]));
|
||||
|
||||
poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
|
||||
"", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
|
||||
"", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
|
||||
}
|
||||
|
||||
$update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours'));
|
||||
|
||||
if ($update_photo AND isset($author["author-avatar"])) {
|
||||
if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) {
|
||||
logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
|
||||
|
||||
$photos = import_profile_photo($author["author-avatar"], $importer["uid"], $contact["id"]);
|
||||
|
||||
q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d AND `network` = '%s'",
|
||||
dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]),
|
||||
dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS));
|
||||
update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
|
||||
}
|
||||
|
||||
|
||||
/// @todo Add the "addr" field
|
||||
$contact["generation"] = 2;
|
||||
$contact["photo"] = $author["author-avatar"];
|
||||
update_gcontact($contact);
|
||||
}
|
||||
|
||||
return($author);
|
||||
|
|
@ -183,14 +184,14 @@ function ostatus_salmon_author($xml, $importer) {
|
|||
@$doc->loadXML($xml);
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
$xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom");
|
||||
$xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0");
|
||||
$xpath->registerNamespace('georss', "http://www.georss.org/georss");
|
||||
$xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/");
|
||||
$xpath->registerNamespace('media', "http://purl.org/syndication/atommedia");
|
||||
$xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0");
|
||||
$xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0");
|
||||
$xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/");
|
||||
$xpath->registerNamespace('atom', NAMESPACE_ATOM1);
|
||||
$xpath->registerNamespace('thr', NAMESPACE_THREAD);
|
||||
$xpath->registerNamespace('georss', NAMESPACE_GEORSS);
|
||||
$xpath->registerNamespace('activity', NAMESPACE_ACTIVITY);
|
||||
$xpath->registerNamespace('media', NAMESPACE_MEDIA);
|
||||
$xpath->registerNamespace('poco', NAMESPACE_POCO);
|
||||
$xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS);
|
||||
$xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET);
|
||||
|
||||
$entries = $xpath->query('/atom:entry');
|
||||
|
||||
|
|
@ -214,14 +215,14 @@ function ostatus_import($xml,$importer,&$contact, &$hub) {
|
|||
@$doc->loadXML($xml);
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
$xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom");
|
||||
$xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0");
|
||||
$xpath->registerNamespace('georss', "http://www.georss.org/georss");
|
||||
$xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/");
|
||||
$xpath->registerNamespace('media', "http://purl.org/syndication/atommedia");
|
||||
$xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0");
|
||||
$xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0");
|
||||
$xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/");
|
||||
$xpath->registerNamespace('atom', NAMESPACE_ATOM1);
|
||||
$xpath->registerNamespace('thr', NAMESPACE_THREAD);
|
||||
$xpath->registerNamespace('georss', NAMESPACE_GEORSS);
|
||||
$xpath->registerNamespace('activity', NAMESPACE_ACTIVITY);
|
||||
$xpath->registerNamespace('media', NAMESPACE_MEDIA);
|
||||
$xpath->registerNamespace('poco', NAMESPACE_POCO);
|
||||
$xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS);
|
||||
$xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET);
|
||||
|
||||
$gub = "";
|
||||
$hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes;
|
||||
|
|
@ -546,29 +547,6 @@ function ostatus_import($xml,$importer,&$contact, &$hub) {
|
|||
}
|
||||
|
||||
logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
|
||||
$item["id"] = $item_id;
|
||||
|
||||
if ($mention) {
|
||||
$u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($item['uid']));
|
||||
$r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($item_id));
|
||||
|
||||
notification(array(
|
||||
'type' => NOTIFY_TAGSELF,
|
||||
'notify_flags' => $u[0]["notify-flags"],
|
||||
'language' => $u[0]["language"],
|
||||
'to_name' => $u[0]["username"],
|
||||
'to_email' => $u[0]["email"],
|
||||
'uid' => $item["uid"],
|
||||
'item' => $item,
|
||||
'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($item_id)),
|
||||
'source_name' => $item["author-name"],
|
||||
'source_link' => $item["author-link"],
|
||||
'source_photo' => $item["author-avatar"],
|
||||
'verb' => ACTIVITY_TAG,
|
||||
'otype' => 'item',
|
||||
'parent' => $r[0]["parent"]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1013,28 +991,6 @@ function ostatus_completion($conversation_url, $uid, $item = array()) {
|
|||
// Add the conversation entry (but don't fetch the whole conversation)
|
||||
ostatus_store_conversation($newitem, $conversation_url);
|
||||
|
||||
if ($mention) {
|
||||
$u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($uid));
|
||||
$r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($newitem));
|
||||
|
||||
notification(array(
|
||||
'type' => NOTIFY_TAGSELF,
|
||||
'notify_flags' => $u[0]["notify-flags"],
|
||||
'language' => $u[0]["language"],
|
||||
'to_name' => $u[0]["username"],
|
||||
'to_email' => $u[0]["email"],
|
||||
'uid' => $uid,
|
||||
'item' => $arr,
|
||||
'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($newitem)),
|
||||
'source_name' => $arr["author-name"],
|
||||
'source_link' => $arr["author-link"],
|
||||
'source_photo' => $arr["author-avatar"],
|
||||
'verb' => ACTIVITY_TAG,
|
||||
'otype' => 'item',
|
||||
'parent' => $r[0]["parent"]
|
||||
));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
@ -1109,7 +1065,7 @@ function get_reshared_guid($item) {
|
|||
return $guid;
|
||||
}
|
||||
|
||||
function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) {
|
||||
function xml_create_element($doc, $element, $value = "", $attributes = array()) {
|
||||
$element = $doc->createElement($element, xmlify($value));
|
||||
|
||||
foreach ($attributes AS $key => $value) {
|
||||
|
|
@ -1117,7 +1073,11 @@ function xml_add_element($doc, $parent, $element, $value = "", $attributes = arr
|
|||
$attribute->value = xmlify($value);
|
||||
$element->appendChild($attribute);
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) {
|
||||
$element = xml_create_element($doc, $element, $value, $attributes);
|
||||
$parent->appendChild($element);
|
||||
}
|
||||
|
||||
|
|
@ -1151,16 +1111,16 @@ function ostatus_format_picture_post($body) {
|
|||
function ostatus_add_header($doc, $owner) {
|
||||
$a = get_app();
|
||||
|
||||
$root = $doc->createElementNS(NS_ATOM, 'feed');
|
||||
$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
|
||||
$doc->appendChild($root);
|
||||
|
||||
$root->setAttribute("xmlns:thr", NS_THR);
|
||||
$root->setAttribute("xmlns:georss", NS_GEORSS);
|
||||
$root->setAttribute("xmlns:activity", NS_ACTIVITY);
|
||||
$root->setAttribute("xmlns:media", NS_MEDIA);
|
||||
$root->setAttribute("xmlns:poco", NS_POCO);
|
||||
$root->setAttribute("xmlns:ostatus", NS_OSTATUS);
|
||||
$root->setAttribute("xmlns:statusnet", NS_STATUSNET);
|
||||
$root->setAttribute("xmlns:thr", NAMESPACE_THREAD);
|
||||
$root->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
|
||||
$root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
|
||||
$root->setAttribute("xmlns:media", NAMESPACE_MEDIA);
|
||||
$root->setAttribute("xmlns:poco", NAMESPACE_POCO);
|
||||
$root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
|
||||
$root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
|
||||
|
||||
$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
|
||||
xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
|
||||
|
|
@ -1352,6 +1312,10 @@ function ostatus_add_author($doc, $owner) {
|
|||
function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) {
|
||||
$a = get_app();
|
||||
|
||||
if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
|
||||
logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG);
|
||||
}
|
||||
|
||||
$is_repeat = false;
|
||||
|
||||
/* if (!$repeat) {
|
||||
|
|
@ -1374,15 +1338,15 @@ function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false)
|
|||
$entry = $doc->createElement("activity:object");
|
||||
$title = sprintf("New note by %s", $owner["nick"]);
|
||||
} else {
|
||||
$entry = $doc->createElementNS(NS_ATOM, "entry");
|
||||
$entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry");
|
||||
|
||||
$entry->setAttribute("xmlns:thr", NS_THR);
|
||||
$entry->setAttribute("xmlns:georss", NS_GEORSS);
|
||||
$entry->setAttribute("xmlns:activity", NS_ACTIVITY);
|
||||
$entry->setAttribute("xmlns:media", NS_MEDIA);
|
||||
$entry->setAttribute("xmlns:poco", NS_POCO);
|
||||
$entry->setAttribute("xmlns:ostatus", NS_OSTATUS);
|
||||
$entry->setAttribute("xmlns:statusnet", NS_STATUSNET);
|
||||
$entry->setAttribute("xmlns:thr", NAMESPACE_THREAD);
|
||||
$entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
|
||||
$entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
|
||||
$entry->setAttribute("xmlns:media", NAMESPACE_MEDIA);
|
||||
$entry->setAttribute("xmlns:poco", NAMESPACE_POCO);
|
||||
$entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
|
||||
$entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
|
||||
|
||||
$author = ostatus_add_author($doc, $owner);
|
||||
$entry->appendChild($author);
|
||||
|
|
@ -1447,9 +1411,12 @@ function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false)
|
|||
$repeated_owner["about"] = "";
|
||||
$repeated_owner["uid"] = 0;
|
||||
|
||||
$r =q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", normalise_link($repeated_item["author-link"]));
|
||||
// Fetch the missing data from the global contacts
|
||||
$r =q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", normalise_link($repeated_item["author-link"]));
|
||||
if ($r) {
|
||||
$repeated_owner["nick"] = $r[0]["nick"];
|
||||
if ($r[0]["nick"] != "")
|
||||
$repeated_owner["nick"] = $r[0]["nick"];
|
||||
|
||||
$repeated_owner["location"] = $r[0]["location"];
|
||||
$repeated_owner["about"] = $r[0]["about"];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@ function get_attached_data($body) {
|
|||
$post["text"] = $body;
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match_all("(\[url\]([$URLSearchString]*)\[\/url\])ism", $body, $links, PREG_SET_ORDER)) {
|
||||
if (count($links) == 1) {
|
||||
$post["type"] = "text";
|
||||
$post["url"] = $links[0][1];
|
||||
$post["text"] = $body;
|
||||
}
|
||||
}
|
||||
if (!isset($post["type"])) {
|
||||
$post["type"] = "text";
|
||||
$post["text"] = trim($body);
|
||||
|
|
@ -129,9 +137,17 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
|
|||
require_once("include/html2plain.php");
|
||||
require_once("include/network.php");
|
||||
|
||||
// Remove the hash tags
|
||||
$URLSearchString = "^\[\]";
|
||||
$body = preg_replace("/([#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $b["body"]);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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"]);
|
||||
//$post = get_attached_data($b["body"]);
|
||||
$post = get_attached_data($body);
|
||||
|
||||
if (($b["title"] != "") AND ($post["text"] != ""))
|
||||
$post["text"] = trim($b["title"]."\n\n".$post["text"]);
|
||||
|
|
@ -146,6 +162,8 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
|
|||
if ($includedlinks) {
|
||||
if ($post["type"] == "link")
|
||||
$link = $post["url"];
|
||||
elseif ($post["type"] == "text")
|
||||
$link = $post["url"];
|
||||
elseif ($post["type"] == "video")
|
||||
$link = $post["url"];
|
||||
elseif ($post["type"] == "photo")
|
||||
|
|
@ -161,8 +179,22 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
|
|||
// But: if the link is beyond the limit, then it has to be added.
|
||||
if (($link != "") AND strstr($msg, $link)) {
|
||||
$pos = strpos($msg, $link);
|
||||
if (($limit == 0) OR ($pos < $limit))
|
||||
|
||||
// Will the text be shortened in the link?
|
||||
// Or is the link the last item in the post?
|
||||
if (($limit > 0) AND ($pos < $limit) AND (($pos + 23 > $limit) OR ($pos + strlen($link) == strlen($msg))))
|
||||
$msg = trim(str_replace($link, "", $msg));
|
||||
elseif (($limit == 0) OR ($pos < $limit)) {
|
||||
// The limit has to be increased since it will be shortened - but not now
|
||||
// Only do it with Twitter (htmlmode = 8)
|
||||
if (($limit > 0) AND (strlen($link) > 23) AND ($htmlmode == 8))
|
||||
$limit = $limit - 23 + strlen($link);
|
||||
|
||||
$link = "";
|
||||
|
||||
if ($post["type"] == "text")
|
||||
unset($post["url"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +210,9 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
|
|||
|
||||
if (iconv_strlen($msg, "UTF-8") > $limit) {
|
||||
|
||||
if (!isset($post["url"])) {
|
||||
if (($post["type"] == "text") AND isset($post["url"]))
|
||||
$post["url"] = $b["plink"];
|
||||
elseif (!isset($post["url"])) {
|
||||
$limit = $limit - 23;
|
||||
$post["url"] = $b["plink"];
|
||||
} elseif (strpos($b["body"], "[share") !== false)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ function poller_run(&$argv, &$argc){
|
|||
unset($db_host, $db_user, $db_pass, $db_data);
|
||||
};
|
||||
|
||||
if (poller_max_connections_reached())
|
||||
return;
|
||||
|
||||
$load = current_load();
|
||||
if($load) {
|
||||
$maxsysload = intval(get_config('system','maxloadavg'));
|
||||
|
|
@ -39,8 +42,10 @@ function poller_run(&$argv, &$argc){
|
|||
}
|
||||
|
||||
// Checking the number of workers
|
||||
if (poller_too_much_workers(1))
|
||||
if (poller_too_much_workers(1)) {
|
||||
poller_kill_stale_workers();
|
||||
return;
|
||||
}
|
||||
|
||||
if(($argc <= 1) OR ($argv[1] != "no_cron")) {
|
||||
// Run the cron job that calls all other jobs
|
||||
|
|
@ -50,16 +55,7 @@ function poller_run(&$argv, &$argc){
|
|||
proc_run("php","include/cronhooks.php");
|
||||
|
||||
// Cleaning dead processes
|
||||
$r = q("SELECT DISTINCT(`pid`) FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00'");
|
||||
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",
|
||||
intval($pid["pid"]));
|
||||
else {
|
||||
/// @TODO Kill long running processes
|
||||
/// But: Update processes (like the database update) mustn't be killed
|
||||
}
|
||||
|
||||
poller_kill_stale_workers();
|
||||
} else
|
||||
// Sleep four seconds before checking for running processes again to avoid having too many workers
|
||||
sleep(4);
|
||||
|
|
@ -72,6 +68,10 @@ function poller_run(&$argv, &$argc){
|
|||
|
||||
while ($r = q("SELECT * FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `created` LIMIT 1")) {
|
||||
|
||||
// Constantly check the number of available database connections to let the frontend be accessible at any time
|
||||
if (poller_max_connections_reached())
|
||||
return;
|
||||
|
||||
// Count active workers and compare them with a maximum value that depends on the load
|
||||
if (poller_too_much_workers(3))
|
||||
return;
|
||||
|
|
@ -124,6 +124,107 @@ 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'");
|
||||
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",
|
||||
intval($pid["pid"]));
|
||||
else {
|
||||
// Kill long running processes
|
||||
$duration = (time() - strtotime($pid["executed"])) / 60;
|
||||
if ($duration > 180) {
|
||||
logger("Worker process ".$pid["pid"]." took more than 3 hours. It will be killed now.");
|
||||
posix_kill($pid["pid"], SIGTERM);
|
||||
|
||||
// Question: If a process is stale: Should we remove it or should we reschedule it?
|
||||
// By now we rescheduling it. It's maybe not the wisest decision?
|
||||
q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d",
|
||||
intval($pid["pid"]));
|
||||
} else
|
||||
logger("Worker process ".$pid["pid"]." now runs for ".round($duration)." minutes. That's okay.", LOGGER_DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
function poller_too_much_workers($stage) {
|
||||
|
||||
$queues = get_config("system", "worker_queues");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
require_once('include/datetime.php');
|
||||
require_once('include/diaspora.php');
|
||||
require_once('include/queue_fn.php');
|
||||
require_once('include/Contact.php');
|
||||
|
||||
function profile_change() {
|
||||
|
||||
|
|
@ -53,19 +54,7 @@ function profile_change() {
|
|||
$about = xmlify($profile['about']);
|
||||
require_once('include/bbcode.php');
|
||||
$about = xmlify(strip_tags(bbcode($about)));
|
||||
$location = '';
|
||||
if($profile['locality'])
|
||||
$location .= $profile['locality'];
|
||||
if($profile['region']) {
|
||||
if($location)
|
||||
$location .= ', ';
|
||||
$location .= $profile['region'];
|
||||
}
|
||||
if($profile['country-name']) {
|
||||
if($location)
|
||||
$location .= ', ';
|
||||
$location .= $profile['country-name'];
|
||||
}
|
||||
$location = formatted_location($profile);
|
||||
$location = xmlify($location);
|
||||
$tags = '';
|
||||
if($profile['pub_keywords']) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
require_once("boot.php");
|
||||
require_once('include/queue_fn.php');
|
||||
require_once('include/dfrn.php');
|
||||
|
||||
function queue_run(&$argv, &$argc){
|
||||
global $a, $db;
|
||||
|
|
@ -52,6 +53,8 @@ function queue_run(&$argv, &$argc){
|
|||
$queue_id = 0;
|
||||
|
||||
$deadguys = array();
|
||||
$deadservers = array();
|
||||
$serverlist = array();
|
||||
|
||||
logger('queue: start');
|
||||
|
||||
|
|
@ -95,7 +98,7 @@ function queue_run(&$argv, &$argc){
|
|||
// For the first 12 hours we'll try to deliver every 15 minutes
|
||||
// After that, we'll only attempt delivery once per hour.
|
||||
|
||||
$r = q("SELECT `id` FROM `queue` WHERE (( `created` > UTC_TIMESTAMP() - INTERVAL 12 HOUR && `last` < UTC_TIMESTAMP() - INTERVAL 15 MINUTE ) OR ( `last` < UTC_TIMESTAMP() - INTERVAL 1 HOUR ))");
|
||||
$r = q("SELECT `id` FROM `queue` WHERE ((`created` > UTC_TIMESTAMP() - INTERVAL 12 HOUR && `last` < UTC_TIMESTAMP() - INTERVAL 15 MINUTE) OR (`last` < UTC_TIMESTAMP() - INTERVAL 1 HOUR)) ORDER BY `cid`, `created`");
|
||||
}
|
||||
if(! $r){
|
||||
return;
|
||||
|
|
@ -116,7 +119,7 @@ function queue_run(&$argv, &$argc){
|
|||
// so check again if this entry still needs processing
|
||||
|
||||
if($queue_id) {
|
||||
$qi = q("select * from queue where `id` = %d limit 1",
|
||||
$qi = q("SELECT * FROM `queue` WHERE `id` = %d LIMIT 1",
|
||||
intval($queue_id)
|
||||
);
|
||||
}
|
||||
|
|
@ -142,8 +145,18 @@ function queue_run(&$argv, &$argc){
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!poco_reachable($c[0]['url'])) {
|
||||
logger('queue: skipping probably dead url: ' . $c[0]['url']);
|
||||
$server = poco_detect_server($c[0]['url']);
|
||||
|
||||
if (($server != "") AND !in_array($server, $serverlist)) {
|
||||
logger("Check server ".$server." (".$c[0]["network"].")");
|
||||
if (!poco_check_server($server, $c[0]["network"], true))
|
||||
$deadservers[] = $server;
|
||||
|
||||
$serverlist[] = $server;
|
||||
}
|
||||
|
||||
if (($server != "") AND in_array($server, $deadservers)) {
|
||||
logger('queue: skipping known dead server: '.$server);
|
||||
update_queue_time($q_item['id']);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -166,37 +179,39 @@ function queue_run(&$argv, &$argc){
|
|||
|
||||
switch($contact['network']) {
|
||||
case NETWORK_DFRN:
|
||||
logger('queue: dfrndelivery: item ' . $q_item['id'] . ' for ' . $contact['name']);
|
||||
$deliver_status = dfrn_deliver($owner,$contact,$data);
|
||||
logger('queue: dfrndelivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>');
|
||||
$deliver_status = dfrn::deliver($owner,$contact,$data);
|
||||
|
||||
if($deliver_status == (-1)) {
|
||||
update_queue_time($q_item['id']);
|
||||
$deadguys[] = $contact['notify'];
|
||||
}
|
||||
else {
|
||||
} else
|
||||
remove_queue_item($q_item['id']);
|
||||
}
|
||||
|
||||
break;
|
||||
case NETWORK_OSTATUS:
|
||||
if($contact['notify']) {
|
||||
logger('queue: slapdelivery: item ' . $q_item['id'] . ' for ' . $contact['name']);
|
||||
logger('queue: slapdelivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>');
|
||||
$deliver_status = slapper($owner,$contact['notify'],$data);
|
||||
|
||||
if($deliver_status == (-1))
|
||||
if($deliver_status == (-1)) {
|
||||
update_queue_time($q_item['id']);
|
||||
else
|
||||
$deadguys[] = $contact['notify'];
|
||||
} else
|
||||
remove_queue_item($q_item['id']);
|
||||
}
|
||||
break;
|
||||
case NETWORK_DIASPORA:
|
||||
if($contact['notify']) {
|
||||
logger('queue: diaspora_delivery: item ' . $q_item['id'] . ' for ' . $contact['name']);
|
||||
logger('queue: diaspora_delivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>');
|
||||
$deliver_status = diaspora_transmit($owner,$contact,$data,$public,true);
|
||||
|
||||
if($deliver_status == (-1))
|
||||
if($deliver_status == (-1)) {
|
||||
update_queue_time($q_item['id']);
|
||||
else
|
||||
$deadguys[] = $contact['notify'];
|
||||
} else
|
||||
remove_queue_item($q_item['id']);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -212,6 +227,7 @@ function queue_run(&$argv, &$argc){
|
|||
break;
|
||||
|
||||
}
|
||||
logger('Deliver status '.$deliver_status.' for item '.$q_item['id'].' to '.$contact['name'].' <'.$contact['url'].'>');
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,261 +0,0 @@
|
|||
<?php
|
||||
/* update friendica */
|
||||
define('APIBASE', 'http://github.com/api/v2/');
|
||||
define('F9KREPO', 'friendica/friendica');
|
||||
|
||||
$up_totalfiles = 0;
|
||||
$up_countfiles = 0;
|
||||
$up_lastp = -1;
|
||||
|
||||
function checkUpdate(){
|
||||
$r = fetch_url( APIBASE."json/repos/show/".F9KREPO."/tags" );
|
||||
$tags = json_decode($r);
|
||||
|
||||
$tag = 0.0;
|
||||
foreach ($tags->tags as $i=>$v){
|
||||
$i = (float)$i;
|
||||
if ($i>$tag) $tag=$i;
|
||||
}
|
||||
|
||||
if ($tag==0.0) return false;
|
||||
$f = fetch_url("https://raw.github.com/".F9KREPO."/".$tag."/boot.php","r");
|
||||
preg_match("|'FRIENDICA_VERSION', *'([^']*)'|", $f, $m);
|
||||
$version = $m[1];
|
||||
|
||||
$lv = explode(".", FRIENDICA_VERSION);
|
||||
$rv = explode(".",$version);
|
||||
foreach($lv as $i=>$v){
|
||||
if ((int)$lv[$i] < (int)$rv[$i]) {
|
||||
return array($tag, $version, "https://github.com/friendica/friendica/zipball/".$tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function canWeWrite(){
|
||||
$bd = dirname(dirname(__file__));
|
||||
return is_writable( $bd."/boot.php" );
|
||||
}
|
||||
|
||||
function out($txt){ echo "§".$txt."§"; ob_end_flush(); flush();}
|
||||
|
||||
function up_count($path){
|
||||
|
||||
$file_count = 0;
|
||||
|
||||
$dir_handle = opendir($path);
|
||||
|
||||
if (!$dir_handle) return -1;
|
||||
|
||||
while ($file = readdir($dir_handle)) {
|
||||
|
||||
if ($file == '.' || $file == '..') continue;
|
||||
$file_count++;
|
||||
|
||||
if (is_dir($path . $file)){
|
||||
$file_count += up_count($path . $file . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
closedir($dir_handle);
|
||||
|
||||
return $file_count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function up_unzip($file, $folder="/tmp"){
|
||||
$folder.="/";
|
||||
$zip = zip_open($file);
|
||||
if ($zip) {
|
||||
while ($zip_entry = zip_read($zip)) {
|
||||
$zip_entry_name = zip_entry_name($zip_entry);
|
||||
if (substr($zip_entry_name,strlen($zip_entry_name)-1,1)=="/"){
|
||||
mkdir($folder.$zip_entry_name,0777, true);
|
||||
} else {
|
||||
$fp = fopen($folder.$zip_entry_name, "w");
|
||||
if (zip_entry_open($zip, $zip_entry, "r")) {
|
||||
$buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));
|
||||
fwrite($fp,"$buf");
|
||||
zip_entry_close($zip_entry);
|
||||
fclose($fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
zip_close($zip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk recoursively in a folder and call a callback function on every
|
||||
* dir entry.
|
||||
* args:
|
||||
* $dir string base dir to walk
|
||||
* $callback function callback function
|
||||
* $sort int 0: ascending, 1: descending
|
||||
* $cb_argv any extra value passed to callback
|
||||
*
|
||||
* callback signature:
|
||||
* function name($fn, $dir [, $argv])
|
||||
* $fn string full dir entry name
|
||||
* $dir string start dir path
|
||||
* $argv any user value to callback
|
||||
*
|
||||
*/
|
||||
function up_walktree($dir, $callback=Null, $sort=0, $cb_argv=Null , $startdir=Null){
|
||||
if (is_null($callback)) return;
|
||||
if (is_null($startdir)) $startdir = $dir;
|
||||
$res = scandir($dir, $sort);
|
||||
foreach($res as $i=>$v){
|
||||
if ($v!="." && $v!=".."){
|
||||
$fn = $dir."/".$v;
|
||||
if ($sort==0) $callback($fn, $startdir, $cb_argv);
|
||||
if (is_dir($fn)) up_walktree($fn, $callback, $sort, $cb_argv, $startdir);
|
||||
if ($sort==1) $callback($fn, $startdir, $cb_argv);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function up_copy($fn, $dir){
|
||||
global $up_countfiles, $up_totalfiles, $up_lastp;
|
||||
$up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100);
|
||||
|
||||
if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return;
|
||||
$ddest = dirname(dirname(__file__));
|
||||
$fd = str_replace($dir, $ddest, $fn);
|
||||
|
||||
if (is_dir($fn) && !is_dir($fd)) {
|
||||
$re=mkdir($fd,0777,true);
|
||||
}
|
||||
if (!is_dir($fn)){
|
||||
$re=copy($fn, $fd);
|
||||
}
|
||||
|
||||
if ($re===false) {
|
||||
out("ERROR. Abort.");
|
||||
killme();
|
||||
}
|
||||
out("copy@Copy@$prc%");
|
||||
}
|
||||
|
||||
function up_ftp($fn, $dir, $argv){
|
||||
global $up_countfiles, $up_totalfiles, $up_lastp;
|
||||
$up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100);
|
||||
|
||||
if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return;
|
||||
|
||||
list($ddest, $conn_id) = $argv;
|
||||
$l = strlen($ddest)-1;
|
||||
if (substr($ddest,$l,1)=="/") $ddest = substr($ddest,0,$l);
|
||||
$fd = str_replace($dir, $ddest, $fn);
|
||||
|
||||
if (is_dir($fn)){
|
||||
if (ftp_nlist($conn_id, $fd)===false) {
|
||||
$ret = ftp_mkdir($conn_id, $fd);
|
||||
} else {
|
||||
$ret=true;
|
||||
}
|
||||
} else {
|
||||
$ret = ftp_put($conn_id, $fd, $fn, FTP_BINARY);
|
||||
}
|
||||
if (!$ret) {
|
||||
out("ERROR. Abort.");
|
||||
killme();
|
||||
}
|
||||
out("copy@Copy@$prc%");
|
||||
}
|
||||
|
||||
function up_rm($fn, $dir){
|
||||
if (is_dir($fn)){
|
||||
rmdir($fn);
|
||||
} else {
|
||||
unlink($fn);
|
||||
}
|
||||
}
|
||||
|
||||
function up_dlfile($url, $file) {
|
||||
$in = fopen ($url, "r");
|
||||
$out = fopen ($file, "w");
|
||||
|
||||
$fs = filesize($url);
|
||||
|
||||
|
||||
if (!$in || !$out) return false;
|
||||
|
||||
$s=0; $count=0;
|
||||
while (!feof ($in)) {
|
||||
$line = fgets ($in, 1024);
|
||||
fwrite( $out, $line);
|
||||
|
||||
$count++; $s += strlen($line);
|
||||
if ($count==50){
|
||||
$count=0;
|
||||
$sp=$s/1024.0; $ex="Kb";
|
||||
if ($sp>1024) { $sp=$sp/1024; $ex="Mb"; }
|
||||
if ($sp>1024) { $sp=$sp/1024; $ex="Gb"; }
|
||||
$sp = ((int)($sp*100))/100;
|
||||
out("dwl@Download@".$sp.$ex);
|
||||
}
|
||||
}
|
||||
fclose($in);
|
||||
return true;
|
||||
}
|
||||
|
||||
function doUpdate($remotefile, $ftpdata=false){
|
||||
global $up_totalfiles;
|
||||
|
||||
|
||||
$localtmpfile = tempnam("/tmp", "fk");
|
||||
out("dwl@Download@starting...");
|
||||
$rt= up_dlfile($remotefile, $localtmpfile);
|
||||
if ($rt==false || filesize($localtmpfile)==0){
|
||||
out("dwl@Download@ERROR.");
|
||||
unlink($localtmpfile);
|
||||
return;
|
||||
}
|
||||
out("dwl@Download@Ok.");
|
||||
|
||||
out("unzip@Unzip@");
|
||||
$tmpdirname = $localfile."ex";
|
||||
mkdir($tmpdirname);
|
||||
up_unzip($localtmpfile, $tmpdirname);
|
||||
$basedir = glob($tmpdirname."/*"); $basedir=$basedir[0];
|
||||
out ("unzip@Unzip@Ok.");
|
||||
|
||||
$up_totalfiles = up_count($basedir."/");
|
||||
|
||||
if (canWeWrite()){
|
||||
out("copy@Copy@");
|
||||
up_walktree($basedir, 'up_copy');
|
||||
}
|
||||
if ($ftpdata!==false && is_array($ftpdata) && $ftpdata['ftphost']!="" ){
|
||||
out("ftpcon@Connect to FTP@");
|
||||
$conn_id = ftp_connect($ftpdata['ftphost']);
|
||||
$login_result = ftp_login($conn_id, $ftpdata['ftpuser'], $ftpdata['ftppwd']);
|
||||
|
||||
if ((!$conn_id) || (!$login_result)) {
|
||||
out("ftpcon@Connect to FTP@FAILED");
|
||||
up_clean($tmpdirname, $localtmpfile);
|
||||
return;
|
||||
} else {
|
||||
out("ftpcon@Connect to FTP@Ok.");
|
||||
}
|
||||
out("copy@Copy@");
|
||||
up_walktree($basedir, 'up_ftp', 0, array( $ftpdata['ftppath'], $conn_id));
|
||||
|
||||
ftp_close($conn_id);
|
||||
}
|
||||
|
||||
up_clean($tmpdirname, $localtmpfile);
|
||||
|
||||
}
|
||||
|
||||
function up_clean($tmpdirname, $localtmpfile){
|
||||
out("clean@Clean up@");
|
||||
unlink($localtmpfile);
|
||||
up_walktree($tmpdirname, 'up_rm', 1);
|
||||
rmdir($tmpdirname);
|
||||
out("clean@Clean up@Ok.");
|
||||
}
|
||||
|
|
@ -139,15 +139,16 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) {
|
|||
poco_check($profile_url, $name, $network, $profile_photo, $about, $location, $gender, $keywords, $connect_url, $updated, $generation, $cid, $uid, $zcid);
|
||||
|
||||
// Update the Friendica contacts. Diaspora is doing it via a message. (See include/diaspora.php)
|
||||
if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != ""))
|
||||
q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s'
|
||||
WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'",
|
||||
dbesc($location),
|
||||
dbesc($about),
|
||||
dbesc($keywords),
|
||||
dbesc($gender),
|
||||
dbesc(normalise_link($profile_url)),
|
||||
dbesc(NETWORK_DFRN));
|
||||
// Deactivated because we now update Friendica contacts in dfrn.php
|
||||
//if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != ""))
|
||||
// q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s'
|
||||
// WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'",
|
||||
// dbesc($location),
|
||||
// dbesc($about),
|
||||
// dbesc($keywords),
|
||||
// dbesc($gender),
|
||||
// dbesc(normalise_link($profile_url)),
|
||||
// dbesc(NETWORK_DFRN));
|
||||
}
|
||||
logger("poco_load: loaded $total entries",LOGGER_DEBUG);
|
||||
|
||||
|
|
@ -227,6 +228,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
|
|||
$server_url = $x[0]["server_url"];
|
||||
$nick = $x[0]["nick"];
|
||||
$addr = $x[0]["addr"];
|
||||
$alias = $x[0]["alias"];
|
||||
$notify = $x[0]["notify"];
|
||||
} else {
|
||||
$created = "0000-00-00 00:00:00";
|
||||
$server_url = "";
|
||||
|
|
@ -234,6 +237,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
|
|||
$urlparts = parse_url($profile_url);
|
||||
$nick = end(explode("/", $urlparts["path"]));
|
||||
$addr = "";
|
||||
$alias = "";
|
||||
$notify = "";
|
||||
}
|
||||
|
||||
if ((($network == "") OR ($name == "") OR ($addr == "") OR ($profile_photo == "") OR ($server_url == "") OR $alternate)
|
||||
|
|
@ -246,6 +251,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
|
|||
$name = $data["name"];
|
||||
$nick = $data["nick"];
|
||||
$addr = $data["addr"];
|
||||
$alias = $data["alias"];
|
||||
$notify = $data["notify"];
|
||||
$profile_url = $data["url"];
|
||||
$profile_photo = $data["photo"];
|
||||
$server_url = $data["baseurl"];
|
||||
|
|
@ -283,76 +290,25 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
|
|||
|
||||
poco_check_server($server_url, $network);
|
||||
|
||||
if(count($x)) {
|
||||
$gcid = $x[0]['id'];
|
||||
$gcontact = array("url" => $profile_url,
|
||||
"addr" => $addr,
|
||||
"alias" => $alias,
|
||||
"name" => $name,
|
||||
"network" => $network,
|
||||
"photo" => $profile_photo,
|
||||
"about" => $about,
|
||||
"location" => $location,
|
||||
"gender" => $gender,
|
||||
"keywords" => $keywords,
|
||||
"server_url" => $server_url,
|
||||
"connect" => $connect_url,
|
||||
"notify" => $notify,
|
||||
"updated" => $updated,
|
||||
"generation" => $generation);
|
||||
|
||||
if (($location == "") AND ($x[0]['location'] != ""))
|
||||
$location = $x[0]['location'];
|
||||
$gcid = update_gcontact($gcontact);
|
||||
|
||||
if (($about == "") AND ($x[0]['about'] != ""))
|
||||
$about = $x[0]['about'];
|
||||
|
||||
if (($gender == "") AND ($x[0]['gender'] != ""))
|
||||
$gender = $x[0]['gender'];
|
||||
|
||||
if (($keywords == "") AND ($x[0]['keywords'] != ""))
|
||||
$keywords = $x[0]['keywords'];
|
||||
|
||||
if (($addr == "") AND ($x[0]['addr'] != ""))
|
||||
$addr = $x[0]['addr'];
|
||||
|
||||
if (($generation == 0) AND ($x[0]['generation'] > 0))
|
||||
$generation = $x[0]['generation'];
|
||||
|
||||
if($x[0]['name'] != $name || $x[0]['photo'] != $profile_photo || $x[0]['updated'] < $updated) {
|
||||
q("UPDATE `gcontact` SET `name` = '%s', `addr` = '%s', `network` = '%s', `photo` = '%s', `connect` = '%s', `url` = '%s', `server_url` = '%s',
|
||||
`updated` = '%s', `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s', `generation` = %d
|
||||
WHERE (`generation` >= %d OR `generation` = 0) AND `nurl` = '%s'",
|
||||
dbesc($name),
|
||||
dbesc($addr),
|
||||
dbesc($network),
|
||||
dbesc($profile_photo),
|
||||
dbesc($connect_url),
|
||||
dbesc($profile_url),
|
||||
dbesc($server_url),
|
||||
dbesc($updated),
|
||||
dbesc($location),
|
||||
dbesc($about),
|
||||
dbesc($keywords),
|
||||
dbesc($gender),
|
||||
intval($generation),
|
||||
intval($generation),
|
||||
dbesc(normalise_link($profile_url))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
q("INSERT INTO `gcontact` (`name`, `nick`, `addr`, `network`, `url`, `nurl`, `photo`, `connect`, `server_url`, `created`, `updated`, `location`, `about`, `keywords`, `gender`, `generation`)
|
||||
VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)",
|
||||
dbesc($name),
|
||||
dbesc($nick),
|
||||
dbesc($addr),
|
||||
dbesc($network),
|
||||
dbesc($profile_url),
|
||||
dbesc(normalise_link($profile_url)),
|
||||
dbesc($profile_photo),
|
||||
dbesc($connect_url),
|
||||
dbesc($server_url),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($updated),
|
||||
dbesc($location),
|
||||
dbesc($about),
|
||||
dbesc($keywords),
|
||||
dbesc($gender),
|
||||
intval($generation)
|
||||
);
|
||||
$x = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
|
||||
dbesc(normalise_link($profile_url))
|
||||
);
|
||||
if(count($x))
|
||||
$gcid = $x[0]['id'];
|
||||
}
|
||||
|
||||
if(! $gcid)
|
||||
if(!$gcid)
|
||||
return $gcid;
|
||||
|
||||
$r = q("SELECT * FROM `glink` WHERE `cid` = %d AND `uid` = %d AND `gcid` = %d AND `zcid` = %d LIMIT 1",
|
||||
|
|
@ -379,13 +335,6 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
|
|||
);
|
||||
}
|
||||
|
||||
// For unknown reasons there are sometimes duplicates
|
||||
q("DELETE FROM `gcontact` WHERE `nurl` = '%s' AND `id` != %d AND
|
||||
NOT EXISTS (SELECT `gcid` FROM `glink` WHERE `gcid` = `gcontact`.`id`)",
|
||||
dbesc(normalise_link($profile_url)),
|
||||
intval($gcid)
|
||||
);
|
||||
|
||||
return $gcid;
|
||||
}
|
||||
|
||||
|
|
@ -698,6 +647,10 @@ function poco_to_boolean($val) {
|
|||
|
||||
function poco_check_server($server_url, $network = "", $force = false) {
|
||||
|
||||
// Unify the server address
|
||||
$server_url = trim($server_url, "/");
|
||||
$server_url = str_replace("/index.php", "", $server_url);
|
||||
|
||||
if ($server_url == "")
|
||||
return false;
|
||||
|
||||
|
|
@ -769,17 +722,23 @@ function poco_check_server($server_url, $network = "", $force = false) {
|
|||
// Test for Diaspora
|
||||
$serverret = z_fetch_url($server_url);
|
||||
|
||||
$lines = explode("\n",$serverret["header"]);
|
||||
if(count($lines))
|
||||
foreach($lines as $line) {
|
||||
$line = trim($line);
|
||||
if(stristr($line,'X-Diaspora-Version:')) {
|
||||
$platform = "Diaspora";
|
||||
$version = trim(str_replace("X-Diaspora-Version:", "", $line));
|
||||
$version = trim(str_replace("x-diaspora-version:", "", $version));
|
||||
$network = NETWORK_DIASPORA;
|
||||
if (!$serverret["success"] OR ($serverret["body"] == ""))
|
||||
$failure = true;
|
||||
else {
|
||||
$lines = explode("\n",$serverret["header"]);
|
||||
if(count($lines))
|
||||
foreach($lines as $line) {
|
||||
$line = trim($line);
|
||||
if(stristr($line,'X-Diaspora-Version:')) {
|
||||
$platform = "Diaspora";
|
||||
$version = trim(str_replace("X-Diaspora-Version:", "", $line));
|
||||
$version = trim(str_replace("x-diaspora-version:", "", $version));
|
||||
$network = NETWORK_DIASPORA;
|
||||
$versionparts = explode("-", $version);
|
||||
$version = $versionparts[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$failure) {
|
||||
|
|
@ -1290,18 +1249,30 @@ function poco_discover_federation() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Discover Friendica, Hubzilla and Diaspora servers
|
||||
$serverdata = fetch_url("http://the-federation.info/pods.json");
|
||||
|
||||
if (!$serverdata)
|
||||
return;
|
||||
if ($serverdata) {
|
||||
$servers = json_decode($serverdata);
|
||||
|
||||
$servers = json_decode($serverdata);
|
||||
foreach($servers->pods AS $server)
|
||||
poco_check_server("https://".$server->host);
|
||||
}
|
||||
|
||||
foreach($servers->pods AS $server)
|
||||
poco_check_server("https://".$server->host);
|
||||
// Discover GNU Social Servers
|
||||
if (!get_config('system','ostatus_disabled')) {
|
||||
$serverdata = "http://gstools.org/api/get_open_instances/";
|
||||
|
||||
$result = z_fetch_url($serverdata);
|
||||
if ($result["success"]) {
|
||||
$servers = json_decode($result["body"]);
|
||||
|
||||
foreach($servers->data AS $server)
|
||||
poco_check_server($server->instance_address);
|
||||
}
|
||||
}
|
||||
|
||||
set_config('poco','last_federation_discovery', time());
|
||||
|
||||
}
|
||||
|
||||
function poco_discover($complete = false) {
|
||||
|
|
@ -1481,4 +1452,214 @@ function poco_discover_server($data, $default_generation = 0) {
|
|||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetch the gcontact id, add an entry if not existed
|
||||
*
|
||||
* @param arr $contact contact array
|
||||
* @return bool|int Returns false if not found, integer if contact was found
|
||||
*/
|
||||
function get_gcontact_id($contact) {
|
||||
|
||||
$gcontact_id = 0;
|
||||
|
||||
if ($contact["network"] == NETWORK_STATUSNET)
|
||||
$contact["network"] = NETWORK_OSTATUS;
|
||||
|
||||
$r = q("SELECT `id` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 2",
|
||||
dbesc(normalise_link($contact["url"])));
|
||||
|
||||
if ($r)
|
||||
$gcontact_id = $r[0]["id"];
|
||||
else {
|
||||
q("INSERT INTO `gcontact` (`name`, `nick`, `addr` , `network`, `url`, `nurl`, `photo`, `created`, `updated`, `location`, `about`, `generation`)
|
||||
VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)",
|
||||
dbesc($contact["name"]),
|
||||
dbesc($contact["nick"]),
|
||||
dbesc($contact["addr"]),
|
||||
dbesc($contact["network"]),
|
||||
dbesc($contact["url"]),
|
||||
dbesc(normalise_link($contact["url"])),
|
||||
dbesc($contact["photo"]),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc($contact["location"]),
|
||||
dbesc($contact["about"]),
|
||||
intval($contact["generation"])
|
||||
);
|
||||
|
||||
$r = q("SELECT `id` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 2",
|
||||
dbesc(normalise_link($contact["url"])));
|
||||
|
||||
if ($r)
|
||||
$gcontact_id = $r[0]["id"];
|
||||
}
|
||||
|
||||
if ((count($r) > 1) AND ($gcontact_id > 0) AND ($contact["url"] != ""))
|
||||
q("DELETE FROM `gcontact` WHERE `nurl` = '%s' AND `id` != %d",
|
||||
dbesc(normalise_link($contact["url"])),
|
||||
intval($gcontact_id));
|
||||
|
||||
return $gcontact_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the gcontact table from a given array
|
||||
*
|
||||
* @param arr $contact contact array
|
||||
* @return bool|int Returns false if not found, integer if contact was found
|
||||
*/
|
||||
function update_gcontact($contact) {
|
||||
|
||||
/// @todo update contact table as well
|
||||
|
||||
$gcontact_id = get_gcontact_id($contact);
|
||||
|
||||
if (!$gcontact_id)
|
||||
return false;
|
||||
|
||||
$r = q("SELECT `name`, `nick`, `photo`, `location`, `about`, `addr`, `generation`, `birthday`, `gender`, `keywords`,
|
||||
`hide`, `nsfw`, `network`, `alias`, `notify`, `server_url`, `connect`, `updated`, `url`
|
||||
FROM `gcontact` WHERE `id` = %d LIMIT 1",
|
||||
intval($gcontact_id));
|
||||
|
||||
// Get all field names
|
||||
$fields = array();
|
||||
foreach ($r[0] AS $field => $data)
|
||||
$fields[$field] = $data;
|
||||
|
||||
unset($fields["url"]);
|
||||
unset($fields["updated"]);
|
||||
|
||||
// assign all unassigned fields from the database entry
|
||||
foreach ($fields AS $field => $data)
|
||||
if (!isset($contact[$field]))
|
||||
$contact[$field] = $r[0][$field];
|
||||
|
||||
if ($contact["network"] == NETWORK_STATUSNET)
|
||||
$contact["network"] = NETWORK_OSTATUS;
|
||||
|
||||
if (!isset($contact["updated"]))
|
||||
$contact["updated"] = datetime_convert();
|
||||
|
||||
// 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"] < $r[0]["generation"])
|
||||
$update = true;
|
||||
|
||||
if ($update) {
|
||||
q("UPDATE `gcontact` SET `photo` = '%s', `name` = '%s', `nick` = '%s', `addr` = '%s', `network` = '%s',
|
||||
`birthday` = '%s', `gender` = '%s', `keywords` = '%s', `hide` = %d, `nsfw` = %d,
|
||||
`alias` = '%s', `notify` = '%s', `url` = '%s',
|
||||
`location` = '%s', `about` = '%s', `generation` = %d, `updated` = '%s',
|
||||
`server_url` = '%s', `connect` = '%s'
|
||||
WHERE `nurl` = '%s' AND (`generation` = 0 OR `generation` >= %d)",
|
||||
dbesc($contact["photo"]), dbesc($contact["name"]), dbesc($contact["nick"]),
|
||||
dbesc($contact["addr"]), dbesc($contact["network"]), dbesc($contact["birthday"]),
|
||||
dbesc($contact["gender"]), dbesc($contact["keywords"]), intval($contact["hide"]),
|
||||
intval($contact["nsfw"]), dbesc($contact["alias"]), dbesc($contact["notify"]),
|
||||
dbesc($contact["url"]), dbesc($contact["location"]), dbesc($contact["about"]),
|
||||
intval($contact["generation"]), dbesc($contact["updated"]),
|
||||
dbesc($contact["server_url"]), dbesc($contact["connect"]),
|
||||
dbesc(normalise_link($contact["url"])), intval($contact["generation"]));
|
||||
}
|
||||
|
||||
return $gcontact_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the gcontact entry from probe
|
||||
*
|
||||
* @param str $url profile link
|
||||
*/
|
||||
function update_gcontact_from_probe($url) {
|
||||
$data = probe_url($url);
|
||||
|
||||
if ($data["network"] != NETWORK_PHANTOM)
|
||||
update_gcontact($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetches users of given GNU Social server
|
||||
*
|
||||
* If the "Statistics" plugin is enabled (See http://gstools.org/ for details) we query user data with this.
|
||||
*
|
||||
* @param str $server Server address
|
||||
*/
|
||||
function gs_fetch_users($server) {
|
||||
|
||||
logger("Fetching users from GNU Social server ".$server, LOGGER_DEBUG);
|
||||
|
||||
$a = get_app();
|
||||
|
||||
$url = $server."/main/statistics";
|
||||
|
||||
$result = z_fetch_url($url);
|
||||
if (!$result["success"])
|
||||
return false;
|
||||
|
||||
$statistics = json_decode($result["body"]);
|
||||
|
||||
if (is_object($statistics->config)) {
|
||||
if ($statistics->config->instance_with_ssl)
|
||||
$server = "https://";
|
||||
else
|
||||
$server = "http://";
|
||||
|
||||
$server .= $statistics->config->instance_address;
|
||||
|
||||
$hostname = $statistics->config->instance_address;
|
||||
} else {
|
||||
if ($statistics->instance_with_ssl)
|
||||
$server = "https://";
|
||||
else
|
||||
$server = "http://";
|
||||
|
||||
$server .= $statistics->instance_address;
|
||||
|
||||
$hostname = $statistics->instance_address;
|
||||
}
|
||||
|
||||
if (is_object($statistics->users))
|
||||
foreach ($statistics->users AS $nick => $user) {
|
||||
$profile_url = $server."/".$user->nickname;
|
||||
|
||||
$contact = array("url" => $profile_url,
|
||||
"name" => $user->fullname,
|
||||
"addr" => $user->nickname."@".$hostname,
|
||||
"nick" => $user->nickname,
|
||||
"about" => $user->bio,
|
||||
"network" => NETWORK_OSTATUS,
|
||||
"photo" => $a->get_baseurl()."/images/person-175.jpg");
|
||||
get_gcontact_id($contact);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Asking GNU Social server on a regular base for their user data
|
||||
*
|
||||
*/
|
||||
function gs_discover() {
|
||||
|
||||
$requery_days = intval(get_config("system", "poco_requery_days"));
|
||||
|
||||
$last_update = date("c", time() - (60 * 60 * 24 * $requery_days));
|
||||
|
||||
$r = q("SELECT `nurl`, `url` FROM `gserver` WHERE `last_contact` >= `last_failure` AND `network` = '%s' AND `last_poco_query` < '%s' ORDER BY RAND() LIMIT 5",
|
||||
dbesc(NETWORK_OSTATUS), dbesc($last_update));
|
||||
|
||||
if (!$r)
|
||||
return;
|
||||
|
||||
foreach ($r AS $server) {
|
||||
gs_fetch_users($server["url"]);
|
||||
q("UPDATE `gserver` SET `last_poco_query` = '%s' WHERE `nurl` = '%s'", dbesc(datetime_convert()), dbesc($server["nurl"]));
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
102
include/text.php
102
include/text.php
|
|
@ -20,10 +20,10 @@ function replace_macros($s,$r) {
|
|||
$stamp1 = microtime(true);
|
||||
|
||||
$a = get_app();
|
||||
|
||||
|
||||
// pass $baseurl to all templates
|
||||
$r['$baseurl'] = z_root();
|
||||
|
||||
$r['$baseurl'] = $a->get_baseurl();
|
||||
|
||||
|
||||
$t = $a->template_engine();
|
||||
try {
|
||||
|
|
@ -829,35 +829,6 @@ function qp($s) {
|
|||
return str_replace ("%","=",rawurlencode($s));
|
||||
}}
|
||||
|
||||
|
||||
|
||||
if(! function_exists('get_mentions')) {
|
||||
/**
|
||||
* @param array $item
|
||||
* @return string html for mentions #FIXME: remove html
|
||||
*/
|
||||
function get_mentions($item) {
|
||||
$o = '';
|
||||
if(! strlen($item['tag']))
|
||||
return $o;
|
||||
|
||||
$arr = explode(',',$item['tag']);
|
||||
foreach($arr as $x) {
|
||||
$matches = null;
|
||||
if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
|
||||
$o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
|
||||
$o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$item['private']) {
|
||||
$o .= "\t\t".'<link rel="ostatus:attention" href="http://activityschema.org/collection/public"/>'."\r\n";
|
||||
$o .= "\t\t".'<link rel="mentioned" href="http://activityschema.org/collection/public"/>'."\r\n";
|
||||
}
|
||||
|
||||
return $o;
|
||||
}}
|
||||
|
||||
if(! function_exists('contact_block')) {
|
||||
/**
|
||||
* Get html for contact block.
|
||||
|
|
@ -895,9 +866,9 @@ function contact_block() {
|
|||
$micropro = Null;
|
||||
|
||||
} else {
|
||||
$r = q("SELECT * FROM `contact`
|
||||
WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
|
||||
AND `hidden` = 0 AND `archive` = 0
|
||||
$r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `micro`, `network` FROM `contact`
|
||||
WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending`
|
||||
AND NOT `hidden` AND NOT `archive`
|
||||
AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
|
||||
intval($a->profile['uid']),
|
||||
dbesc(NETWORK_DFRN),
|
||||
|
|
@ -1415,7 +1386,14 @@ function prepare_body(&$item,$attach = false, $preview = false) {
|
|||
$item['hashtags'] = $hashtags;
|
||||
$item['mentions'] = $mentions;
|
||||
|
||||
put_item_in_cache($item, true);
|
||||
// Update the cached values if there is no "zrl=..." on the links
|
||||
$update = (!local_user() and !remote_user() and ($item["uid"] == 0));
|
||||
|
||||
// Or update it if the current viewer is the intented viewer
|
||||
if (($item["uid"] == local_user()) AND ($item["uid"] != 0))
|
||||
$update = true;
|
||||
|
||||
put_item_in_cache($item, $update);
|
||||
$s = $item["rendered-html"];
|
||||
|
||||
$prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
|
||||
|
|
@ -1526,7 +1504,7 @@ function prepare_body(&$item,$attach = false, $preview = false) {
|
|||
|
||||
$pos = strpos($s, $spoilersearch);
|
||||
$rnd = random_string(8);
|
||||
$spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
|
||||
$spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
|
||||
'<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
|
||||
$s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
|
||||
}
|
||||
|
|
@ -1538,7 +1516,7 @@ function prepare_body(&$item,$attach = false, $preview = false) {
|
|||
|
||||
$pos = strpos($s, $authorsearch);
|
||||
$rnd = random_string(8);
|
||||
$authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
|
||||
$authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" class="author-wrap fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
|
||||
'<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
|
||||
$s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
|
||||
}
|
||||
|
|
@ -1650,54 +1628,6 @@ function get_cats_and_terms($item) {
|
|||
return array($categories, $folders);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(! function_exists('feed_hublinks')) {
|
||||
/**
|
||||
* return atom link elements for all of our hubs
|
||||
* @return string hub link xml elements
|
||||
*/
|
||||
function feed_hublinks() {
|
||||
$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 = z_root() . '/pubsubhubbub';
|
||||
$hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $hubxml;
|
||||
}}
|
||||
|
||||
|
||||
if(! function_exists('feed_salmonlinks')) {
|
||||
/**
|
||||
* return atom link elements for salmon endpoints
|
||||
* @param string $nick user nickname
|
||||
* @return string salmon link xml elements
|
||||
*/
|
||||
function feed_salmonlinks($nick) {
|
||||
|
||||
$a = get_app();
|
||||
|
||||
$salmon = '<link rel="salmon" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
|
||||
|
||||
// old style links that status.net still needed as of 12/2010
|
||||
|
||||
$salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
|
||||
$salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
|
||||
return $salmon;
|
||||
}}
|
||||
|
||||
if(! function_exists('get_plink')) {
|
||||
/**
|
||||
* get private link for item
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
function add_thread($itemid, $onlyshadow = false) {
|
||||
$items = q("SELECT `uid`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`,
|
||||
$items = q("SELECT `uid`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`, `gcontact-id`,
|
||||
`deleted`, `origin`, `forum_mode`, `mention`, `network` FROM `item` WHERE `id` = %d AND (`parent` = %d OR `parent` = 0) LIMIT 1", intval($itemid), intval($itemid));
|
||||
|
||||
if (!$items)
|
||||
|
|
@ -66,6 +66,7 @@ function add_thread($itemid, $onlyshadow = false) {
|
|||
|
||||
unset($item[0]['id']);
|
||||
$item[0]['uid'] = 0;
|
||||
$item[0]['origin'] = 0;
|
||||
$item[0]['contact-id'] = get_contact($item[0]['author-link'], 0);
|
||||
$public_shadow = item_store($item[0], false, false, true);
|
||||
|
||||
|
|
@ -111,8 +112,8 @@ function update_thread_uri($itemuri, $uid) {
|
|||
}
|
||||
|
||||
function update_thread($itemid, $setmention = false) {
|
||||
$items = q("SELECT `uid`, `guid`, `title`, `body`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`,
|
||||
`deleted`, `origin`, `forum_mode`, `network` FROM `item` WHERE `id` = %d AND (`parent` = %d OR `parent` = 0) LIMIT 1", intval($itemid), intval($itemid));
|
||||
$items = q("SELECT `uid`, `guid`, `title`, `body`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`, `gcontact-id`,
|
||||
`deleted`, `origin`, `forum_mode`, `network`, `rendered-html`, `rendered-hash` FROM `item` WHERE `id` = %d AND (`parent` = %d OR `parent` = 0) LIMIT 1", intval($itemid), intval($itemid));
|
||||
|
||||
if (!$items)
|
||||
return;
|
||||
|
|
@ -125,7 +126,7 @@ function update_thread($itemid, $setmention = false) {
|
|||
$sql = "";
|
||||
|
||||
foreach ($item AS $field => $data)
|
||||
if (!in_array($field, array("guid", "title", "body"))) {
|
||||
if (!in_array($field, array("guid", "title", "body", "rendered-html", "rendered-hash"))) {
|
||||
if ($sql != "")
|
||||
$sql .= ", ";
|
||||
|
||||
|
|
@ -142,9 +143,11 @@ function update_thread($itemid, $setmention = false) {
|
|||
if (!$items)
|
||||
return;
|
||||
|
||||
$result = q("UPDATE `item` SET `title` = '%s', `body` = '%s' WHERE `id` = %d",
|
||||
$result = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
|
||||
dbesc($item["title"]),
|
||||
dbesc($item["body"]),
|
||||
dbesc($item["rendered-html"]),
|
||||
dbesc($item["rendered-hash"]),
|
||||
intval($items[0]["id"])
|
||||
);
|
||||
logger("Updating public shadow for post ".$items[0]["id"]." - guid ".$item["guid"]." Result: ".print_r($result, true), LOGGER_DEBUG);
|
||||
|
|
|
|||
10
index.php
10
index.php
|
|
@ -407,15 +407,6 @@ if(x($_SESSION,'sysmsg_info')) {
|
|||
call_hooks('page_end', $a->page['content']);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Add a place for the pause/resume Ajax indicator
|
||||
*
|
||||
*/
|
||||
|
||||
$a->page['content'] .= '<div id="pause"></div>';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Add the navigation (menu) template
|
||||
|
|
@ -556,6 +547,7 @@ EOT;
|
|||
$page = $a->page;
|
||||
$profile = $a->profile;
|
||||
|
||||
header("X-Friendica-Version: ".FRIENDICA_VERSION);
|
||||
header("Content-type: text/html; charset=utf-8");
|
||||
|
||||
|
||||
|
|
|
|||
215
js/acl.js
215
js/acl.js
|
|
@ -1,49 +1,56 @@
|
|||
function ACL(backend_url, preset, automention){
|
||||
that = this;
|
||||
function ACL(backend_url, preset, automention, is_mobile){
|
||||
|
||||
that.url = backend_url;
|
||||
that.automention = automention;
|
||||
this.url = backend_url;
|
||||
this.automention = automention;
|
||||
this.is_mobile = is_mobile;
|
||||
|
||||
that.kp_timer = null;
|
||||
|
||||
this.kp_timer = null;
|
||||
|
||||
if (preset==undefined) preset = [];
|
||||
that.allow_cid = (preset[0] || []);
|
||||
that.allow_gid = (preset[1] || []);
|
||||
that.deny_cid = (preset[2] || []);
|
||||
that.deny_gid = (preset[3] || []);
|
||||
that.group_uids = [];
|
||||
that.nw = 4; //items per row. should be calulated from #acl-list.width
|
||||
|
||||
that.list_content = $("#acl-list-content");
|
||||
that.item_tpl = unescape($(".acl-list-item[rel=acl-template]").html());
|
||||
that.showall = $("#acl-showall");
|
||||
this.allow_cid = (preset[0] || []);
|
||||
this.allow_gid = (preset[1] || []);
|
||||
this.deny_cid = (preset[2] || []);
|
||||
this.deny_gid = (preset[3] || []);
|
||||
this.group_uids = [];
|
||||
|
||||
if (preset.length==0) that.showall.addClass("selected");
|
||||
if (this.is_mobile) {
|
||||
this.nw = 1;
|
||||
} else {
|
||||
this.nw = 4;
|
||||
}
|
||||
|
||||
|
||||
this.list_content = $("#acl-list-content");
|
||||
this.item_tpl = unescape($(".acl-list-item[rel=acl-template]").html());
|
||||
this.showall = $("#acl-showall");
|
||||
|
||||
if (preset.length==0) this.showall.addClass("selected");
|
||||
|
||||
/*events*/
|
||||
that.showall.click(that.on_showall);
|
||||
$(document).on("click", ".acl-button-show", that.on_button_show);
|
||||
$(document).on("click", ".acl-button-hide", that.on_button_hide);
|
||||
$("#acl-search").keypress(that.on_search);
|
||||
$("#acl-wrapper").parents("form").submit(that.on_submit);
|
||||
this.showall.click(this.on_showall.bind(this));
|
||||
$(document).on("click", ".acl-button-show", this.on_button_show.bind(this));
|
||||
$(document).on("click", ".acl-button-hide", this.on_button_hide.bind(this));
|
||||
$("#acl-search").keypress(this.on_search.bind(this));
|
||||
$("#acl-wrapper").parents("form").submit(this.on_submit.bind(this));
|
||||
|
||||
/* add/remove mentions */
|
||||
that.element = $("#profile-jot-text");
|
||||
that.htmlelm = that.element.get()[0];
|
||||
this.element = $("#profile-jot-text");
|
||||
this.htmlelm = this.element.get()[0];
|
||||
|
||||
/* startup! */
|
||||
that.get(0,100);
|
||||
this.get(0,100);
|
||||
}
|
||||
|
||||
ACL.prototype.remove_mention = function(id) {
|
||||
if (!that.automention) return;
|
||||
var nick = that.data[id].nick;
|
||||
if (!this.automention) return;
|
||||
var nick = this.data[id].nick;
|
||||
var searchText = "@"+nick+"+"+id+" ";
|
||||
if (tinyMCE.activeEditor===null) {
|
||||
start = that.element.val().indexOf(searchText);
|
||||
start = this.element.val().indexOf(searchText);
|
||||
if ( start<0) return;
|
||||
end = start+searchText.length;
|
||||
that.element.setSelection(start,end).replaceSelectedText('').collapseSelection(false);
|
||||
this.element.setSelection(start,end).replaceSelectedText('').collapseSelection(false);
|
||||
} else {
|
||||
start = tinyMCE.activeEditor.getContent({format : 'raw'}).search( searchText );
|
||||
if ( start<0 ) return;
|
||||
|
|
@ -54,12 +61,12 @@ ACL.prototype.remove_mention = function(id) {
|
|||
}
|
||||
|
||||
ACL.prototype.add_mention = function(id) {
|
||||
if (!that.automention) return;
|
||||
var nick = that.data[id].nick;
|
||||
if (!this.automention) return;
|
||||
var nick = this.data[id].nick;
|
||||
var searchText = "@"+nick+"+"+id+" ";
|
||||
if (tinyMCE.activeEditor===null) {
|
||||
if ( that.element.val().indexOf( searchText) >= 0 ) return;
|
||||
that.element.val( searchText + that.element.val() );
|
||||
if ( this.element.val().indexOf( searchText) >= 0 ) return;
|
||||
this.element.val( searchText + this.element.val() );
|
||||
} else {
|
||||
if ( tinyMCE.activeEditor.getContent({format : 'raw'}).search(searchText) >= 0 ) return;
|
||||
tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'dummy', {}, searchText);
|
||||
|
|
@ -68,46 +75,46 @@ ACL.prototype.add_mention = function(id) {
|
|||
|
||||
ACL.prototype.on_submit = function(){
|
||||
aclfileds = $("#acl-fields").html("");
|
||||
$(that.allow_gid).each(function(i,v){
|
||||
$(this.allow_gid).each(function(i,v){
|
||||
aclfileds.append("<input type='hidden' name='group_allow[]' value='"+v+"'>");
|
||||
});
|
||||
$(that.allow_cid).each(function(i,v){
|
||||
$(this.allow_cid).each(function(i,v){
|
||||
aclfileds.append("<input type='hidden' name='contact_allow[]' value='"+v+"'>");
|
||||
});
|
||||
$(that.deny_gid).each(function(i,v){
|
||||
$(this.deny_gid).each(function(i,v){
|
||||
aclfileds.append("<input type='hidden' name='group_deny[]' value='"+v+"'>");
|
||||
});
|
||||
$(that.deny_cid).each(function(i,v){
|
||||
$(this.deny_cid).each(function(i,v){
|
||||
aclfileds.append("<input type='hidden' name='contact_deny[]' value='"+v+"'>");
|
||||
});
|
||||
}
|
||||
|
||||
ACL.prototype.search = function(){
|
||||
var srcstr = $("#acl-search").val();
|
||||
that.list_content.html("");
|
||||
that.get(0,100, srcstr);
|
||||
this.list_content.html("");
|
||||
this.get(0,100, srcstr);
|
||||
}
|
||||
|
||||
ACL.prototype.on_search = function(event){
|
||||
if (that.kp_timer) clearTimeout(that.kp_timer);
|
||||
that.kp_timer = setTimeout( that.search, 1000);
|
||||
if (this.kp_timer) clearTimeout(this.kp_timer);
|
||||
this.kp_timer = setTimeout( this.search.bind(this), 1000);
|
||||
}
|
||||
|
||||
ACL.prototype.on_showall = function(event){
|
||||
event.preventDefault()
|
||||
event.stopPropagation();
|
||||
|
||||
if (that.showall.hasClass("selected")){
|
||||
if (this.showall.hasClass("selected")){
|
||||
return false;
|
||||
}
|
||||
that.showall.addClass("selected");
|
||||
this.showall.addClass("selected");
|
||||
|
||||
that.allow_cid = [];
|
||||
that.allow_gid = [];
|
||||
that.deny_cid = [];
|
||||
that.deny_gid = [];
|
||||
this.allow_cid = [];
|
||||
this.allow_gid = [];
|
||||
this.deny_cid = [];
|
||||
this.deny_gid = [];
|
||||
|
||||
that.update_view();
|
||||
this.update_view();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -116,12 +123,8 @@ ACL.prototype.on_button_show = function(event){
|
|||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
event.stopPropagation();
|
||||
|
||||
/*that.showall.removeClass("selected");
|
||||
$(this).siblings(".acl-button-hide").removeClass("selected");
|
||||
$(this).toggleClass("selected");*/
|
||||
|
||||
that.set_allow($(this).parent().attr('id'));
|
||||
|
||||
this.set_allow($(event.target).parent().attr('id'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -130,11 +133,7 @@ ACL.prototype.on_button_hide = function(event){
|
|||
event.stopImmediatePropagation()
|
||||
event.stopPropagation();
|
||||
|
||||
/*that.showall.removeClass("selected");
|
||||
$(this).siblings(".acl-button-show").removeClass("selected");
|
||||
$(this).toggleClass("selected");*/
|
||||
|
||||
that.set_deny($(this).parent().attr('id'));
|
||||
this.set_deny($(event.target).parent().attr('id'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -145,25 +144,25 @@ ACL.prototype.set_allow = function(itemid){
|
|||
|
||||
switch(type){
|
||||
case "g":
|
||||
if (that.allow_gid.indexOf(id)<0){
|
||||
that.allow_gid.push(id)
|
||||
if (this.allow_gid.indexOf(id)<0){
|
||||
this.allow_gid.push(id)
|
||||
}else {
|
||||
that.allow_gid.remove(id);
|
||||
this.allow_gid.remove(id);
|
||||
}
|
||||
if (that.deny_gid.indexOf(id)>=0) that.deny_gid.remove(id);
|
||||
if (this.deny_gid.indexOf(id)>=0) this.deny_gid.remove(id);
|
||||
break;
|
||||
case "c":
|
||||
if (that.allow_cid.indexOf(id)<0){
|
||||
that.allow_cid.push(id)
|
||||
if (that.data[id].forum=="1") that.add_mention(id);
|
||||
if (this.allow_cid.indexOf(id)<0){
|
||||
this.allow_cid.push(id)
|
||||
if (this.data[id].forum=="1") this.add_mention(id);
|
||||
} else {
|
||||
that.allow_cid.remove(id);
|
||||
if (that.data[id].forum=="1") that.remove_mention(id);
|
||||
this.allow_cid.remove(id);
|
||||
if (this.data[id].forum=="1") this.remove_mention(id);
|
||||
}
|
||||
if (that.deny_cid.indexOf(id)>=0) that.deny_cid.remove(id);
|
||||
if (this.deny_cid.indexOf(id)>=0) this.deny_cid.remove(id);
|
||||
break;
|
||||
}
|
||||
that.update_view();
|
||||
this.update_view();
|
||||
}
|
||||
|
||||
ACL.prototype.set_deny = function(itemid){
|
||||
|
|
@ -172,34 +171,34 @@ ACL.prototype.set_deny = function(itemid){
|
|||
|
||||
switch(type){
|
||||
case "g":
|
||||
if (that.deny_gid.indexOf(id)<0){
|
||||
that.deny_gid.push(id)
|
||||
if (this.deny_gid.indexOf(id)<0){
|
||||
this.deny_gid.push(id)
|
||||
} else {
|
||||
that.deny_gid.remove(id);
|
||||
this.deny_gid.remove(id);
|
||||
}
|
||||
if (that.allow_gid.indexOf(id)>=0) that.allow_gid.remove(id);
|
||||
if (this.allow_gid.indexOf(id)>=0) this.allow_gid.remove(id);
|
||||
break;
|
||||
case "c":
|
||||
if (that.data[id].forum=="1") that.remove_mention(id);
|
||||
if (that.deny_cid.indexOf(id)<0){
|
||||
that.deny_cid.push(id)
|
||||
if (this.data[id].forum=="1") this.remove_mention(id);
|
||||
if (this.deny_cid.indexOf(id)<0){
|
||||
this.deny_cid.push(id)
|
||||
} else {
|
||||
that.deny_cid.remove(id);
|
||||
this.deny_cid.remove(id);
|
||||
}
|
||||
if (that.allow_cid.indexOf(id)>=0) that.allow_cid.remove(id);
|
||||
if (this.allow_cid.indexOf(id)>=0) this.allow_cid.remove(id);
|
||||
break;
|
||||
}
|
||||
that.update_view();
|
||||
this.update_view();
|
||||
}
|
||||
|
||||
ACL.prototype.is_show_all = function() {
|
||||
return (that.allow_gid.length==0 && that.allow_cid.length==0 &&
|
||||
that.deny_gid.length==0 && that.deny_cid.length==0);
|
||||
return (this.allow_gid.length==0 && this.allow_cid.length==0 &&
|
||||
this.deny_gid.length==0 && this.deny_cid.length==0);
|
||||
}
|
||||
|
||||
ACL.prototype.update_view = function(){
|
||||
if (this.is_show_all()){
|
||||
that.showall.addClass("selected");
|
||||
this.showall.addClass("selected");
|
||||
/* jot acl */
|
||||
$('#jot-perms-icon').removeClass('lock').addClass('unlock');
|
||||
$('#jot-public').show();
|
||||
|
|
@ -209,7 +208,7 @@ ACL.prototype.update_view = function(){
|
|||
}
|
||||
|
||||
} else {
|
||||
that.showall.removeClass("selected");
|
||||
this.showall.removeClass("selected");
|
||||
/* jot acl */
|
||||
$('#jot-perms-icon').removeClass('unlock').addClass('lock');
|
||||
$('#jot-public').hide();
|
||||
|
|
@ -220,29 +219,29 @@ ACL.prototype.update_view = function(){
|
|||
$(this).removeClass("groupshow grouphide");
|
||||
});
|
||||
|
||||
$("#acl-list-content .acl-list-item").each(function(){
|
||||
itemid = $(this).attr('id');
|
||||
$("#acl-list-content .acl-list-item").each(function(index, element){
|
||||
itemid = $(element).attr('id');
|
||||
type = itemid[0];
|
||||
id = parseInt(itemid.substr(1));
|
||||
|
||||
btshow = $(this).children(".acl-button-show").removeClass("selected");
|
||||
bthide = $(this).children(".acl-button-hide").removeClass("selected");
|
||||
btshow = $(element).children(".acl-button-show").removeClass("selected");
|
||||
bthide = $(element).children(".acl-button-hide").removeClass("selected");
|
||||
|
||||
switch(type){
|
||||
case "g":
|
||||
var uclass = "";
|
||||
if (that.allow_gid.indexOf(id)>=0){
|
||||
if (this.allow_gid.indexOf(id)>=0){
|
||||
btshow.addClass("selected");
|
||||
bthide.removeClass("selected");
|
||||
uclass="groupshow";
|
||||
}
|
||||
if (that.deny_gid.indexOf(id)>=0){
|
||||
if (this.deny_gid.indexOf(id)>=0){
|
||||
btshow.removeClass("selected");
|
||||
bthide.addClass("selected");
|
||||
uclass="grouphide";
|
||||
}
|
||||
|
||||
$(that.group_uids[id]).each(function(i,v) {
|
||||
$(this.group_uids[id]).each(function(i,v) {
|
||||
if(uclass == "grouphide")
|
||||
$("#c"+v).removeClass("groupshow");
|
||||
if(uclass != "") {
|
||||
|
|
@ -257,17 +256,17 @@ ACL.prototype.update_view = function(){
|
|||
|
||||
break;
|
||||
case "c":
|
||||
if (that.allow_cid.indexOf(id)>=0){
|
||||
if (this.allow_cid.indexOf(id)>=0){
|
||||
btshow.addClass("selected");
|
||||
bthide.removeClass("selected");
|
||||
}
|
||||
if (that.deny_cid.indexOf(id)>=0){
|
||||
if (this.deny_cid.indexOf(id)>=0){
|
||||
btshow.removeClass("selected");
|
||||
bthide.addClass("selected");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -281,30 +280,30 @@ ACL.prototype.get = function(start,count, search){
|
|||
|
||||
$.ajax({
|
||||
type:'POST',
|
||||
url: that.url,
|
||||
url: this.url,
|
||||
data: postdata,
|
||||
dataType: 'json',
|
||||
success:that.populate
|
||||
success:this.populate.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
ACL.prototype.populate = function(data){
|
||||
var height = Math.ceil(data.tot / that.nw) * 42;
|
||||
that.list_content.height(height);
|
||||
that.data = {};
|
||||
$(data.items).each(function(){
|
||||
html = "<div class='acl-list-item {4} {5} type{2}' title='{6}' id='{2}{3}'>"+that.item_tpl+"</div>";
|
||||
html = html.format(this.photo, this.name, this.type, this.id, (this.forum=='1'?'forum':''), this.network, this.link);
|
||||
if (this.uids!=undefined) that.group_uids[this.id] = this.uids;
|
||||
//console.log(html);
|
||||
that.list_content.append(html);
|
||||
that.data[this.id] = this;
|
||||
});
|
||||
$(".acl-list-item img[data-src]", that.list_content).each(function(i, el){
|
||||
var height = Math.ceil(data.tot / this.nw) * 42;
|
||||
this.list_content.height(height);
|
||||
this.data = {};
|
||||
$(data.items).each(function(index, item){
|
||||
html = "<div class='acl-list-item {4} {5} type{2}' title='{6}' id='{2}{3}'>"+this.item_tpl+"</div>";
|
||||
html = html.format(item.photo, item.name, item.type, item.id, (item.forum=='1'?'forum':''), item.network, item.link);
|
||||
if (item.uids!=undefined) this.group_uids[item.id] = item.uids;
|
||||
|
||||
this.list_content.append(html);
|
||||
this.data[item.id] = item;
|
||||
}.bind(this));
|
||||
$(".acl-list-item img[data-src]", this.list_content).each(function(i, el){
|
||||
// Add src attribute for images with a data-src attribute
|
||||
$(el).attr('src', $(el).data("src"));
|
||||
});
|
||||
|
||||
that.update_view();
|
||||
this.update_view();
|
||||
}
|
||||
|
||||
|
|
|
|||
13
js/main.js
13
js/main.js
|
|
@ -9,7 +9,7 @@
|
|||
if (h==ch) {
|
||||
return;
|
||||
}
|
||||
console.log("_resizeIframe", obj, desth, ch);
|
||||
//console.log("_resizeIframe", obj, desth, ch);
|
||||
if (desth!=ch) {
|
||||
setTimeout(_resizeIframe, 500, obj, ch);
|
||||
} else {
|
||||
|
|
@ -147,6 +147,7 @@
|
|||
} else {
|
||||
last_popup_menu = menu;
|
||||
last_popup_button = parent;
|
||||
$('#nav-notifications-menu').perfectScrollbar('update');
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
|
@ -159,7 +160,9 @@
|
|||
'inline' : true,
|
||||
'transition' : 'elastic'
|
||||
});
|
||||
|
||||
$("a.ajax-popupbox").colorbox({
|
||||
'transition' : 'elastic'
|
||||
});
|
||||
|
||||
/* notifications template */
|
||||
var notifications_tpl= unescape($("#nav-notifications-template[rel=template]").html());
|
||||
|
|
@ -167,6 +170,9 @@
|
|||
var notifications_mark = unescape($('<div>').append( $("#nav-notifications-mark-all").clone() ).html()); //outerHtml hack
|
||||
var notifications_empty = unescape($("#nav-notifications-menu").html());
|
||||
|
||||
/* enable perfect-scrollbars for different elements */
|
||||
$('#nav-notifications-menu, aside').perfectScrollbar();
|
||||
|
||||
/* nav update event */
|
||||
$('nav').bind('nav-update', function(e,data){
|
||||
var invalid = $(data).find('invalid').text();
|
||||
|
|
@ -311,6 +317,9 @@
|
|||
$.jGrowl(text, { sticky: false, theme: 'info', life: 5000 });
|
||||
});
|
||||
|
||||
/* update the js scrollbars */
|
||||
$('#nav-notifications-menu').perfectScrollbar('update');
|
||||
|
||||
});
|
||||
|
||||
NavUpdate();
|
||||
|
|
|
|||
11
library/Chart.js-1.0.2/Chart.min.js
vendored
Normal file
11
library/Chart.js-1.0.2/Chart.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
library/Chart.js-1.0.2/LICENSE.md
Normal file
7
library/Chart.js-1.0.2/LICENSE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Copyright (c) 2013-2015 Nick Downie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
20
library/Chart.js-1.0.2/README.md
Normal file
20
library/Chart.js-1.0.2/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Chart.js
|
||||
|
||||
[](https://travis-ci.org/nnnick/Chart.js) [](https://codeclimate.com/github/nnnick/Chart.js)
|
||||
|
||||
|
||||
*Simple HTML5 Charts using the canvas element* [chartjs.org](http://www.chartjs.org)
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find documentation at [chartjs.org/docs](http://www.chartjs.org/docs/). The markdown files that build the site are available under `/docs`. Please note - in some of the json examples of configuration you might notice some liquid tags - this is just for the generating the site html, please disregard.
|
||||
|
||||
## Bugs, issues and contributing
|
||||
|
||||
Before submitting an issue or a pull request to the project, please take a moment to look over the [contributing guidelines](https://github.com/nnnick/Chart.js/blob/master/CONTRIBUTING.md) first.
|
||||
|
||||
For support using Chart.js, please post questions with the [`chartjs` tag on Stack Overflow](http://stackoverflow.com/questions/tagged/chartjs).
|
||||
|
||||
## License
|
||||
|
||||
Chart.js is available under the [MIT license](http://opensource.org/licenses/MIT).
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
|
||||
{
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(false); // opacity is non-negative, but we will clamp it
|
||||
}
|
||||
|
||||
public function validate($number, $config, $context) {
|
||||
$result = parent::validate($number, $config, $context);
|
||||
if ($result === false) return $result;
|
||||
$float = (float) $result;
|
||||
if ($float < 0.0) $result = '0';
|
||||
if ($float > 1.0) $result = '1';
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Decorator which enables CSS properties to be disabled for specific elements.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
|
||||
{
|
||||
public $def, $element;
|
||||
|
||||
/**
|
||||
* @param $def Definition to wrap
|
||||
* @param $element Element to deny
|
||||
*/
|
||||
public function __construct($def, $element) {
|
||||
$this->def = $def;
|
||||
$this->element = $element;
|
||||
}
|
||||
/**
|
||||
* Checks if CurrentToken is set and equal to $this->element
|
||||
*/
|
||||
public function validate($string, $config, $context) {
|
||||
$token = $context->get('CurrentToken', true);
|
||||
if ($token && $token->name == $this->element) return false;
|
||||
return $this->def->validate($string, $config, $context);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a font family list according to CSS spec
|
||||
* @todo whitelisting allowed fonts would be nice
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
static $generic_names = array(
|
||||
'serif' => true,
|
||||
'sans-serif' => true,
|
||||
'monospace' => true,
|
||||
'fantasy' => true,
|
||||
'cursive' => true
|
||||
);
|
||||
|
||||
// assume that no font names contain commas in them
|
||||
$fonts = explode(',', $string);
|
||||
$final = '';
|
||||
foreach($fonts as $font) {
|
||||
$font = trim($font);
|
||||
if ($font === '') continue;
|
||||
// match a generic name
|
||||
if (isset($generic_names[$font])) {
|
||||
$final .= $font . ', ';
|
||||
continue;
|
||||
}
|
||||
// match a quoted name
|
||||
if ($font[0] === '"' || $font[0] === "'") {
|
||||
$length = strlen($font);
|
||||
if ($length <= 2) continue;
|
||||
$quote = $font[0];
|
||||
if ($font[$length - 1] !== $quote) continue;
|
||||
$font = substr($font, 1, $length - 2);
|
||||
}
|
||||
|
||||
$font = $this->expandCSSEscape($font);
|
||||
|
||||
// $font is a pure representation of the font name
|
||||
|
||||
if (ctype_alnum($font) && $font !== '') {
|
||||
// very simple font, allow it in unharmed
|
||||
$final .= $font . ', ';
|
||||
continue;
|
||||
}
|
||||
|
||||
// bugger out on whitespace. form feed (0C) really
|
||||
// shouldn't show up regardless
|
||||
$font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
|
||||
|
||||
// These ugly transforms don't pose a security
|
||||
// risk (as \\ and \" might). We could try to be clever and
|
||||
// use single-quote wrapping when there is a double quote
|
||||
// present, but I have choosen not to implement that.
|
||||
// (warning: this code relies on the selection of quotation
|
||||
// mark below)
|
||||
$font = str_replace('\\', '\\5C ', $font);
|
||||
$font = str_replace('"', '\\22 ', $font);
|
||||
|
||||
// complicated font, requires quoting
|
||||
$final .= "\"$font\", "; // note that this will later get turned into "
|
||||
}
|
||||
$final = rtrim($final, ', ');
|
||||
if ($final === '') return false;
|
||||
return $final;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Represents a Length as defined by CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $min, $max;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Length $max Minimum length, or null for no bound. String is also acceptable.
|
||||
* @param HTMLPurifier_Length $max Maximum length, or null for no bound. String is also acceptable.
|
||||
*/
|
||||
public function __construct($min = null, $max = null) {
|
||||
$this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
|
||||
$this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
// Optimizations
|
||||
if ($string === '') return false;
|
||||
if ($string === '0') return '0';
|
||||
if (strlen($string) === 1) return false;
|
||||
|
||||
$length = HTMLPurifier_Length::make($string);
|
||||
if (!$length->isValid()) return false;
|
||||
|
||||
if ($this->min) {
|
||||
$c = $length->compareTo($this->min);
|
||||
if ($c === false) return false;
|
||||
if ($c < 0) return false;
|
||||
}
|
||||
if ($this->max) {
|
||||
$c = $length->compareTo($this->max);
|
||||
if ($c === false) return false;
|
||||
if ($c > 0) return false;
|
||||
}
|
||||
|
||||
return $length->toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates shorthand CSS property list-style.
|
||||
* @warning Does not support url tokens that have internal spaces.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Local copy of component validators.
|
||||
* @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
|
||||
*/
|
||||
protected $info;
|
||||
|
||||
public function __construct($config) {
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['list-style-type'] = $def->info['list-style-type'];
|
||||
$this->info['list-style-position'] = $def->info['list-style-position'];
|
||||
$this->info['list-style-image'] = $def->info['list-style-image'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
// regular pre-processing
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
// assumes URI doesn't have spaces in it
|
||||
$bits = explode(' ', strtolower($string)); // bits to process
|
||||
|
||||
$caught = array();
|
||||
$caught['type'] = false;
|
||||
$caught['position'] = false;
|
||||
$caught['image'] = false;
|
||||
|
||||
$i = 0; // number of catches
|
||||
$none = false;
|
||||
|
||||
foreach ($bits as $bit) {
|
||||
if ($i >= 3) return; // optimization bit
|
||||
if ($bit === '') continue;
|
||||
foreach ($caught as $key => $status) {
|
||||
if ($status !== false) continue;
|
||||
$r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
|
||||
if ($r === false) continue;
|
||||
if ($r === 'none') {
|
||||
if ($none) continue;
|
||||
else $none = true;
|
||||
if ($key == 'image') continue;
|
||||
}
|
||||
$caught[$key] = $r;
|
||||
$i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$i) return false;
|
||||
|
||||
$ret = array();
|
||||
|
||||
// construct type
|
||||
if ($caught['type']) $ret[] = $caught['type'];
|
||||
|
||||
// construct image
|
||||
if ($caught['image']) $ret[] = $caught['image'];
|
||||
|
||||
// construct position
|
||||
if ($caught['position']) $ret[] = $caught['position'];
|
||||
|
||||
if (empty($ret)) return false;
|
||||
return implode(' ', $ret);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a Percentage as defined by the CSS spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of HTMLPurifier_AttrDef_CSS_Number to defer number validation
|
||||
*/
|
||||
protected $number_def;
|
||||
|
||||
/**
|
||||
* @param Bool indicating whether to forbid negative values
|
||||
*/
|
||||
public function __construct($non_negative = false) {
|
||||
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
if ($string === '') return false;
|
||||
$length = strlen($string);
|
||||
if ($length === 1) return false;
|
||||
if ($string[$length - 1] !== '%') return false;
|
||||
|
||||
$number = substr($string, 0, $length - 1);
|
||||
$number = $this->number_def->validate($number, $config, $context);
|
||||
|
||||
if ($number === false) return false;
|
||||
return "$number%";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a boolean attribute
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $name;
|
||||
public $minimized = true;
|
||||
|
||||
public function __construct($name = false) {$this->name = $name;}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
if (empty($string)) return false;
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string Name of attribute
|
||||
*/
|
||||
public function make($string) {
|
||||
return new HTMLPurifier_AttrDef_HTML_Bool($string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a color according to the HTML spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
static $colors = null;
|
||||
if ($colors === null) $colors = $config->get('Core.ColorKeywords');
|
||||
|
||||
$string = trim($string);
|
||||
|
||||
if (empty($string)) return false;
|
||||
if (isset($colors[$string])) return $colors[$string];
|
||||
if ($string[0] === '#') $hex = substr($string, 1);
|
||||
else $hex = $string;
|
||||
|
||||
$length = strlen($hex);
|
||||
if ($length !== 3 && $length !== 6) return false;
|
||||
if (!ctype_xdigit($hex)) return false;
|
||||
if ($length === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
|
||||
|
||||
return "#$hex";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Special-case enum attribute definition that lazy loads allowed frame targets
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
|
||||
{
|
||||
|
||||
public $valid_values = false; // uninitialized value
|
||||
protected $case_sensitive = false;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
if ($this->valid_values === false) $this->valid_values = $config->get('Attr.AllowedFrameTargets');
|
||||
return parent::validate($string, $config, $context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates the HTML attribute ID.
|
||||
* @warning Even though this is the id processor, it
|
||||
* will ignore the directive Attr:IDBlacklist, since it will only
|
||||
* go according to the ID accumulator. Since the accumulator is
|
||||
* automatically generated, it will have already absorbed the
|
||||
* blacklist. If you're hacking around, make sure you use load()!
|
||||
*/
|
||||
|
||||
class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
// ref functionality disabled, since we also have to verify
|
||||
// whether or not the ID it refers to exists
|
||||
|
||||
public function validate($id, $config, $context) {
|
||||
|
||||
if (!$config->get('Attr.EnableID')) return false;
|
||||
|
||||
$id = trim($id); // trim it first
|
||||
|
||||
if ($id === '') return false;
|
||||
|
||||
$prefix = $config->get('Attr.IDPrefix');
|
||||
if ($prefix !== '') {
|
||||
$prefix .= $config->get('Attr.IDPrefixLocal');
|
||||
// prevent re-appending the prefix
|
||||
if (strpos($id, $prefix) !== 0) $id = $prefix . $id;
|
||||
} elseif ($config->get('Attr.IDPrefixLocal') !== '') {
|
||||
trigger_error('%Attr.IDPrefixLocal cannot be used unless '.
|
||||
'%Attr.IDPrefix is set', E_USER_WARNING);
|
||||
}
|
||||
|
||||
//if (!$this->ref) {
|
||||
$id_accumulator =& $context->get('IDAccumulator');
|
||||
if (isset($id_accumulator->ids[$id])) return false;
|
||||
//}
|
||||
|
||||
// we purposely avoid using regex, hopefully this is faster
|
||||
|
||||
if (ctype_alpha($id)) {
|
||||
$result = true;
|
||||
} else {
|
||||
if (!ctype_alpha(@$id[0])) return false;
|
||||
$trim = trim( // primitive style of regexps, I suppose
|
||||
$id,
|
||||
'A..Za..z0..9:-._'
|
||||
);
|
||||
$result = ($trim === '');
|
||||
}
|
||||
|
||||
$regexp = $config->get('Attr.IDBlacklistRegexp');
|
||||
if ($regexp && preg_match($regexp, $id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/*!$this->ref && */$result) $id_accumulator->add($id);
|
||||
|
||||
// if no change was made to the ID, return the result
|
||||
// else, return the new id if stripping whitespace made it
|
||||
// valid, or return false.
|
||||
return $result ? $id : false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates the HTML type length (not to be confused with CSS's length).
|
||||
*
|
||||
* This accepts integer pixels or percentages as lengths for certain
|
||||
* HTML attributes.
|
||||
*/
|
||||
|
||||
class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
$parent_result = parent::validate($string, $config, $context);
|
||||
if ($parent_result !== false) return $parent_result;
|
||||
|
||||
$length = strlen($string);
|
||||
$last_char = $string[$length - 1];
|
||||
|
||||
if ($last_char !== '%') return false;
|
||||
|
||||
$points = substr($string, 0, $length - 1);
|
||||
|
||||
if (!is_numeric($points)) return false;
|
||||
|
||||
$points = (int) $points;
|
||||
|
||||
if ($points < 0) return '0%';
|
||||
if ($points > 100) return '100%';
|
||||
|
||||
return ((string) $points) . '%';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a MultiLength as defined by the HTML spec.
|
||||
*
|
||||
* A multilength is either a integer (pixel count), a percentage, or
|
||||
* a relative number.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
$parent_result = parent::validate($string, $config, $context);
|
||||
if ($parent_result !== false) return $parent_result;
|
||||
|
||||
$length = strlen($string);
|
||||
$last_char = $string[$length - 1];
|
||||
|
||||
if ($last_char !== '*') return false;
|
||||
|
||||
$int = substr($string, 0, $length - 1);
|
||||
|
||||
if ($int == '') return '*';
|
||||
if (!is_numeric($int)) return false;
|
||||
|
||||
$int = (int) $int;
|
||||
|
||||
if ($int < 0) return false;
|
||||
if ($int == 0) return '0';
|
||||
if ($int == 1) return '*';
|
||||
return ((string) $int) . '*';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates an integer representation of pixels according to the HTML spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $max;
|
||||
|
||||
public function __construct($max = null) {
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
if ($string === '0') return $string;
|
||||
if ($string === '') return false;
|
||||
$length = strlen($string);
|
||||
if (substr($string, $length - 2) == 'px') {
|
||||
$string = substr($string, 0, $length - 2);
|
||||
}
|
||||
if (!is_numeric($string)) return false;
|
||||
$int = (int) $string;
|
||||
|
||||
if ($int < 0) return '0';
|
||||
|
||||
// upper-bound value, extremely high values can
|
||||
// crash operating systems, see <http://ha.ckers.org/imagecrash.html>
|
||||
// WARNING, above link WILL crash you if you're using Windows
|
||||
|
||||
if ($this->max !== null && $int > $this->max) return (string) $this->max;
|
||||
|
||||
return (string) $int;
|
||||
|
||||
}
|
||||
|
||||
public function make($string) {
|
||||
if ($string === '') $max = null;
|
||||
else $max = (int) $string;
|
||||
$class = get_class($this);
|
||||
return new $class($max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates arbitrary text according to the HTML spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
return $this->parseCDATA($string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of HTMLPurifier_AttrDef_URI_IPv4 sub-validator
|
||||
*/
|
||||
protected $ipv4;
|
||||
|
||||
/**
|
||||
* Instance of HTMLPurifier_AttrDef_URI_IPv6 sub-validator
|
||||
*/
|
||||
protected $ipv6;
|
||||
|
||||
public function __construct() {
|
||||
$this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
|
||||
$this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$length = strlen($string);
|
||||
if ($string === '') return '';
|
||||
if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') {
|
||||
//IPv6
|
||||
$ip = substr($string, 1, $length - 2);
|
||||
$valid = $this->ipv6->validate($ip, $config, $context);
|
||||
if ($valid === false) return false;
|
||||
return '['. $valid . ']';
|
||||
}
|
||||
|
||||
// need to do checks on unusual encodings too
|
||||
$ipv4 = $this->ipv4->validate($string, $config, $context);
|
||||
if ($ipv4 !== false) return $ipv4;
|
||||
|
||||
// A regular domain name.
|
||||
|
||||
// This breaks I18N domain names, but we don't have proper IRI support,
|
||||
// so force users to insert Punycode. If there's complaining we'll
|
||||
// try to fix things into an international friendly form.
|
||||
|
||||
// The productions describing this are:
|
||||
$a = '[a-z]'; // alpha
|
||||
$an = '[a-z0-9]'; // alphanum
|
||||
$and = '[a-z0-9-]'; // alphanum | "-"
|
||||
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
|
||||
$domainlabel = "$an($and*$an)?";
|
||||
// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
|
||||
$toplabel = "$a($and*$an)?";
|
||||
// hostname = *( domainlabel "." ) toplabel [ "." ]
|
||||
$match = preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string);
|
||||
if (!$match) return false;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates an IPv6 address.
|
||||
* @author Feyd @ forums.devnetwork.net (public domain)
|
||||
* @note This function requires brackets to have been removed from address
|
||||
* in URI.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
|
||||
{
|
||||
|
||||
public function validate($aIP, $config, $context) {
|
||||
|
||||
if (!$this->ip4) $this->_loadRegex();
|
||||
|
||||
$original = $aIP;
|
||||
|
||||
$hex = '[0-9a-fA-F]';
|
||||
$blk = '(?:' . $hex . '{1,4})';
|
||||
$pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
|
||||
|
||||
// prefix check
|
||||
if (strpos($aIP, '/') !== false)
|
||||
{
|
||||
if (preg_match('#' . $pre . '$#s', $aIP, $find))
|
||||
{
|
||||
$aIP = substr($aIP, 0, 0-strlen($find[0]));
|
||||
unset($find);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// IPv4-compatiblity check
|
||||
if (preg_match('#(?<=:'.')' . $this->ip4 . '$#s', $aIP, $find))
|
||||
{
|
||||
$aIP = substr($aIP, 0, 0-strlen($find[0]));
|
||||
$ip = explode('.', $find[0]);
|
||||
$ip = array_map('dechex', $ip);
|
||||
$aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
|
||||
unset($find, $ip);
|
||||
}
|
||||
|
||||
// compression check
|
||||
$aIP = explode('::', $aIP);
|
||||
$c = count($aIP);
|
||||
if ($c > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
elseif ($c == 2)
|
||||
{
|
||||
list($first, $second) = $aIP;
|
||||
$first = explode(':', $first);
|
||||
$second = explode(':', $second);
|
||||
|
||||
if (count($first) + count($second) > 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while(count($first) < 8)
|
||||
{
|
||||
array_push($first, '0');
|
||||
}
|
||||
|
||||
array_splice($first, 8 - count($second), 8, $second);
|
||||
$aIP = $first;
|
||||
unset($first,$second);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aIP = explode(':', $aIP[0]);
|
||||
}
|
||||
$c = count($aIP);
|
||||
|
||||
if ($c != 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// All the pieces should be 16-bit hex strings. Are they?
|
||||
foreach ($aIP as $piece)
|
||||
{
|
||||
if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $original;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pre-transform that changes converts a boolean attribute to fixed CSS
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform {
|
||||
|
||||
/**
|
||||
* Name of boolean attribute that is trigger
|
||||
*/
|
||||
protected $attr;
|
||||
|
||||
/**
|
||||
* CSS declarations to add to style, needs trailing semicolon
|
||||
*/
|
||||
protected $css;
|
||||
|
||||
/**
|
||||
* @param $attr string attribute name to convert from
|
||||
* @param $css string CSS declarations to add to style (needs semicolon)
|
||||
*/
|
||||
public function __construct($attr, $css) {
|
||||
$this->attr = $attr;
|
||||
$this->css = $css;
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (!isset($attr[$this->attr])) return $attr;
|
||||
unset($attr[$this->attr]);
|
||||
$this->prependCSS($attr, $this->css);
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Generic pre-transform that converts an attribute with a fixed number of
|
||||
* values (enumerated) to CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform {
|
||||
|
||||
/**
|
||||
* Name of attribute to transform from
|
||||
*/
|
||||
protected $attr;
|
||||
|
||||
/**
|
||||
* Lookup array of attribute values to CSS
|
||||
*/
|
||||
protected $enumToCSS = array();
|
||||
|
||||
/**
|
||||
* Case sensitivity of the matching
|
||||
* @warning Currently can only be guaranteed to work with ASCII
|
||||
* values.
|
||||
*/
|
||||
protected $caseSensitive = false;
|
||||
|
||||
/**
|
||||
* @param $attr String attribute name to transform from
|
||||
* @param $enumToCSS Lookup array of attribute values to CSS
|
||||
* @param $case_sensitive Boolean case sensitivity indicator, default false
|
||||
*/
|
||||
public function __construct($attr, $enum_to_css, $case_sensitive = false) {
|
||||
$this->attr = $attr;
|
||||
$this->enumToCSS = $enum_to_css;
|
||||
$this->caseSensitive = (bool) $case_sensitive;
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
|
||||
if (!isset($attr[$this->attr])) return $attr;
|
||||
|
||||
$value = trim($attr[$this->attr]);
|
||||
unset($attr[$this->attr]);
|
||||
|
||||
if (!$this->caseSensitive) $value = strtolower($value);
|
||||
|
||||
if (!isset($this->enumToCSS[$value])) {
|
||||
return $attr;
|
||||
}
|
||||
|
||||
$this->prependCSS($attr, $this->enumToCSS[$value]);
|
||||
|
||||
return $attr;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Class for handling width/height length attribute transformations to CSS
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
protected $name;
|
||||
protected $cssName;
|
||||
|
||||
public function __construct($name, $css_name = null) {
|
||||
$this->name = $name;
|
||||
$this->cssName = $css_name ? $css_name : $name;
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (!isset($attr[$this->name])) return $attr;
|
||||
$length = $this->confiscateAttr($attr, $this->name);
|
||||
if(ctype_digit($length)) $length .= 'px';
|
||||
$this->prependCSS($attr, $this->cssName . ":$length;");
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pre-transform that changes deprecated name attribute to ID if necessary
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
// Abort early if we're using relaxed definition of name
|
||||
if ($config->get('HTML.Attr.Name.UseCDATA')) return $attr;
|
||||
if (!isset($attr['name'])) return $attr;
|
||||
$id = $this->confiscateAttr($attr, 'name');
|
||||
if ( isset($attr['id'])) return $attr;
|
||||
$attr['id'] = $id;
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Post-transform that performs validation to the name attribute; if
|
||||
* it is present with an equivalent id attribute, it is passed through;
|
||||
* otherwise validation is performed.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
public function __construct() {
|
||||
$this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (!isset($attr['name'])) return $attr;
|
||||
$name = $attr['name'];
|
||||
if (isset($attr['id']) && $attr['id'] === $name) return $attr;
|
||||
$result = $this->idDef->validate($name, $config, $context);
|
||||
if ($result === false) unset($attr['name']);
|
||||
else $attr['name'] = $result;
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Writes default type for all objects. Currently only supports flash.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
public $name = "SafeObject";
|
||||
|
||||
function transform($attr, $config, $context) {
|
||||
if (!isset($attr['type'])) $attr['type'] = 'application/x-shockwave-flash';
|
||||
return $attr;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Sets height/width defaults for <textarea>
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
// Calculated from Firefox
|
||||
if (!isset($attr['cols'])) $attr['cols'] = '22';
|
||||
if (!isset($attr['rows'])) $attr['rows'] = '3';
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
<?php
|
||||
|
||||
// constants are slow, so we use as few as possible
|
||||
if (!defined('HTMLPURIFIER_PREFIX')) {
|
||||
define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..'));
|
||||
}
|
||||
|
||||
// accomodations for versions earlier than 5.0.2
|
||||
// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
|
||||
if (!defined('PHP_EOL')) {
|
||||
switch (strtoupper(substr(PHP_OS, 0, 3))) {
|
||||
case 'WIN':
|
||||
define('PHP_EOL', "\r\n");
|
||||
break;
|
||||
case 'DAR':
|
||||
define('PHP_EOL', "\r");
|
||||
break;
|
||||
default:
|
||||
define('PHP_EOL', "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap class that contains meta-functionality for HTML Purifier such as
|
||||
* the autoload function.
|
||||
*
|
||||
* @note
|
||||
* This class may be used without any other files from HTML Purifier.
|
||||
*/
|
||||
class HTMLPurifier_Bootstrap
|
||||
{
|
||||
|
||||
/**
|
||||
* Autoload function for HTML Purifier
|
||||
* @param $class Class to load
|
||||
*/
|
||||
public static function autoload($class) {
|
||||
$file = HTMLPurifier_Bootstrap::getPath($class);
|
||||
if (!$file) return false;
|
||||
require HTMLPURIFIER_PREFIX . '/' . $file;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path for a specific class.
|
||||
*/
|
||||
public static function getPath($class) {
|
||||
if (strncmp('HTMLPurifier', $class, 12) !== 0) return false;
|
||||
// Custom implementations
|
||||
if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
|
||||
$code = str_replace('_', '-', substr($class, 22));
|
||||
$file = 'HTMLPurifier/Language/classes/' . $code . '.php';
|
||||
} else {
|
||||
$file = str_replace('_', '/', $class) . '.php';
|
||||
}
|
||||
if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false;
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Pre-registers" our autoloader on the SPL stack.
|
||||
*/
|
||||
public static function registerAutoload() {
|
||||
$autoload = array('HTMLPurifier_Bootstrap', 'autoload');
|
||||
if ( ($funcs = spl_autoload_functions()) === false ) {
|
||||
spl_autoload_register($autoload);
|
||||
} elseif (function_exists('spl_autoload_unregister')) {
|
||||
$compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
|
||||
version_compare(PHP_VERSION, '5.1.0', '>=');
|
||||
foreach ($funcs as $func) {
|
||||
if (is_array($func)) {
|
||||
// :TRICKY: There are some compatibility issues and some
|
||||
// places where we need to error out
|
||||
$reflector = new ReflectionMethod($func[0], $func[1]);
|
||||
if (!$reflector->isStatic()) {
|
||||
throw new Exception('
|
||||
HTML Purifier autoloader registrar is not compatible
|
||||
with non-static object methods due to PHP Bug #44144;
|
||||
Please do not use HTMLPurifier.autoload.php (or any
|
||||
file that includes this file); instead, place the code:
|
||||
spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
|
||||
after your own autoloaders.
|
||||
');
|
||||
}
|
||||
// Suprisingly, spl_autoload_register supports the
|
||||
// Class::staticMethod callback format, although call_user_func doesn't
|
||||
if ($compat) $func = implode('::', $func);
|
||||
}
|
||||
spl_autoload_unregister($func);
|
||||
}
|
||||
spl_autoload_register($autoload);
|
||||
foreach ($funcs as $func) spl_autoload_register($func);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,292 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Defines allowed CSS attributes and what their values are.
|
||||
* @see HTMLPurifier_HTMLDefinition
|
||||
*/
|
||||
class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
|
||||
{
|
||||
|
||||
public $type = 'CSS';
|
||||
|
||||
/**
|
||||
* Assoc array of attribute name to definition object.
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Constructs the info array. The meat of this class.
|
||||
*/
|
||||
protected function doSetup($config) {
|
||||
|
||||
$this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('left', 'right', 'center', 'justify'), false);
|
||||
|
||||
$border_style =
|
||||
$this->info['border-bottom-style'] =
|
||||
$this->info['border-right-style'] =
|
||||
$this->info['border-left-style'] =
|
||||
$this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
|
||||
'groove', 'ridge', 'inset', 'outset'), false);
|
||||
|
||||
$this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
|
||||
|
||||
$this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('none', 'left', 'right', 'both'), false);
|
||||
$this->info['float'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('none', 'left', 'right'), false);
|
||||
$this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('normal', 'italic', 'oblique'), false);
|
||||
$this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('normal', 'small-caps'), false);
|
||||
|
||||
$uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
|
||||
array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('none')),
|
||||
new HTMLPurifier_AttrDef_CSS_URI()
|
||||
)
|
||||
);
|
||||
|
||||
$this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('inside', 'outside'), false);
|
||||
$this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('disc', 'circle', 'square', 'decimal', 'lower-roman',
|
||||
'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false);
|
||||
$this->info['list-style-image'] = $uri_or_none;
|
||||
|
||||
$this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
|
||||
|
||||
$this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('capitalize', 'uppercase', 'lowercase', 'none'), false);
|
||||
$this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
|
||||
$this->info['background-image'] = $uri_or_none;
|
||||
$this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
|
||||
);
|
||||
$this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('scroll', 'fixed')
|
||||
);
|
||||
$this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
|
||||
|
||||
$border_color =
|
||||
$this->info['border-top-color'] =
|
||||
$this->info['border-bottom-color'] =
|
||||
$this->info['border-left-color'] =
|
||||
$this->info['border-right-color'] =
|
||||
$this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('transparent')),
|
||||
new HTMLPurifier_AttrDef_CSS_Color()
|
||||
));
|
||||
|
||||
$this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
|
||||
|
||||
$this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
|
||||
|
||||
$border_width =
|
||||
$this->info['border-top-width'] =
|
||||
$this->info['border-bottom-width'] =
|
||||
$this->info['border-left-width'] =
|
||||
$this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
|
||||
));
|
||||
|
||||
$this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
|
||||
|
||||
$this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('normal')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length()
|
||||
));
|
||||
|
||||
$this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('normal')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length()
|
||||
));
|
||||
|
||||
$this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small',
|
||||
'small', 'medium', 'large', 'x-large', 'xx-large',
|
||||
'larger', 'smaller')),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(),
|
||||
new HTMLPurifier_AttrDef_CSS_Length()
|
||||
));
|
||||
|
||||
$this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('normal')),
|
||||
new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
|
||||
new HTMLPurifier_AttrDef_CSS_Length('0'),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(true)
|
||||
));
|
||||
|
||||
$margin =
|
||||
$this->info['margin-top'] =
|
||||
$this->info['margin-bottom'] =
|
||||
$this->info['margin-left'] =
|
||||
$this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(),
|
||||
new HTMLPurifier_AttrDef_Enum(array('auto'))
|
||||
));
|
||||
|
||||
$this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
|
||||
|
||||
// non-negative
|
||||
$padding =
|
||||
$this->info['padding-top'] =
|
||||
$this->info['padding-bottom'] =
|
||||
$this->info['padding-left'] =
|
||||
$this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length('0'),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(true)
|
||||
));
|
||||
|
||||
$this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
|
||||
|
||||
$this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage()
|
||||
));
|
||||
|
||||
$trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length('0'),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(true),
|
||||
new HTMLPurifier_AttrDef_Enum(array('auto'))
|
||||
));
|
||||
$max = $config->get('CSS.MaxImgLength');
|
||||
|
||||
$this->info['width'] =
|
||||
$this->info['height'] =
|
||||
$max === null ?
|
||||
$trusted_wh :
|
||||
new HTMLPurifier_AttrDef_Switch('img',
|
||||
// For img tags:
|
||||
new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length('0', $max),
|
||||
new HTMLPurifier_AttrDef_Enum(array('auto'))
|
||||
)),
|
||||
// For everyone else:
|
||||
$trusted_wh
|
||||
);
|
||||
|
||||
$this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
|
||||
|
||||
$this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
|
||||
|
||||
// this could use specialized code
|
||||
$this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300',
|
||||
'400', '500', '600', '700', '800', '900'), false);
|
||||
|
||||
// MUST be called after other font properties, as it references
|
||||
// a CSSDefinition object
|
||||
$this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
|
||||
|
||||
// same here
|
||||
$this->info['border'] =
|
||||
$this->info['border-bottom'] =
|
||||
$this->info['border-top'] =
|
||||
$this->info['border-left'] =
|
||||
$this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
|
||||
|
||||
$this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'collapse', 'separate'));
|
||||
|
||||
$this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'top', 'bottom'));
|
||||
|
||||
$this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'auto', 'fixed'));
|
||||
|
||||
$this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super',
|
||||
'top', 'text-top', 'middle', 'bottom', 'text-bottom')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage()
|
||||
));
|
||||
|
||||
$this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
|
||||
|
||||
// partial support
|
||||
$this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap'));
|
||||
|
||||
if ($config->get('CSS.Proprietary')) {
|
||||
$this->doSetupProprietary($config);
|
||||
}
|
||||
|
||||
if ($config->get('CSS.AllowTricky')) {
|
||||
$this->doSetupTricky($config);
|
||||
}
|
||||
|
||||
$allow_important = $config->get('CSS.AllowImportant');
|
||||
// wrap all attr-defs with decorator that handles !important
|
||||
foreach ($this->info as $k => $v) {
|
||||
$this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
|
||||
}
|
||||
|
||||
$this->setupConfigStuff($config);
|
||||
}
|
||||
|
||||
protected function doSetupProprietary($config) {
|
||||
// Internet Explorer only scrollbar colors
|
||||
$this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
|
||||
// technically not proprietary, but CSS3, and no one supports it
|
||||
$this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
|
||||
$this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
|
||||
$this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
|
||||
|
||||
// only opacity, for now
|
||||
$this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
|
||||
|
||||
}
|
||||
|
||||
protected function doSetupTricky($config) {
|
||||
$this->info['display'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'inline', 'block', 'list-item', 'run-in', 'compact',
|
||||
'marker', 'table', 'inline-table', 'table-row-group',
|
||||
'table-header-group', 'table-footer-group', 'table-row',
|
||||
'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none'
|
||||
));
|
||||
$this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'visible', 'hidden', 'collapse'
|
||||
));
|
||||
$this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs extra config-based processing. Based off of
|
||||
* HTMLPurifier_HTMLDefinition.
|
||||
* @todo Refactor duplicate elements into common class (probably using
|
||||
* composition, not inheritance).
|
||||
*/
|
||||
protected function setupConfigStuff($config) {
|
||||
|
||||
// setup allowed elements
|
||||
$support = "(for information on implementing this, see the ".
|
||||
"support forums) ";
|
||||
$allowed_attributes = $config->get('CSS.AllowedProperties');
|
||||
if ($allowed_attributes !== null) {
|
||||
foreach ($this->info as $name => $d) {
|
||||
if(!isset($allowed_attributes[$name])) unset($this->info[$name]);
|
||||
unset($allowed_attributes[$name]);
|
||||
}
|
||||
// emit errors
|
||||
foreach ($allowed_attributes as $name => $d) {
|
||||
// :TODO: Is this htmlspecialchars() call really necessary?
|
||||
$name = htmlspecialchars($name);
|
||||
trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Definition that allows a set of elements, and allows no children.
|
||||
* @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
|
||||
* really, one shouldn't inherit from the other. Only altered behavior
|
||||
* is to overload a returned false with an array. Thus, it will never
|
||||
* return false.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
|
||||
{
|
||||
public $allow_empty = true;
|
||||
public $type = 'optional';
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
$result = parent::validateChildren($tokens_of_children, $config, $context);
|
||||
// we assume that $tokens_of_children is not modified
|
||||
if ($result === false) {
|
||||
if (empty($tokens_of_children)) return true;
|
||||
elseif ($this->whitespace) return $tokens_of_children;
|
||||
else return array();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Definition that allows a set of elements, but disallows empty children.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
|
||||
{
|
||||
/**
|
||||
* Lookup table of allowed elements.
|
||||
* @public
|
||||
*/
|
||||
public $elements = array();
|
||||
/**
|
||||
* Whether or not the last passed node was all whitespace.
|
||||
*/
|
||||
protected $whitespace = false;
|
||||
/**
|
||||
* @param $elements List of allowed element names (lowercase).
|
||||
*/
|
||||
public function __construct($elements) {
|
||||
if (is_string($elements)) {
|
||||
$elements = str_replace(' ', '', $elements);
|
||||
$elements = explode('|', $elements);
|
||||
}
|
||||
$keys = array_keys($elements);
|
||||
if ($keys == array_keys($keys)) {
|
||||
$elements = array_flip($elements);
|
||||
foreach ($elements as $i => $x) {
|
||||
$elements[$i] = true;
|
||||
if (empty($i)) unset($elements[$i]); // remove blank
|
||||
}
|
||||
}
|
||||
$this->elements = $elements;
|
||||
}
|
||||
public $allow_empty = false;
|
||||
public $type = 'required';
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
// Flag for subclasses
|
||||
$this->whitespace = false;
|
||||
|
||||
// if there are no tokens, delete parent node
|
||||
if (empty($tokens_of_children)) return false;
|
||||
|
||||
// the new set of children
|
||||
$result = array();
|
||||
|
||||
// current depth into the nest
|
||||
$nesting = 0;
|
||||
|
||||
// whether or not we're deleting a node
|
||||
$is_deleting = false;
|
||||
|
||||
// whether or not parsed character data is allowed
|
||||
// this controls whether or not we silently drop a tag
|
||||
// or generate escaped HTML from it
|
||||
$pcdata_allowed = isset($this->elements['#PCDATA']);
|
||||
|
||||
// a little sanity check to make sure it's not ALL whitespace
|
||||
$all_whitespace = true;
|
||||
|
||||
// some configuration
|
||||
$escape_invalid_children = $config->get('Core.EscapeInvalidChildren');
|
||||
|
||||
// generator
|
||||
$gen = new HTMLPurifier_Generator($config, $context);
|
||||
|
||||
foreach ($tokens_of_children as $token) {
|
||||
if (!empty($token->is_whitespace)) {
|
||||
$result[] = $token;
|
||||
continue;
|
||||
}
|
||||
$all_whitespace = false; // phew, we're not talking about whitespace
|
||||
|
||||
$is_child = ($nesting == 0);
|
||||
|
||||
if ($token instanceof HTMLPurifier_Token_Start) {
|
||||
$nesting++;
|
||||
} elseif ($token instanceof HTMLPurifier_Token_End) {
|
||||
$nesting--;
|
||||
}
|
||||
|
||||
if ($is_child) {
|
||||
$is_deleting = false;
|
||||
if (!isset($this->elements[$token->name])) {
|
||||
$is_deleting = true;
|
||||
if ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text) {
|
||||
$result[] = $token;
|
||||
} elseif ($pcdata_allowed && $escape_invalid_children) {
|
||||
$result[] = new HTMLPurifier_Token_Text(
|
||||
$gen->generateFromToken($token)
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!$is_deleting || ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text)) {
|
||||
$result[] = $token;
|
||||
} elseif ($pcdata_allowed && $escape_invalid_children) {
|
||||
$result[] =
|
||||
new HTMLPurifier_Token_Text(
|
||||
$gen->generateFromToken($token)
|
||||
);
|
||||
} else {
|
||||
// drop silently
|
||||
}
|
||||
}
|
||||
if (empty($result)) return false;
|
||||
if ($all_whitespace) {
|
||||
$this->whitespace = true;
|
||||
return false;
|
||||
}
|
||||
if ($tokens_of_children == $result) return true;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Takes the contents of blockquote when in strict and reformats for validation.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
|
||||
{
|
||||
protected $real_elements;
|
||||
protected $fake_elements;
|
||||
public $allow_empty = true;
|
||||
public $type = 'strictblockquote';
|
||||
protected $init = false;
|
||||
|
||||
/**
|
||||
* @note We don't want MakeWellFormed to auto-close inline elements since
|
||||
* they might be allowed.
|
||||
*/
|
||||
public function getAllowedElements($config) {
|
||||
$this->init($config);
|
||||
return $this->fake_elements;
|
||||
}
|
||||
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
|
||||
$this->init($config);
|
||||
|
||||
// trick the parent class into thinking it allows more
|
||||
$this->elements = $this->fake_elements;
|
||||
$result = parent::validateChildren($tokens_of_children, $config, $context);
|
||||
$this->elements = $this->real_elements;
|
||||
|
||||
if ($result === false) return array();
|
||||
if ($result === true) $result = $tokens_of_children;
|
||||
|
||||
$def = $config->getHTMLDefinition();
|
||||
$block_wrap_start = new HTMLPurifier_Token_Start($def->info_block_wrapper);
|
||||
$block_wrap_end = new HTMLPurifier_Token_End( $def->info_block_wrapper);
|
||||
$is_inline = false;
|
||||
$depth = 0;
|
||||
$ret = array();
|
||||
|
||||
// assuming that there are no comment tokens
|
||||
foreach ($result as $i => $token) {
|
||||
$token = $result[$i];
|
||||
// ifs are nested for readability
|
||||
if (!$is_inline) {
|
||||
if (!$depth) {
|
||||
if (
|
||||
($token instanceof HTMLPurifier_Token_Text && !$token->is_whitespace) ||
|
||||
(!$token instanceof HTMLPurifier_Token_Text && !isset($this->elements[$token->name]))
|
||||
) {
|
||||
$is_inline = true;
|
||||
$ret[] = $block_wrap_start;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!$depth) {
|
||||
// starting tokens have been inline text / empty
|
||||
if ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) {
|
||||
if (isset($this->elements[$token->name])) {
|
||||
// ended
|
||||
$ret[] = $block_wrap_end;
|
||||
$is_inline = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$ret[] = $token;
|
||||
if ($token instanceof HTMLPurifier_Token_Start) $depth++;
|
||||
if ($token instanceof HTMLPurifier_Token_End) $depth--;
|
||||
}
|
||||
if ($is_inline) $ret[] = $block_wrap_end;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function init($config) {
|
||||
if (!$this->init) {
|
||||
$def = $config->getHTMLDefinition();
|
||||
// allow all inline elements
|
||||
$this->real_elements = $this->elements;
|
||||
$this->fake_elements = $def->info_content_sets['Flow'];
|
||||
$this->fake_elements['#PCDATA'] = true;
|
||||
$this->init = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Definition for tables
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $allow_empty = false;
|
||||
public $type = 'table';
|
||||
public $elements = array('tr' => true, 'tbody' => true, 'thead' => true,
|
||||
'tfoot' => true, 'caption' => true, 'colgroup' => true, 'col' => true);
|
||||
public function __construct() {}
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
if (empty($tokens_of_children)) return false;
|
||||
|
||||
// this ensures that the loop gets run one last time before closing
|
||||
// up. It's a little bit of a hack, but it works! Just make sure you
|
||||
// get rid of the token later.
|
||||
$tokens_of_children[] = false;
|
||||
|
||||
// only one of these elements is allowed in a table
|
||||
$caption = false;
|
||||
$thead = false;
|
||||
$tfoot = false;
|
||||
|
||||
// as many of these as you want
|
||||
$cols = array();
|
||||
$content = array();
|
||||
|
||||
$nesting = 0; // current depth so we can determine nodes
|
||||
$is_collecting = false; // are we globbing together tokens to package
|
||||
// into one of the collectors?
|
||||
$collection = array(); // collected nodes
|
||||
$tag_index = 0; // the first node might be whitespace,
|
||||
// so this tells us where the start tag is
|
||||
|
||||
foreach ($tokens_of_children as $token) {
|
||||
$is_child = ($nesting == 0);
|
||||
|
||||
if ($token === false) {
|
||||
// terminating sequence started
|
||||
} elseif ($token instanceof HTMLPurifier_Token_Start) {
|
||||
$nesting++;
|
||||
} elseif ($token instanceof HTMLPurifier_Token_End) {
|
||||
$nesting--;
|
||||
}
|
||||
|
||||
// handle node collection
|
||||
if ($is_collecting) {
|
||||
if ($is_child) {
|
||||
// okay, let's stash the tokens away
|
||||
// first token tells us the type of the collection
|
||||
switch ($collection[$tag_index]->name) {
|
||||
case 'tr':
|
||||
case 'tbody':
|
||||
$content[] = $collection;
|
||||
break;
|
||||
case 'caption':
|
||||
if ($caption !== false) break;
|
||||
$caption = $collection;
|
||||
break;
|
||||
case 'thead':
|
||||
case 'tfoot':
|
||||
// access the appropriate variable, $thead or $tfoot
|
||||
$var = $collection[$tag_index]->name;
|
||||
if ($$var === false) {
|
||||
$$var = $collection;
|
||||
} else {
|
||||
// transmutate the first and less entries into
|
||||
// tbody tags, and then put into content
|
||||
$collection[$tag_index]->name = 'tbody';
|
||||
$collection[count($collection)-1]->name = 'tbody';
|
||||
$content[] = $collection;
|
||||
}
|
||||
break;
|
||||
case 'colgroup':
|
||||
$cols[] = $collection;
|
||||
break;
|
||||
}
|
||||
$collection = array();
|
||||
$is_collecting = false;
|
||||
$tag_index = 0;
|
||||
} else {
|
||||
// add the node to the collection
|
||||
$collection[] = $token;
|
||||
}
|
||||
}
|
||||
|
||||
// terminate
|
||||
if ($token === false) break;
|
||||
|
||||
if ($is_child) {
|
||||
// determine what we're dealing with
|
||||
if ($token->name == 'col') {
|
||||
// the only empty tag in the possie, we can handle it
|
||||
// immediately
|
||||
$cols[] = array_merge($collection, array($token));
|
||||
$collection = array();
|
||||
$tag_index = 0;
|
||||
continue;
|
||||
}
|
||||
switch($token->name) {
|
||||
case 'caption':
|
||||
case 'colgroup':
|
||||
case 'thead':
|
||||
case 'tfoot':
|
||||
case 'tbody':
|
||||
case 'tr':
|
||||
$is_collecting = true;
|
||||
$collection[] = $token;
|
||||
continue;
|
||||
default:
|
||||
if (!empty($token->is_whitespace)) {
|
||||
$collection[] = $token;
|
||||
$tag_index++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($content)) return false;
|
||||
|
||||
$ret = array();
|
||||
if ($caption !== false) $ret = array_merge($ret, $caption);
|
||||
if ($cols !== false) foreach ($cols as $token_array) $ret = array_merge($ret, $token_array);
|
||||
if ($thead !== false) $ret = array_merge($ret, $thead);
|
||||
if ($tfoot !== false) $ret = array_merge($ret, $tfoot);
|
||||
foreach ($content as $token_array) $ret = array_merge($ret, $token_array);
|
||||
if (!empty($collection) && $is_collecting == false){
|
||||
// grab the trailing space
|
||||
$ret = array_merge($ret, $collection);
|
||||
}
|
||||
|
||||
array_pop($tokens_of_children); // remove phantom token
|
||||
|
||||
return ($ret === $tokens_of_children) ? true : $ret;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,580 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Configuration object that triggers customizable behavior.
|
||||
*
|
||||
* @warning This class is strongly defined: that means that the class
|
||||
* will fail if an undefined directive is retrieved or set.
|
||||
*
|
||||
* @note Many classes that could (although many times don't) use the
|
||||
* configuration object make it a mandatory parameter. This is
|
||||
* because a configuration object should always be forwarded,
|
||||
* otherwise, you run the risk of missing a parameter and then
|
||||
* being stumped when a configuration directive doesn't work.
|
||||
*
|
||||
* @todo Reconsider some of the public member variables
|
||||
*/
|
||||
class HTMLPurifier_Config
|
||||
{
|
||||
|
||||
/**
|
||||
* HTML Purifier's version
|
||||
*/
|
||||
public $version = '4.1.1';
|
||||
|
||||
/**
|
||||
* Bool indicator whether or not to automatically finalize
|
||||
* the object if a read operation is done
|
||||
*/
|
||||
public $autoFinalize = true;
|
||||
|
||||
// protected member variables
|
||||
|
||||
/**
|
||||
* Namespace indexed array of serials for specific namespaces (see
|
||||
* getSerial() for more info).
|
||||
*/
|
||||
protected $serials = array();
|
||||
|
||||
/**
|
||||
* Serial for entire configuration object
|
||||
*/
|
||||
protected $serial;
|
||||
|
||||
/**
|
||||
* Parser for variables
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* Reference HTMLPurifier_ConfigSchema for value checking
|
||||
* @note This is public for introspective purposes. Please don't
|
||||
* abuse!
|
||||
*/
|
||||
public $def;
|
||||
|
||||
/**
|
||||
* Indexed array of definitions
|
||||
*/
|
||||
protected $definitions;
|
||||
|
||||
/**
|
||||
* Bool indicator whether or not config is finalized
|
||||
*/
|
||||
protected $finalized = false;
|
||||
|
||||
/**
|
||||
* Property list containing configuration directives.
|
||||
*/
|
||||
protected $plist;
|
||||
|
||||
/**
|
||||
* Whether or not a set is taking place due to an
|
||||
* alias lookup.
|
||||
*/
|
||||
private $aliasMode;
|
||||
|
||||
/**
|
||||
* Set to false if you do not want line and file numbers in errors
|
||||
* (useful when unit testing)
|
||||
*/
|
||||
public $chatty = true;
|
||||
|
||||
/**
|
||||
* Current lock; only gets to this namespace are allowed.
|
||||
*/
|
||||
private $lock;
|
||||
|
||||
/**
|
||||
* @param $definition HTMLPurifier_ConfigSchema that defines what directives
|
||||
* are allowed.
|
||||
*/
|
||||
public function __construct($definition, $parent = null) {
|
||||
$parent = $parent ? $parent : $definition->defaultPlist;
|
||||
$this->plist = new HTMLPurifier_PropertyList($parent);
|
||||
$this->def = $definition; // keep a copy around for checking
|
||||
$this->parser = new HTMLPurifier_VarParser_Flexible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor that creates a config object based on a mixed var
|
||||
* @param mixed $config Variable that defines the state of the config
|
||||
* object. Can be: a HTMLPurifier_Config() object,
|
||||
* an array of directives based on loadArray(),
|
||||
* or a string filename of an ini file.
|
||||
* @param HTMLPurifier_ConfigSchema Schema object
|
||||
* @return Configured HTMLPurifier_Config object
|
||||
*/
|
||||
public static function create($config, $schema = null) {
|
||||
if ($config instanceof HTMLPurifier_Config) {
|
||||
// pass-through
|
||||
return $config;
|
||||
}
|
||||
if (!$schema) {
|
||||
$ret = HTMLPurifier_Config::createDefault();
|
||||
} else {
|
||||
$ret = new HTMLPurifier_Config($schema);
|
||||
}
|
||||
if (is_string($config)) $ret->loadIni($config);
|
||||
elseif (is_array($config)) $ret->loadArray($config);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new config object that inherits from a previous one.
|
||||
* @param HTMLPurifier_Config $config Configuration object to inherit
|
||||
* from.
|
||||
* @return HTMLPurifier_Config object with $config as its parent.
|
||||
*/
|
||||
public static function inherit(HTMLPurifier_Config $config) {
|
||||
return new HTMLPurifier_Config($config->def, $config->plist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor that creates a default configuration object.
|
||||
* @return Default HTMLPurifier_Config object.
|
||||
*/
|
||||
public static function createDefault() {
|
||||
$definition = HTMLPurifier_ConfigSchema::instance();
|
||||
$config = new HTMLPurifier_Config($definition);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives a value from the configuration.
|
||||
* @param $key String key
|
||||
*/
|
||||
public function get($key, $a = null) {
|
||||
if ($a !== null) {
|
||||
$this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING);
|
||||
$key = "$key.$a";
|
||||
}
|
||||
if (!$this->finalized) $this->autoFinalize();
|
||||
if (!isset($this->def->info[$key])) {
|
||||
// can't add % due to SimpleTest bug
|
||||
$this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
|
||||
E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
if (isset($this->def->info[$key]->isAlias)) {
|
||||
$d = $this->def->info[$key];
|
||||
$this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key,
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if ($this->lock) {
|
||||
list($ns) = explode('.', $key);
|
||||
if ($ns !== $this->lock) {
|
||||
$this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return $this->plist->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives an array of directives to values from a given namespace
|
||||
* @param $namespace String namespace
|
||||
*/
|
||||
public function getBatch($namespace) {
|
||||
if (!$this->finalized) $this->autoFinalize();
|
||||
$full = $this->getAll();
|
||||
if (!isset($full[$namespace])) {
|
||||
$this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
|
||||
E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
return $full[$namespace];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a md5 signature of a segment of the configuration object
|
||||
* that uniquely identifies that particular configuration
|
||||
* @note Revision is handled specially and is removed from the batch
|
||||
* before processing!
|
||||
* @param $namespace Namespace to get serial for
|
||||
*/
|
||||
public function getBatchSerial($namespace) {
|
||||
if (empty($this->serials[$namespace])) {
|
||||
$batch = $this->getBatch($namespace);
|
||||
unset($batch['DefinitionRev']);
|
||||
$this->serials[$namespace] = md5(serialize($batch));
|
||||
}
|
||||
return $this->serials[$namespace];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a md5 signature for the entire configuration object
|
||||
* that uniquely identifies that particular configuration
|
||||
*/
|
||||
public function getSerial() {
|
||||
if (empty($this->serial)) {
|
||||
$this->serial = md5(serialize($this->getAll()));
|
||||
}
|
||||
return $this->serial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all directives, organized by namespace
|
||||
* @warning This is a pretty inefficient function, avoid if you can
|
||||
*/
|
||||
public function getAll() {
|
||||
if (!$this->finalized) $this->autoFinalize();
|
||||
$ret = array();
|
||||
foreach ($this->plist->squash() as $name => $value) {
|
||||
list($ns, $key) = explode('.', $name, 2);
|
||||
$ret[$ns][$key] = $value;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value to configuration.
|
||||
* @param $key String key
|
||||
* @param $value Mixed value
|
||||
*/
|
||||
public function set($key, $value, $a = null) {
|
||||
if (strpos($key, '.') === false) {
|
||||
$namespace = $key;
|
||||
$directive = $value;
|
||||
$value = $a;
|
||||
$key = "$key.$directive";
|
||||
$this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
|
||||
} else {
|
||||
list($namespace) = explode('.', $key);
|
||||
}
|
||||
if ($this->isFinalized('Cannot set directive after finalization')) return;
|
||||
if (!isset($this->def->info[$key])) {
|
||||
$this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
|
||||
E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
$def = $this->def->info[$key];
|
||||
|
||||
if (isset($def->isAlias)) {
|
||||
if ($this->aliasMode) {
|
||||
$this->triggerError('Double-aliases not allowed, please fix '.
|
||||
'ConfigSchema bug with' . $key, E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$this->aliasMode = true;
|
||||
$this->set($def->key, $value);
|
||||
$this->aliasMode = false;
|
||||
$this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Raw type might be negative when using the fully optimized form
|
||||
// of stdclass, which indicates allow_null == true
|
||||
$rtype = is_int($def) ? $def : $def->type;
|
||||
if ($rtype < 0) {
|
||||
$type = -$rtype;
|
||||
$allow_null = true;
|
||||
} else {
|
||||
$type = $rtype;
|
||||
$allow_null = isset($def->allow_null);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $this->parser->parse($value, $type, $allow_null);
|
||||
} catch (HTMLPurifier_VarParserException $e) {
|
||||
$this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
if (is_string($value) && is_object($def)) {
|
||||
// resolve value alias if defined
|
||||
if (isset($def->aliases[$value])) {
|
||||
$value = $def->aliases[$value];
|
||||
}
|
||||
// check to see if the value is allowed
|
||||
if (isset($def->allowed) && !isset($def->allowed[$value])) {
|
||||
$this->triggerError('Value not supported, valid values are: ' .
|
||||
$this->_listify($def->allowed), E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->plist->set($key, $value);
|
||||
|
||||
// reset definitions if the directives they depend on changed
|
||||
// this is a very costly process, so it's discouraged
|
||||
// with finalization
|
||||
if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
|
||||
$this->definitions[$namespace] = null;
|
||||
}
|
||||
|
||||
$this->serials[$namespace] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for error reporting
|
||||
*/
|
||||
private function _listify($lookup) {
|
||||
$list = array();
|
||||
foreach ($lookup as $name => $b) $list[] = $name;
|
||||
return implode(', ', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves object reference to the HTML definition.
|
||||
* @param $raw Return a copy that has not been setup yet. Must be
|
||||
* called before it's been setup, otherwise won't work.
|
||||
*/
|
||||
public function getHTMLDefinition($raw = false) {
|
||||
return $this->getDefinition('HTML', $raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves object reference to the CSS definition
|
||||
* @param $raw Return a copy that has not been setup yet. Must be
|
||||
* called before it's been setup, otherwise won't work.
|
||||
*/
|
||||
public function getCSSDefinition($raw = false) {
|
||||
return $this->getDefinition('CSS', $raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a definition
|
||||
* @param $type Type of definition: HTML, CSS, etc
|
||||
* @param $raw Whether or not definition should be returned raw
|
||||
*/
|
||||
public function getDefinition($type, $raw = false) {
|
||||
if (!$this->finalized) $this->autoFinalize();
|
||||
// temporarily suspend locks, so we can handle recursive definition calls
|
||||
$lock = $this->lock;
|
||||
$this->lock = null;
|
||||
$factory = HTMLPurifier_DefinitionCacheFactory::instance();
|
||||
$cache = $factory->create($type, $this);
|
||||
$this->lock = $lock;
|
||||
if (!$raw) {
|
||||
// see if we can quickly supply a definition
|
||||
if (!empty($this->definitions[$type])) {
|
||||
if (!$this->definitions[$type]->setup) {
|
||||
$this->definitions[$type]->setup($this);
|
||||
$cache->set($this->definitions[$type], $this);
|
||||
}
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
// memory check missed, try cache
|
||||
$this->definitions[$type] = $cache->get($this);
|
||||
if ($this->definitions[$type]) {
|
||||
// definition in cache, return it
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
} elseif (
|
||||
!empty($this->definitions[$type]) &&
|
||||
!$this->definitions[$type]->setup
|
||||
) {
|
||||
// raw requested, raw in memory, quick return
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
// quick checks failed, let's create the object
|
||||
if ($type == 'HTML') {
|
||||
$this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
|
||||
} elseif ($type == 'CSS') {
|
||||
$this->definitions[$type] = new HTMLPurifier_CSSDefinition();
|
||||
} elseif ($type == 'URI') {
|
||||
$this->definitions[$type] = new HTMLPurifier_URIDefinition();
|
||||
} else {
|
||||
throw new HTMLPurifier_Exception("Definition of $type type not supported");
|
||||
}
|
||||
// quick abort if raw
|
||||
if ($raw) {
|
||||
if (is_null($this->get($type . '.DefinitionID'))) {
|
||||
// fatally error out if definition ID not set
|
||||
throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
|
||||
}
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
// set it up
|
||||
$this->lock = $type;
|
||||
$this->definitions[$type]->setup($this);
|
||||
$this->lock = null;
|
||||
// save in cache
|
||||
$cache->set($this->definitions[$type], $this);
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration values from an array with the following structure:
|
||||
* Namespace.Directive => Value
|
||||
* @param $config_array Configuration associative array
|
||||
*/
|
||||
public function loadArray($config_array) {
|
||||
if ($this->isFinalized('Cannot load directives after finalization')) return;
|
||||
foreach ($config_array as $key => $value) {
|
||||
$key = str_replace('_', '.', $key);
|
||||
if (strpos($key, '.') !== false) {
|
||||
$this->set($key, $value);
|
||||
} else {
|
||||
$namespace = $key;
|
||||
$namespace_values = $value;
|
||||
foreach ($namespace_values as $directive => $value) {
|
||||
$this->set($namespace .'.'. $directive, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of array(namespace, directive) for all directives
|
||||
* that are allowed in a web-form context as per an allowed
|
||||
* namespaces/directives list.
|
||||
* @param $allowed List of allowed namespaces/directives
|
||||
*/
|
||||
public static function getAllowedDirectivesForForm($allowed, $schema = null) {
|
||||
if (!$schema) {
|
||||
$schema = HTMLPurifier_ConfigSchema::instance();
|
||||
}
|
||||
if ($allowed !== true) {
|
||||
if (is_string($allowed)) $allowed = array($allowed);
|
||||
$allowed_ns = array();
|
||||
$allowed_directives = array();
|
||||
$blacklisted_directives = array();
|
||||
foreach ($allowed as $ns_or_directive) {
|
||||
if (strpos($ns_or_directive, '.') !== false) {
|
||||
// directive
|
||||
if ($ns_or_directive[0] == '-') {
|
||||
$blacklisted_directives[substr($ns_or_directive, 1)] = true;
|
||||
} else {
|
||||
$allowed_directives[$ns_or_directive] = true;
|
||||
}
|
||||
} else {
|
||||
// namespace
|
||||
$allowed_ns[$ns_or_directive] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$ret = array();
|
||||
foreach ($schema->info as $key => $def) {
|
||||
list($ns, $directive) = explode('.', $key, 2);
|
||||
if ($allowed !== true) {
|
||||
if (isset($blacklisted_directives["$ns.$directive"])) continue;
|
||||
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
|
||||
}
|
||||
if (isset($def->isAlias)) continue;
|
||||
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
|
||||
$ret[] = array($ns, $directive);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration values from $_GET/$_POST that were posted
|
||||
* via ConfigForm
|
||||
* @param $array $_GET or $_POST array to import
|
||||
* @param $index Index/name that the config variables are in
|
||||
* @param $allowed List of allowed namespaces/directives
|
||||
* @param $mq_fix Boolean whether or not to enable magic quotes fix
|
||||
* @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy
|
||||
*/
|
||||
public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
|
||||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
|
||||
$config = HTMLPurifier_Config::create($ret, $schema);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
|
||||
* @note Same parameters as loadArrayFromForm
|
||||
*/
|
||||
public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) {
|
||||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
|
||||
$this->loadArray($ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an array from a form into something usable for the more
|
||||
* strict parts of HTMLPurifier_Config
|
||||
*/
|
||||
public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
|
||||
if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
|
||||
$mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
|
||||
|
||||
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
|
||||
$ret = array();
|
||||
foreach ($allowed as $key) {
|
||||
list($ns, $directive) = $key;
|
||||
$skey = "$ns.$directive";
|
||||
if (!empty($array["Null_$skey"])) {
|
||||
$ret[$ns][$directive] = null;
|
||||
continue;
|
||||
}
|
||||
if (!isset($array[$skey])) continue;
|
||||
$value = $mq ? stripslashes($array[$skey]) : $array[$skey];
|
||||
$ret[$ns][$directive] = $value;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration values from an ini file
|
||||
* @param $filename Name of ini file
|
||||
*/
|
||||
public function loadIni($filename) {
|
||||
if ($this->isFinalized('Cannot load directives after finalization')) return;
|
||||
$array = parse_ini_file($filename, true);
|
||||
$this->loadArray($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the configuration object is finalized.
|
||||
* @param $error String error message, or false for no error
|
||||
*/
|
||||
public function isFinalized($error = false) {
|
||||
if ($this->finalized && $error) {
|
||||
$this->triggerError($error, E_USER_ERROR);
|
||||
}
|
||||
return $this->finalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes configuration only if auto finalize is on and not
|
||||
* already finalized
|
||||
*/
|
||||
public function autoFinalize() {
|
||||
if ($this->autoFinalize) {
|
||||
$this->finalize();
|
||||
} else {
|
||||
$this->plist->squash(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes a configuration object, prohibiting further change
|
||||
*/
|
||||
public function finalize() {
|
||||
$this->finalized = true;
|
||||
unset($this->parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a nicely formatted error message by supplying the
|
||||
* stack frame information from two levels up and OUTSIDE of
|
||||
* HTMLPurifier_Config.
|
||||
*/
|
||||
protected function triggerError($msg, $no) {
|
||||
// determine previous stack frame
|
||||
$backtrace = debug_backtrace();
|
||||
if ($this->chatty && isset($backtrace[1])) {
|
||||
$frame = $backtrace[1];
|
||||
$extra = " on line {$frame['line']} in file {$frame['file']}";
|
||||
} else {
|
||||
$extra = '';
|
||||
}
|
||||
trigger_error($msg . $extra, $no);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a serialized form of the configuration object that can
|
||||
* be reconstituted.
|
||||
*/
|
||||
public function serialize() {
|
||||
$this->getDefinition('HTML');
|
||||
$this->getDefinition('CSS');
|
||||
$this->getDefinition('URI');
|
||||
return serialize($this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Fluent interface for validating the contents of member variables.
|
||||
* This should be immutable. See HTMLPurifier_ConfigSchema_Validator for
|
||||
* use-cases. We name this an 'atom' because it's ONLY for validations that
|
||||
* are independent and usually scalar.
|
||||
*/
|
||||
class HTMLPurifier_ConfigSchema_ValidatorAtom
|
||||
{
|
||||
|
||||
protected $context, $obj, $member, $contents;
|
||||
|
||||
public function __construct($context, $obj, $member) {
|
||||
$this->context = $context;
|
||||
$this->obj = $obj;
|
||||
$this->member = $member;
|
||||
$this->contents =& $obj->$member;
|
||||
}
|
||||
|
||||
public function assertIsString() {
|
||||
if (!is_string($this->contents)) $this->error('must be a string');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assertIsBool() {
|
||||
if (!is_bool($this->contents)) $this->error('must be a boolean');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assertIsArray() {
|
||||
if (!is_array($this->contents)) $this->error('must be an array');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assertNotNull() {
|
||||
if ($this->contents === null) $this->error('must not be null');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assertAlnum() {
|
||||
$this->assertIsString();
|
||||
if (!ctype_alnum($this->contents)) $this->error('must be alphanumeric');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assertNotEmpty() {
|
||||
if (empty($this->contents)) $this->error('must not be empty');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assertIsLookup() {
|
||||
$this->assertIsArray();
|
||||
foreach ($this->contents as $v) {
|
||||
if ($v !== true) $this->error('must be a lookup array');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function error($msg) {
|
||||
throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
Binary file not shown.
|
|
@ -1,18 +0,0 @@
|
|||
HTML.AllowedElements
|
||||
TYPE: lookup/null
|
||||
VERSION: 1.3.0
|
||||
DEFAULT: NULL
|
||||
--DESCRIPTION--
|
||||
<p>
|
||||
If HTML Purifier's tag set is unsatisfactory for your needs, you
|
||||
can overload it with your own list of tags to allow. Note that this
|
||||
method is subtractive: it does its job by taking away from HTML Purifier
|
||||
usual feature set, so you cannot add a tag that HTML Purifier never
|
||||
supported in the first place (like embed, form or head). If you
|
||||
change this, you probably also want to change %HTML.AllowedAttributes.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Warning:</strong> If another directive conflicts with the
|
||||
elements here, <em>that</em> directive will win and override.
|
||||
</p>
|
||||
--# vim: et sw=4 sts=4
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Registry object that contains information about the current context.
|
||||
* @warning Is a bit buggy when variables are set to null: it thinks
|
||||
* they don't exist! So use false instead, please.
|
||||
* @note Since the variables Context deals with may not be objects,
|
||||
* references are very important here! Do not remove!
|
||||
*/
|
||||
class HTMLPurifier_Context
|
||||
{
|
||||
|
||||
/**
|
||||
* Private array that stores the references.
|
||||
*/
|
||||
private $_storage = array();
|
||||
|
||||
/**
|
||||
* Registers a variable into the context.
|
||||
* @param $name String name
|
||||
* @param $ref Reference to variable to be registered
|
||||
*/
|
||||
public function register($name, &$ref) {
|
||||
if (isset($this->_storage[$name])) {
|
||||
trigger_error("Name $name produces collision, cannot re-register",
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$this->_storage[$name] =& $ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a variable reference from the context.
|
||||
* @param $name String name
|
||||
* @param $ignore_error Boolean whether or not to ignore error
|
||||
*/
|
||||
public function &get($name, $ignore_error = false) {
|
||||
if (!isset($this->_storage[$name])) {
|
||||
if (!$ignore_error) {
|
||||
trigger_error("Attempted to retrieve non-existent variable $name",
|
||||
E_USER_ERROR);
|
||||
}
|
||||
$var = null; // so we can return by reference
|
||||
return $var;
|
||||
}
|
||||
return $this->_storage[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Destorys a variable in the context.
|
||||
* @param $name String name
|
||||
*/
|
||||
public function destroy($name) {
|
||||
if (!isset($this->_storage[$name])) {
|
||||
trigger_error("Attempted to destroy non-existent variable $name",
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
unset($this->_storage[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the variable exists.
|
||||
* @param $name String name
|
||||
*/
|
||||
public function exists($name) {
|
||||
return isset($this->_storage[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a series of variables from an associative array
|
||||
* @param $context_array Assoc array of variables to load
|
||||
*/
|
||||
public function loadArray($context_array) {
|
||||
foreach ($context_array as $key => $discard) {
|
||||
$this->register($key, $context_array[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Super-class for definition datatype objects, implements serialization
|
||||
* functions for the class.
|
||||
*/
|
||||
abstract class HTMLPurifier_Definition
|
||||
{
|
||||
|
||||
/**
|
||||
* Has setup() been called yet?
|
||||
*/
|
||||
public $setup = false;
|
||||
|
||||
/**
|
||||
* What type of definition is it?
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Sets up the definition object into the final form, something
|
||||
* not done by the constructor
|
||||
* @param $config HTMLPurifier_Config instance
|
||||
*/
|
||||
abstract protected function doSetup($config);
|
||||
|
||||
/**
|
||||
* Setup function that aborts if already setup
|
||||
* @param $config HTMLPurifier_Config instance
|
||||
*/
|
||||
public function setup($config) {
|
||||
if ($this->setup) return;
|
||||
$this->setup = true;
|
||||
$this->doSetup($config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
|
||||
{
|
||||
|
||||
/**
|
||||
* Cache object we are decorating
|
||||
*/
|
||||
public $cache;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Lazy decorator function
|
||||
* @param $cache Reference to cache object to decorate
|
||||
*/
|
||||
public function decorate(&$cache) {
|
||||
$decorator = $this->copy();
|
||||
// reference is necessary for mocks in PHP 4
|
||||
$decorator->cache =& $cache;
|
||||
$decorator->type = $cache->type;
|
||||
return $decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-compatible clone substitute
|
||||
*/
|
||||
public function copy() {
|
||||
return new HTMLPurifier_DefinitionCache_Decorator();
|
||||
}
|
||||
|
||||
public function add($def, $config) {
|
||||
return $this->cache->add($def, $config);
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
return $this->cache->set($def, $config);
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
return $this->cache->replace($def, $config);
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
return $this->cache->get($config);
|
||||
}
|
||||
|
||||
public function remove($config) {
|
||||
return $this->cache->remove($config);
|
||||
}
|
||||
|
||||
public function flush($config) {
|
||||
return $this->cache->flush($config);
|
||||
}
|
||||
|
||||
public function cleanup($config) {
|
||||
return $this->cache->cleanup($config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Definition cache decorator class that cleans up the cache
|
||||
* whenever there is a cache miss.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends
|
||||
HTMLPurifier_DefinitionCache_Decorator
|
||||
{
|
||||
|
||||
public $name = 'Cleanup';
|
||||
|
||||
public function copy() {
|
||||
return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
|
||||
}
|
||||
|
||||
public function add($def, $config) {
|
||||
$status = parent::add($def, $config);
|
||||
if (!$status) parent::cleanup($config);
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
$status = parent::set($def, $config);
|
||||
if (!$status) parent::cleanup($config);
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
$status = parent::replace($def, $config);
|
||||
if (!$status) parent::cleanup($config);
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
$ret = parent::get($config);
|
||||
if (!$ret) parent::cleanup($config);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Definition cache decorator class that saves all cache retrievals
|
||||
* to PHP's memory; good for unit tests or circumstances where
|
||||
* there are lots of configuration objects floating around.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Decorator_Memory extends
|
||||
HTMLPurifier_DefinitionCache_Decorator
|
||||
{
|
||||
|
||||
protected $definitions;
|
||||
public $name = 'Memory';
|
||||
|
||||
public function copy() {
|
||||
return new HTMLPurifier_DefinitionCache_Decorator_Memory();
|
||||
}
|
||||
|
||||
public function add($def, $config) {
|
||||
$status = parent::add($def, $config);
|
||||
if ($status) $this->definitions[$this->generateKey($config)] = $def;
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
$status = parent::set($def, $config);
|
||||
if ($status) $this->definitions[$this->generateKey($config)] = $def;
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
$status = parent::replace($def, $config);
|
||||
if ($status) $this->definitions[$this->generateKey($config)] = $def;
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
$key = $this->generateKey($config);
|
||||
if (isset($this->definitions[$key])) return $this->definitions[$key];
|
||||
$this->definitions[$key] = parent::get($config);
|
||||
return $this->definitions[$key];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
|
||||
|
||||
/**
|
||||
* Definition cache decorator template.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Decorator_Template extends
|
||||
HTMLPurifier_DefinitionCache_Decorator
|
||||
{
|
||||
|
||||
var $name = 'Template'; // replace this
|
||||
|
||||
function copy() {
|
||||
// replace class name with yours
|
||||
return new HTMLPurifier_DefinitionCache_Decorator_Template();
|
||||
}
|
||||
|
||||
// remove methods you don't need
|
||||
|
||||
function add($def, $config) {
|
||||
return parent::add($def, $config);
|
||||
}
|
||||
|
||||
function set($def, $config) {
|
||||
return parent::set($def, $config);
|
||||
}
|
||||
|
||||
function replace($def, $config) {
|
||||
return parent::replace($def, $config);
|
||||
}
|
||||
|
||||
function get($config) {
|
||||
return parent::get($config);
|
||||
}
|
||||
|
||||
function flush() {
|
||||
return parent::flush();
|
||||
}
|
||||
|
||||
function cleanup($config) {
|
||||
return parent::cleanup($config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Null cache object to use when no caching is on.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
|
||||
{
|
||||
|
||||
public function add($def, $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function remove($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function flush($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function cleanup($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_DefinitionCache_Serializer extends
|
||||
HTMLPurifier_DefinitionCache
|
||||
{
|
||||
|
||||
public function add($def, $config) {
|
||||
if (!$this->checkDefType($def)) return;
|
||||
$file = $this->generateFilePath($config);
|
||||
if (file_exists($file)) return false;
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
return $this->_write($file, serialize($def));
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
if (!$this->checkDefType($def)) return;
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
return $this->_write($file, serialize($def));
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
if (!$this->checkDefType($def)) return;
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!file_exists($file)) return false;
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
return $this->_write($file, serialize($def));
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!file_exists($file)) return false;
|
||||
return unserialize(file_get_contents($file));
|
||||
}
|
||||
|
||||
public function remove($config) {
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!file_exists($file)) return false;
|
||||
return unlink($file);
|
||||
}
|
||||
|
||||
public function flush($config) {
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
$dir = $this->generateDirectoryPath($config);
|
||||
$dh = opendir($dir);
|
||||
while (false !== ($filename = readdir($dh))) {
|
||||
if (empty($filename)) continue;
|
||||
if ($filename[0] === '.') continue;
|
||||
unlink($dir . '/' . $filename);
|
||||
}
|
||||
}
|
||||
|
||||
public function cleanup($config) {
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
$dir = $this->generateDirectoryPath($config);
|
||||
$dh = opendir($dir);
|
||||
while (false !== ($filename = readdir($dh))) {
|
||||
if (empty($filename)) continue;
|
||||
if ($filename[0] === '.') continue;
|
||||
$key = substr($filename, 0, strlen($filename) - 4);
|
||||
if ($this->isOld($key, $config)) unlink($dir . '/' . $filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the file path to the serial file corresponding to
|
||||
* the configuration and definition name
|
||||
* @todo Make protected
|
||||
*/
|
||||
public function generateFilePath($config) {
|
||||
$key = $this->generateKey($config);
|
||||
return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the path to the directory contain this cache's serial files
|
||||
* @note No trailing slash
|
||||
* @todo Make protected
|
||||
*/
|
||||
public function generateDirectoryPath($config) {
|
||||
$base = $this->generateBaseDirectoryPath($config);
|
||||
return $base . '/' . $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates path to base directory that contains all definition type
|
||||
* serials
|
||||
* @todo Make protected
|
||||
*/
|
||||
public function generateBaseDirectoryPath($config) {
|
||||
$base = $config->get('Cache.SerializerPath');
|
||||
$base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper function for file_put_contents
|
||||
* @param $file File name to write to
|
||||
* @param $data Data to write into file
|
||||
* @return Number of bytes written if success, or false if failure.
|
||||
*/
|
||||
private function _write($file, $data) {
|
||||
return file_put_contents($file, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the directory that this type stores the serials in
|
||||
* @return True if successful
|
||||
*/
|
||||
private function _prepareDir($config) {
|
||||
$directory = $this->generateDirectoryPath($config);
|
||||
if (!is_dir($directory)) {
|
||||
$base = $this->generateBaseDirectoryPath($config);
|
||||
if (!is_dir($base)) {
|
||||
trigger_error('Base directory '.$base.' does not exist,
|
||||
please create or change using %Cache.SerializerPath',
|
||||
E_USER_WARNING);
|
||||
return false;
|
||||
} elseif (!$this->_testPermissions($base)) {
|
||||
return false;
|
||||
}
|
||||
$old = umask(0022); // disable group and world writes
|
||||
mkdir($directory);
|
||||
umask($old);
|
||||
} elseif (!$this->_testPermissions($directory)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests permissions on a directory and throws out friendly
|
||||
* error messages and attempts to chmod it itself if possible
|
||||
*/
|
||||
private function _testPermissions($dir) {
|
||||
// early abort, if it is writable, everything is hunky-dory
|
||||
if (is_writable($dir)) return true;
|
||||
if (!is_dir($dir)) {
|
||||
// generally, you'll want to handle this beforehand
|
||||
// so a more specific error message can be given
|
||||
trigger_error('Directory '.$dir.' does not exist',
|
||||
E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
if (function_exists('posix_getuid')) {
|
||||
// POSIX system, we can give more specific advice
|
||||
if (fileowner($dir) === posix_getuid()) {
|
||||
// we can chmod it ourselves
|
||||
chmod($dir, 0755);
|
||||
return true;
|
||||
} elseif (filegroup($dir) === posix_getgid()) {
|
||||
$chmod = '775';
|
||||
} else {
|
||||
// PHP's probably running as nobody, so we'll
|
||||
// need to give global permissions
|
||||
$chmod = '777';
|
||||
}
|
||||
trigger_error('Directory '.$dir.' not writable, '.
|
||||
'please chmod to ' . $chmod,
|
||||
E_USER_WARNING);
|
||||
} else {
|
||||
// generic error message
|
||||
trigger_error('Directory '.$dir.' not writable, '.
|
||||
'please alter file permissions',
|
||||
E_USER_WARNING);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,135 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This filter extracts <style> blocks from input HTML, cleans them up
|
||||
* using CSSTidy, and then places them in $purifier->context->get('StyleBlocks')
|
||||
* so they can be used elsewhere in the document.
|
||||
*
|
||||
* @note
|
||||
* See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for
|
||||
* sample usage.
|
||||
*
|
||||
* @note
|
||||
* This filter can also be used on stylesheets not included in the
|
||||
* document--something purists would probably prefer. Just directly
|
||||
* call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS()
|
||||
*/
|
||||
class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
|
||||
{
|
||||
|
||||
public $name = 'ExtractStyleBlocks';
|
||||
private $_styleMatches = array();
|
||||
private $_tidy;
|
||||
|
||||
public function __construct() {
|
||||
$this->_tidy = new csstidy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the contents of CSS blocks to style matches
|
||||
* @param $matches preg_replace style $matches array
|
||||
*/
|
||||
protected function styleCallback($matches) {
|
||||
$this->_styleMatches[] = $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes inline <style> tags from HTML, saves them for later use
|
||||
* @todo Extend to indicate non-text/css style blocks
|
||||
*/
|
||||
public function preFilter($html, $config, $context) {
|
||||
$tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl');
|
||||
if ($tidy !== null) $this->_tidy = $tidy;
|
||||
$html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html);
|
||||
$style_blocks = $this->_styleMatches;
|
||||
$this->_styleMatches = array(); // reset
|
||||
$context->register('StyleBlocks', $style_blocks); // $context must not be reused
|
||||
if ($this->_tidy) {
|
||||
foreach ($style_blocks as &$style) {
|
||||
$style = $this->cleanCSS($style, $config, $context);
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes CSS (the stuff found in <style>) and cleans it.
|
||||
* @warning Requires CSSTidy <http://csstidy.sourceforge.net/>
|
||||
* @param $css CSS styling to clean
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return Cleaned CSS
|
||||
*/
|
||||
public function cleanCSS($css, $config, $context) {
|
||||
// prepare scope
|
||||
$scope = $config->get('Filter.ExtractStyleBlocks.Scope');
|
||||
if ($scope !== null) {
|
||||
$scopes = array_map('trim', explode(',', $scope));
|
||||
} else {
|
||||
$scopes = array();
|
||||
}
|
||||
// remove comments from CSS
|
||||
$css = trim($css);
|
||||
if (strncmp('<!--', $css, 4) === 0) {
|
||||
$css = substr($css, 4);
|
||||
}
|
||||
if (strlen($css) > 3 && substr($css, -3) == '-->') {
|
||||
$css = substr($css, 0, -3);
|
||||
}
|
||||
$css = trim($css);
|
||||
$this->_tidy->parse($css);
|
||||
$css_definition = $config->getDefinition('CSS');
|
||||
foreach ($this->_tidy->css as $k => $decls) {
|
||||
// $decls are all CSS declarations inside an @ selector
|
||||
$new_decls = array();
|
||||
foreach ($decls as $selector => $style) {
|
||||
$selector = trim($selector);
|
||||
if ($selector === '') continue; // should not happen
|
||||
if ($selector[0] === '+') {
|
||||
if ($selector !== '' && $selector[0] === '+') continue;
|
||||
}
|
||||
if (!empty($scopes)) {
|
||||
$new_selector = array(); // because multiple ones are possible
|
||||
$selectors = array_map('trim', explode(',', $selector));
|
||||
foreach ($scopes as $s1) {
|
||||
foreach ($selectors as $s2) {
|
||||
$new_selector[] = "$s1 $s2";
|
||||
}
|
||||
}
|
||||
$selector = implode(', ', $new_selector); // now it's a string
|
||||
}
|
||||
foreach ($style as $name => $value) {
|
||||
if (!isset($css_definition->info[$name])) {
|
||||
unset($style[$name]);
|
||||
continue;
|
||||
}
|
||||
$def = $css_definition->info[$name];
|
||||
$ret = $def->validate($value, $config, $context);
|
||||
if ($ret === false) unset($style[$name]);
|
||||
else $style[$name] = $ret;
|
||||
}
|
||||
$new_decls[$selector] = $style;
|
||||
}
|
||||
$this->_tidy->css[$k] = $new_decls;
|
||||
}
|
||||
// remove stuff that shouldn't be used, could be reenabled
|
||||
// after security risks are analyzed
|
||||
$this->_tidy->import = array();
|
||||
$this->_tidy->charset = null;
|
||||
$this->_tidy->namespace = null;
|
||||
$css = $this->_tidy->print->plain();
|
||||
// we are going to escape any special characters <>& to ensure
|
||||
// that no funny business occurs (i.e. </style> in a font-family prop).
|
||||
if ($config->get('Filter.ExtractStyleBlocks.Escaping')) {
|
||||
$css = str_replace(
|
||||
array('<', '>', '&'),
|
||||
array('\3C ', '\3E ', '\26 '),
|
||||
$css
|
||||
);
|
||||
}
|
||||
return $css;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter
|
||||
{
|
||||
|
||||
public $name = 'YouTube';
|
||||
|
||||
public function preFilter($html, $config, $context) {
|
||||
$pre_regex = '#<object[^>]+>.+?'.
|
||||
'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?</object>#s';
|
||||
$pre_replace = '<span class="youtube-embed">\1</span>';
|
||||
return preg_replace($pre_regex, $pre_replace, $html);
|
||||
}
|
||||
|
||||
public function postFilter($html, $config, $context) {
|
||||
$post_regex = '#<span class="youtube-embed">((?:v|cp)/[A-Za-z0-9\-_=]+)</span>#';
|
||||
return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html);
|
||||
}
|
||||
|
||||
protected function armorUrl($url) {
|
||||
return str_replace('--', '--', $url);
|
||||
}
|
||||
|
||||
protected function postFilterCallback($matches) {
|
||||
$url = $this->armorUrl($matches[1]);
|
||||
return '<object width="425" height="350" type="application/x-shockwave-flash" '.
|
||||
'data="http://www.youtube.com/'.$url.'">'.
|
||||
'<param name="movie" value="http://www.youtube.com/'.$url.'"></param>'.
|
||||
'<!--[if IE]>'.
|
||||
'<embed src="http://www.youtube.com/'.$url.'"'.
|
||||
'type="application/x-shockwave-flash"'.
|
||||
'wmode="transparent" width="425" height="350" />'.
|
||||
'<![endif]-->'.
|
||||
'</object>';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
public $name = 'Forms';
|
||||
public $safe = false;
|
||||
|
||||
public $content_sets = array(
|
||||
'Block' => 'Form',
|
||||
'Inline' => 'Formctrl',
|
||||
);
|
||||
|
||||
public function setup($config) {
|
||||
$form = $this->addElement('form', 'Form',
|
||||
'Required: Heading | List | Block | fieldset', 'Common', array(
|
||||
'accept' => 'ContentTypes',
|
||||
'accept-charset' => 'Charsets',
|
||||
'action*' => 'URI',
|
||||
'method' => 'Enum#get,post',
|
||||
// really ContentType, but these two are the only ones used today
|
||||
'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
|
||||
));
|
||||
$form->excludes = array('form' => true);
|
||||
|
||||
$input = $this->addElement('input', 'Formctrl', 'Empty', 'Common', array(
|
||||
'accept' => 'ContentTypes',
|
||||
'accesskey' => 'Character',
|
||||
'alt' => 'Text',
|
||||
'checked' => 'Bool#checked',
|
||||
'disabled' => 'Bool#disabled',
|
||||
'maxlength' => 'Number',
|
||||
'name' => 'CDATA',
|
||||
'readonly' => 'Bool#readonly',
|
||||
'size' => 'Number',
|
||||
'src' => 'URI#embeds',
|
||||
'tabindex' => 'Number',
|
||||
'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
|
||||
'value' => 'CDATA',
|
||||
));
|
||||
$input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
|
||||
|
||||
$this->addElement('select', 'Formctrl', 'Required: optgroup | option', 'Common', array(
|
||||
'disabled' => 'Bool#disabled',
|
||||
'multiple' => 'Bool#multiple',
|
||||
'name' => 'CDATA',
|
||||
'size' => 'Number',
|
||||
'tabindex' => 'Number',
|
||||
));
|
||||
|
||||
$this->addElement('option', false, 'Optional: #PCDATA', 'Common', array(
|
||||
'disabled' => 'Bool#disabled',
|
||||
'label' => 'Text',
|
||||
'selected' => 'Bool#selected',
|
||||
'value' => 'CDATA',
|
||||
));
|
||||
// It's illegal for there to be more than one selected, but not
|
||||
// be multiple. Also, no selected means undefined behavior. This might
|
||||
// be difficult to implement; perhaps an injector, or a context variable.
|
||||
|
||||
$textarea = $this->addElement('textarea', 'Formctrl', 'Optional: #PCDATA', 'Common', array(
|
||||
'accesskey' => 'Character',
|
||||
'cols*' => 'Number',
|
||||
'disabled' => 'Bool#disabled',
|
||||
'name' => 'CDATA',
|
||||
'readonly' => 'Bool#readonly',
|
||||
'rows*' => 'Number',
|
||||
'tabindex' => 'Number',
|
||||
));
|
||||
$textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
|
||||
|
||||
$button = $this->addElement('button', 'Formctrl', 'Optional: #PCDATA | Heading | List | Block | Inline', 'Common', array(
|
||||
'accesskey' => 'Character',
|
||||
'disabled' => 'Bool#disabled',
|
||||
'name' => 'CDATA',
|
||||
'tabindex' => 'Number',
|
||||
'type' => 'Enum#button,submit,reset',
|
||||
'value' => 'CDATA',
|
||||
));
|
||||
|
||||
// For exclusions, ideally we'd specify content sets, not literal elements
|
||||
$button->excludes = $this->makeLookup(
|
||||
'form', 'fieldset', // Form
|
||||
'input', 'select', 'textarea', 'label', 'button', // Formctrl
|
||||
'a' // as per HTML 4.01 spec, this is omitted by modularization
|
||||
);
|
||||
|
||||
// Extra exclusion: img usemap="" is not permitted within this element.
|
||||
// We'll omit this for now, since we don't have any good way of
|
||||
// indicating it yet.
|
||||
|
||||
// This is HIGHLY user-unfriendly; we need a custom child-def for this
|
||||
$this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
|
||||
|
||||
$label = $this->addElement('label', 'Formctrl', 'Optional: #PCDATA | Inline', 'Common', array(
|
||||
'accesskey' => 'Character',
|
||||
// 'for' => 'IDREF', // IDREF not implemented, cannot allow
|
||||
));
|
||||
$label->excludes = array('label' => true);
|
||||
|
||||
$this->addElement('legend', false, 'Optional: #PCDATA | Inline', 'Common', array(
|
||||
'accesskey' => 'Character',
|
||||
));
|
||||
|
||||
$this->addElement('optgroup', false, 'Required: option', 'Common', array(
|
||||
'disabled' => 'Bool#disabled',
|
||||
'label*' => 'Text',
|
||||
));
|
||||
|
||||
// Don't forget an injector for <isindex>. This one's a little complex
|
||||
// because it maps to multiple elements.
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
|
||||
{
|
||||
public $name = 'Tidy_Strict';
|
||||
public $defaultLevel = 'light';
|
||||
|
||||
public function makeFixes() {
|
||||
$r = parent::makeFixes();
|
||||
$r['blockquote#content_model_type'] = 'strictblockquote';
|
||||
return $r;
|
||||
}
|
||||
|
||||
public $defines_child_def = true;
|
||||
public function getChildDef($def) {
|
||||
if ($def->content_model_type != 'strictblockquote') return parent::getChildDef($def);
|
||||
return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
|
||||
{
|
||||
|
||||
private $context, $config, $attrValidator, $removeNbsp, $removeNbspExceptions;
|
||||
|
||||
public function prepare($config, $context) {
|
||||
parent::prepare($config, $context);
|
||||
$this->config = $config;
|
||||
$this->context = $context;
|
||||
$this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
|
||||
$this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
|
||||
$this->attrValidator = new HTMLPurifier_AttrValidator();
|
||||
}
|
||||
|
||||
public function handleElement(&$token) {
|
||||
if (!$token instanceof HTMLPurifier_Token_Start) return;
|
||||
$next = false;
|
||||
for ($i = $this->inputIndex + 1, $c = count($this->inputTokens); $i < $c; $i++) {
|
||||
$next = $this->inputTokens[$i];
|
||||
if ($next instanceof HTMLPurifier_Token_Text) {
|
||||
if ($next->is_whitespace) continue;
|
||||
if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
|
||||
$plain = str_replace("\xC2\xA0", "", $next->data);
|
||||
$isWsOrNbsp = $plain === '' || ctype_space($plain);
|
||||
if ($isWsOrNbsp) continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
|
||||
if ($token->name == 'colgroup') return;
|
||||
$this->attrValidator->validateToken($token, $this->config, $this->context);
|
||||
$token->armor['ValidateAttributes'] = true;
|
||||
if (isset($token->attr['id']) || isset($token->attr['name'])) return;
|
||||
$token = $i - $this->inputIndex + 1;
|
||||
for ($b = $this->inputIndex - 1; $b > 0; $b--) {
|
||||
$prev = $this->inputTokens[$b];
|
||||
if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) continue;
|
||||
break;
|
||||
}
|
||||
// This is safe because we removed the token that triggered this.
|
||||
$this->rewind($b - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
|
||||
$fallback = false;
|
||||
|
||||
$messages = array(
|
||||
|
||||
'HTMLPurifier' => 'HTML Purifier',
|
||||
|
||||
// for unit testing purposes
|
||||
'LanguageFactoryTest: Pizza' => 'Pizza',
|
||||
'LanguageTest: List' => '$1',
|
||||
'LanguageTest: Hash' => '$1.Keys; $1.Values',
|
||||
|
||||
'Item separator' => ', ',
|
||||
'Item separator last' => ' and ', // non-Harvard style
|
||||
|
||||
'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.',
|
||||
'ErrorCollector: At line' => ' at line $line',
|
||||
'ErrorCollector: Incidental errors' => 'Incidental errors',
|
||||
|
||||
'Lexer: Unclosed comment' => 'Unclosed comment',
|
||||
'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <',
|
||||
'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped',
|
||||
'Lexer: Missing attribute key' => 'Attribute declaration has no key',
|
||||
'Lexer: Missing end quote' => 'Attribute declaration has no end quote',
|
||||
'Lexer: Extracted body' => 'Removed document metadata tags',
|
||||
|
||||
'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized',
|
||||
'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1',
|
||||
'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text',
|
||||
'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed',
|
||||
'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed',
|
||||
'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed',
|
||||
'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end',
|
||||
'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed',
|
||||
'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens',
|
||||
|
||||
'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed',
|
||||
'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text',
|
||||
'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact',
|
||||
'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact',
|
||||
'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed',
|
||||
'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text',
|
||||
'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized',
|
||||
'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document',
|
||||
|
||||
'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed',
|
||||
'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element',
|
||||
'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model',
|
||||
'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed',
|
||||
|
||||
'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys',
|
||||
'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed',
|
||||
|
||||
);
|
||||
|
||||
$errorNames = array(
|
||||
E_ERROR => 'Error',
|
||||
E_WARNING => 'Warning',
|
||||
E_NOTICE => 'Notice'
|
||||
);
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Proof-of-concept lexer that uses the PEAR package XML_HTMLSax3 to parse HTML.
|
||||
*
|
||||
* PEAR, not suprisingly, also has a SAX parser for HTML. I don't know
|
||||
* very much about implementation, but it's fairly well written. However, that
|
||||
* abstraction comes at a price: performance. You need to have it installed,
|
||||
* and if the API changes, it might break our adapter. Not sure whether or not
|
||||
* it's UTF-8 aware, but it has some entity parsing trouble (in all areas,
|
||||
* text and attributes).
|
||||
*
|
||||
* Quite personally, I don't recommend using the PEAR class, and the defaults
|
||||
* don't use it. The unit tests do perform the tests on the SAX parser too, but
|
||||
* whatever it does for poorly formed HTML is up to it.
|
||||
*
|
||||
* @todo Generalize so that XML_HTMLSax is also supported.
|
||||
*
|
||||
* @warning Entity-resolution inside attributes is broken.
|
||||
*/
|
||||
|
||||
class HTMLPurifier_Lexer_PEARSax3 extends HTMLPurifier_Lexer
|
||||
{
|
||||
|
||||
/**
|
||||
* Internal accumulator array for SAX parsers.
|
||||
*/
|
||||
protected $tokens = array();
|
||||
protected $last_token_was_empty;
|
||||
|
||||
private $parent_handler;
|
||||
private $stack = array();
|
||||
|
||||
public function tokenizeHTML($string, $config, $context) {
|
||||
|
||||
$this->tokens = array();
|
||||
$this->last_token_was_empty = false;
|
||||
|
||||
$string = $this->normalize($string, $config, $context);
|
||||
|
||||
$this->parent_handler = set_error_handler(array($this, 'muteStrictErrorHandler'));
|
||||
|
||||
$parser = new XML_HTMLSax3();
|
||||
$parser->set_object($this);
|
||||
$parser->set_element_handler('openHandler','closeHandler');
|
||||
$parser->set_data_handler('dataHandler');
|
||||
$parser->set_escape_handler('escapeHandler');
|
||||
|
||||
// doesn't seem to work correctly for attributes
|
||||
$parser->set_option('XML_OPTION_ENTITIES_PARSED', 1);
|
||||
|
||||
$parser->parse($string);
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
return $this->tokens;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Open tag event handler, interface is defined by PEAR package.
|
||||
*/
|
||||
public function openHandler(&$parser, $name, $attrs, $closed) {
|
||||
// entities are not resolved in attrs
|
||||
foreach ($attrs as $key => $attr) {
|
||||
$attrs[$key] = $this->parseData($attr);
|
||||
}
|
||||
if ($closed) {
|
||||
$this->tokens[] = new HTMLPurifier_Token_Empty($name, $attrs);
|
||||
$this->last_token_was_empty = true;
|
||||
} else {
|
||||
$this->tokens[] = new HTMLPurifier_Token_Start($name, $attrs);
|
||||
}
|
||||
$this->stack[] = $name;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close tag event handler, interface is defined by PEAR package.
|
||||
*/
|
||||
public function closeHandler(&$parser, $name) {
|
||||
// HTMLSax3 seems to always send empty tags an extra close tag
|
||||
// check and ignore if you see it:
|
||||
// [TESTME] to make sure it doesn't overreach
|
||||
if ($this->last_token_was_empty) {
|
||||
$this->last_token_was_empty = false;
|
||||
return true;
|
||||
}
|
||||
$this->tokens[] = new HTMLPurifier_Token_End($name);
|
||||
if (!empty($this->stack)) array_pop($this->stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data event handler, interface is defined by PEAR package.
|
||||
*/
|
||||
public function dataHandler(&$parser, $data) {
|
||||
$this->last_token_was_empty = false;
|
||||
$this->tokens[] = new HTMLPurifier_Token_Text($data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escaped text handler, interface is defined by PEAR package.
|
||||
*/
|
||||
public function escapeHandler(&$parser, $data) {
|
||||
if (strpos($data, '--') === 0) {
|
||||
// remove trailing and leading double-dashes
|
||||
$data = substr($data, 2);
|
||||
if (strlen($data) >= 2 && substr($data, -2) == "--") {
|
||||
$data = substr($data, 0, -2);
|
||||
}
|
||||
if (isset($this->stack[sizeof($this->stack) - 1]) &&
|
||||
$this->stack[sizeof($this->stack) - 1] == "style") {
|
||||
$this->tokens[] = new HTMLPurifier_Token_Text($data);
|
||||
} else {
|
||||
$this->tokens[] = new HTMLPurifier_Token_Comment($data);
|
||||
}
|
||||
$this->last_token_was_empty = false;
|
||||
}
|
||||
// CDATA is handled elsewhere, but if it was handled here:
|
||||
//if (strpos($data, '[CDATA[') === 0) {
|
||||
// $this->tokens[] = new HTMLPurifier_Token_Text(
|
||||
// substr($data, 7, strlen($data) - 9) );
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* An error handler that mutes strict errors
|
||||
*/
|
||||
public function muteStrictErrorHandler($errno, $errstr, $errfile=null, $errline=null, $errcontext=null) {
|
||||
if ($errno == E_STRICT) return;
|
||||
return call_user_func($this->parent_handler, $errno, $errstr, $errfile, $errline, $errcontext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,3906 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Experimental HTML5-based parser using Jeroen van der Meer's PH5P library.
|
||||
* Occupies space in the HTML5 pseudo-namespace, which may cause conflicts.
|
||||
*
|
||||
* @note
|
||||
* Recent changes to PHP's DOM extension have resulted in some fatal
|
||||
* error conditions with the original version of PH5P. Pending changes,
|
||||
* this lexer will punt to DirectLex if DOM throughs an exception.
|
||||
*/
|
||||
|
||||
class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex {
|
||||
|
||||
public function tokenizeHTML($html, $config, $context) {
|
||||
$new_html = $this->normalize($html, $config, $context);
|
||||
$new_html = $this->wrapHTML($new_html, $config, $context);
|
||||
try {
|
||||
$parser = new HTML5($new_html);
|
||||
$doc = $parser->save();
|
||||
} catch (DOMException $e) {
|
||||
// Uh oh, it failed. Punt to DirectLex.
|
||||
$lexer = new HTMLPurifier_Lexer_DirectLex();
|
||||
$context->register('PH5PError', $e); // save the error, so we can detect it
|
||||
return $lexer->tokenizeHTML($html, $config, $context); // use original HTML
|
||||
}
|
||||
$tokens = array();
|
||||
$this->tokenizeDOM(
|
||||
$doc->getElementsByTagName('html')->item(0)-> // <html>
|
||||
getElementsByTagName('body')->item(0)-> // <body>
|
||||
getElementsByTagName('div')->item(0) // <div>
|
||||
, $tokens);
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2007 Jeroen van der Meer <http://jero.net/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class HTML5 {
|
||||
private $data;
|
||||
private $char;
|
||||
private $EOF;
|
||||
private $state;
|
||||
private $tree;
|
||||
private $token;
|
||||
private $content_model;
|
||||
private $escape = false;
|
||||
private $entities = array('AElig;','AElig','AMP;','AMP','Aacute;','Aacute',
|
||||
'Acirc;','Acirc','Agrave;','Agrave','Alpha;','Aring;','Aring','Atilde;',
|
||||
'Atilde','Auml;','Auml','Beta;','COPY;','COPY','Ccedil;','Ccedil','Chi;',
|
||||
'Dagger;','Delta;','ETH;','ETH','Eacute;','Eacute','Ecirc;','Ecirc','Egrave;',
|
||||
'Egrave','Epsilon;','Eta;','Euml;','Euml','GT;','GT','Gamma;','Iacute;',
|
||||
'Iacute','Icirc;','Icirc','Igrave;','Igrave','Iota;','Iuml;','Iuml','Kappa;',
|
||||
'LT;','LT','Lambda;','Mu;','Ntilde;','Ntilde','Nu;','OElig;','Oacute;',
|
||||
'Oacute','Ocirc;','Ocirc','Ograve;','Ograve','Omega;','Omicron;','Oslash;',
|
||||
'Oslash','Otilde;','Otilde','Ouml;','Ouml','Phi;','Pi;','Prime;','Psi;',
|
||||
'QUOT;','QUOT','REG;','REG','Rho;','Scaron;','Sigma;','THORN;','THORN',
|
||||
'TRADE;','Tau;','Theta;','Uacute;','Uacute','Ucirc;','Ucirc','Ugrave;',
|
||||
'Ugrave','Upsilon;','Uuml;','Uuml','Xi;','Yacute;','Yacute','Yuml;','Zeta;',
|
||||
'aacute;','aacute','acirc;','acirc','acute;','acute','aelig;','aelig',
|
||||
'agrave;','agrave','alefsym;','alpha;','amp;','amp','and;','ang;','apos;',
|
||||
'aring;','aring','asymp;','atilde;','atilde','auml;','auml','bdquo;','beta;',
|
||||
'brvbar;','brvbar','bull;','cap;','ccedil;','ccedil','cedil;','cedil',
|
||||
'cent;','cent','chi;','circ;','clubs;','cong;','copy;','copy','crarr;',
|
||||
'cup;','curren;','curren','dArr;','dagger;','darr;','deg;','deg','delta;',
|
||||
'diams;','divide;','divide','eacute;','eacute','ecirc;','ecirc','egrave;',
|
||||
'egrave','empty;','emsp;','ensp;','epsilon;','equiv;','eta;','eth;','eth',
|
||||
'euml;','euml','euro;','exist;','fnof;','forall;','frac12;','frac12',
|
||||
'frac14;','frac14','frac34;','frac34','frasl;','gamma;','ge;','gt;','gt',
|
||||
'hArr;','harr;','hearts;','hellip;','iacute;','iacute','icirc;','icirc',
|
||||
'iexcl;','iexcl','igrave;','igrave','image;','infin;','int;','iota;',
|
||||
'iquest;','iquest','isin;','iuml;','iuml','kappa;','lArr;','lambda;','lang;',
|
||||
'laquo;','laquo','larr;','lceil;','ldquo;','le;','lfloor;','lowast;','loz;',
|
||||
'lrm;','lsaquo;','lsquo;','lt;','lt','macr;','macr','mdash;','micro;','micro',
|
||||
'middot;','middot','minus;','mu;','nabla;','nbsp;','nbsp','ndash;','ne;',
|
||||
'ni;','not;','not','notin;','nsub;','ntilde;','ntilde','nu;','oacute;',
|
||||
'oacute','ocirc;','ocirc','oelig;','ograve;','ograve','oline;','omega;',
|
||||
'omicron;','oplus;','or;','ordf;','ordf','ordm;','ordm','oslash;','oslash',
|
||||
'otilde;','otilde','otimes;','ouml;','ouml','para;','para','part;','permil;',
|
||||
'perp;','phi;','pi;','piv;','plusmn;','plusmn','pound;','pound','prime;',
|
||||
'prod;','prop;','psi;','quot;','quot','rArr;','radic;','rang;','raquo;',
|
||||
'raquo','rarr;','rceil;','rdquo;','real;','reg;','reg','rfloor;','rho;',
|
||||
'rlm;','rsaquo;','rsquo;','sbquo;','scaron;','sdot;','sect;','sect','shy;',
|
||||
'shy','sigma;','sigmaf;','sim;','spades;','sub;','sube;','sum;','sup1;',
|
||||
'sup1','sup2;','sup2','sup3;','sup3','sup;','supe;','szlig;','szlig','tau;',
|
||||
'there4;','theta;','thetasym;','thinsp;','thorn;','thorn','tilde;','times;',
|
||||
'times','trade;','uArr;','uacute;','uacute','uarr;','ucirc;','ucirc',
|
||||
'ugrave;','ugrave','uml;','uml','upsih;','upsilon;','uuml;','uuml','weierp;',
|
||||
'xi;','yacute;','yacute','yen;','yen','yuml;','yuml','zeta;','zwj;','zwnj;');
|
||||
|
||||
const PCDATA = 0;
|
||||
const RCDATA = 1;
|
||||
const CDATA = 2;
|
||||
const PLAINTEXT = 3;
|
||||
|
||||
const DOCTYPE = 0;
|
||||
const STARTTAG = 1;
|
||||
const ENDTAG = 2;
|
||||
const COMMENT = 3;
|
||||
const CHARACTR = 4;
|
||||
const EOF = 5;
|
||||
|
||||
public function __construct($data) {
|
||||
$data = str_replace("\r\n", "\n", $data);
|
||||
$data = str_replace("\r", null, $data);
|
||||
|
||||
$this->data = $data;
|
||||
$this->char = -1;
|
||||
$this->EOF = strlen($data);
|
||||
$this->tree = new HTML5TreeConstructer;
|
||||
$this->content_model = self::PCDATA;
|
||||
|
||||
$this->state = 'data';
|
||||
|
||||
while($this->state !== null) {
|
||||
$this->{$this->state.'State'}();
|
||||
}
|
||||
}
|
||||
|
||||
public function save() {
|
||||
return $this->tree->save();
|
||||
}
|
||||
|
||||
private function char() {
|
||||
return ($this->char < $this->EOF)
|
||||
? $this->data[$this->char]
|
||||
: false;
|
||||
}
|
||||
|
||||
private function character($s, $l = 0) {
|
||||
if($s + $l < $this->EOF) {
|
||||
if($l === 0) {
|
||||
return $this->data[$s];
|
||||
} else {
|
||||
return substr($this->data, $s, $l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function characters($char_class, $start) {
|
||||
return preg_replace('#^(['.$char_class.']+).*#s', '\\1', substr($this->data, $start));
|
||||
}
|
||||
|
||||
private function dataState() {
|
||||
// Consume the next input character
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) {
|
||||
/* U+0026 AMPERSAND (&)
|
||||
When the content model flag is set to one of the PCDATA or RCDATA
|
||||
states: switch to the entity data state. Otherwise: treat it as per
|
||||
the "anything else" entry below. */
|
||||
$this->state = 'entityData';
|
||||
|
||||
} elseif($char === '-') {
|
||||
/* If the content model flag is set to either the RCDATA state or
|
||||
the CDATA state, and the escape flag is false, and there are at
|
||||
least three characters before this one in the input stream, and the
|
||||
last four characters in the input stream, including this one, are
|
||||
U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS,
|
||||
and U+002D HYPHEN-MINUS ("<!--"), then set the escape flag to true. */
|
||||
if(($this->content_model === self::RCDATA || $this->content_model ===
|
||||
self::CDATA) && $this->escape === false &&
|
||||
$this->char >= 3 && $this->character($this->char - 4, 4) === '<!--') {
|
||||
$this->escape = true;
|
||||
}
|
||||
|
||||
/* In any case, emit the input character as a character token. Stay
|
||||
in the data state. */
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => $char
|
||||
));
|
||||
|
||||
/* U+003C LESS-THAN SIGN (<) */
|
||||
} elseif($char === '<' && ($this->content_model === self::PCDATA ||
|
||||
(($this->content_model === self::RCDATA ||
|
||||
$this->content_model === self::CDATA) && $this->escape === false))) {
|
||||
/* When the content model flag is set to the PCDATA state: switch
|
||||
to the tag open state.
|
||||
|
||||
When the content model flag is set to either the RCDATA state or
|
||||
the CDATA state and the escape flag is false: switch to the tag
|
||||
open state.
|
||||
|
||||
Otherwise: treat it as per the "anything else" entry below. */
|
||||
$this->state = 'tagOpen';
|
||||
|
||||
/* U+003E GREATER-THAN SIGN (>) */
|
||||
} elseif($char === '>') {
|
||||
/* If the content model flag is set to either the RCDATA state or
|
||||
the CDATA state, and the escape flag is true, and the last three
|
||||
characters in the input stream including this one are U+002D
|
||||
HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"),
|
||||
set the escape flag to false. */
|
||||
if(($this->content_model === self::RCDATA ||
|
||||
$this->content_model === self::CDATA) && $this->escape === true &&
|
||||
$this->character($this->char, 3) === '-->') {
|
||||
$this->escape = false;
|
||||
}
|
||||
|
||||
/* In any case, emit the input character as a character token.
|
||||
Stay in the data state. */
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => $char
|
||||
));
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Emit an end-of-file token. */
|
||||
$this->EOF();
|
||||
|
||||
} elseif($this->content_model === self::PLAINTEXT) {
|
||||
/* When the content model flag is set to the PLAINTEXT state
|
||||
THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of
|
||||
the text and emit it as a character token. */
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => substr($this->data, $this->char)
|
||||
));
|
||||
|
||||
$this->EOF();
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that
|
||||
otherwise would also be treated as a character token and emit it
|
||||
as a single character token. Stay in the data state. */
|
||||
$len = strcspn($this->data, '<&', $this->char);
|
||||
$char = substr($this->data, $this->char, $len);
|
||||
$this->char += $len - 1;
|
||||
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => $char
|
||||
));
|
||||
|
||||
$this->state = 'data';
|
||||
}
|
||||
}
|
||||
|
||||
private function entityDataState() {
|
||||
// Attempt to consume an entity.
|
||||
$entity = $this->entity();
|
||||
|
||||
// If nothing is returned, emit a U+0026 AMPERSAND character token.
|
||||
// Otherwise, emit the character token that was returned.
|
||||
$char = (!$entity) ? '&' : $entity;
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => $char
|
||||
));
|
||||
|
||||
// Finally, switch to the data state.
|
||||
$this->state = 'data';
|
||||
}
|
||||
|
||||
private function tagOpenState() {
|
||||
switch($this->content_model) {
|
||||
case self::RCDATA:
|
||||
case self::CDATA:
|
||||
/* If the next input character is a U+002F SOLIDUS (/) character,
|
||||
consume it and switch to the close tag open state. If the next
|
||||
input character is not a U+002F SOLIDUS (/) character, emit a
|
||||
U+003C LESS-THAN SIGN character token and switch to the data
|
||||
state to process the next input character. */
|
||||
if($this->character($this->char + 1) === '/') {
|
||||
$this->char++;
|
||||
$this->state = 'closeTagOpen';
|
||||
|
||||
} else {
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => '<'
|
||||
));
|
||||
|
||||
$this->state = 'data';
|
||||
}
|
||||
break;
|
||||
|
||||
case self::PCDATA:
|
||||
// If the content model flag is set to the PCDATA state
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if($char === '!') {
|
||||
/* U+0021 EXCLAMATION MARK (!)
|
||||
Switch to the markup declaration open state. */
|
||||
$this->state = 'markupDeclarationOpen';
|
||||
|
||||
} elseif($char === '/') {
|
||||
/* U+002F SOLIDUS (/)
|
||||
Switch to the close tag open state. */
|
||||
$this->state = 'closeTagOpen';
|
||||
|
||||
} elseif(preg_match('/^[A-Za-z]$/', $char)) {
|
||||
/* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
|
||||
Create a new start tag token, set its tag name to the lowercase
|
||||
version of the input character (add 0x0020 to the character's code
|
||||
point), then switch to the tag name state. (Don't emit the token
|
||||
yet; further details will be filled in before it is emitted.) */
|
||||
$this->token = array(
|
||||
'name' => strtolower($char),
|
||||
'type' => self::STARTTAG,
|
||||
'attr' => array()
|
||||
);
|
||||
|
||||
$this->state = 'tagName';
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Parse error. Emit a U+003C LESS-THAN SIGN character token and a
|
||||
U+003E GREATER-THAN SIGN character token. Switch to the data state. */
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => '<>'
|
||||
));
|
||||
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($char === '?') {
|
||||
/* U+003F QUESTION MARK (?)
|
||||
Parse error. Switch to the bogus comment state. */
|
||||
$this->state = 'bogusComment';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Parse error. Emit a U+003C LESS-THAN SIGN character token and
|
||||
reconsume the current input character in the data state. */
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => '<'
|
||||
));
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function closeTagOpenState() {
|
||||
$next_node = strtolower($this->characters('A-Za-z', $this->char + 1));
|
||||
$the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName;
|
||||
|
||||
if(($this->content_model === self::RCDATA || $this->content_model === self::CDATA) &&
|
||||
(!$the_same || ($the_same && (!preg_match('/[\t\n\x0b\x0c >\/]/',
|
||||
$this->character($this->char + 1 + strlen($next_node))) || $this->EOF === $this->char)))) {
|
||||
/* If the content model flag is set to the RCDATA or CDATA states then
|
||||
examine the next few characters. If they do not match the tag name of
|
||||
the last start tag token emitted (case insensitively), or if they do but
|
||||
they are not immediately followed by one of the following characters:
|
||||
* U+0009 CHARACTER TABULATION
|
||||
* U+000A LINE FEED (LF)
|
||||
* U+000B LINE TABULATION
|
||||
* U+000C FORM FEED (FF)
|
||||
* U+0020 SPACE
|
||||
* U+003E GREATER-THAN SIGN (>)
|
||||
* U+002F SOLIDUS (/)
|
||||
* EOF
|
||||
...then there is a parse error. Emit a U+003C LESS-THAN SIGN character
|
||||
token, a U+002F SOLIDUS character token, and switch to the data state
|
||||
to process the next input character. */
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => '</'
|
||||
));
|
||||
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Otherwise, if the content model flag is set to the PCDATA state,
|
||||
or if the next few characters do match that tag name, consume the
|
||||
next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if(preg_match('/^[A-Za-z]$/', $char)) {
|
||||
/* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
|
||||
Create a new end tag token, set its tag name to the lowercase version
|
||||
of the input character (add 0x0020 to the character's code point), then
|
||||
switch to the tag name state. (Don't emit the token yet; further details
|
||||
will be filled in before it is emitted.) */
|
||||
$this->token = array(
|
||||
'name' => strtolower($char),
|
||||
'type' => self::ENDTAG
|
||||
);
|
||||
|
||||
$this->state = 'tagName';
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Parse error. Switch to the data state. */
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F
|
||||
SOLIDUS character token. Reconsume the EOF character in the data state. */
|
||||
$this->emitToken(array(
|
||||
'type' => self::CHARACTR,
|
||||
'data' => '</'
|
||||
));
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Parse error. Switch to the bogus comment state. */
|
||||
$this->state = 'bogusComment';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function tagNameState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
/* U+0009 CHARACTER TABULATION
|
||||
U+000A LINE FEED (LF)
|
||||
U+000B LINE TABULATION
|
||||
U+000C FORM FEED (FF)
|
||||
U+0020 SPACE
|
||||
Switch to the before attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Emit the current tag token. Switch to the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Parse error. Emit the current tag token. Reconsume the EOF
|
||||
character in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($char === '/') {
|
||||
/* U+002F SOLIDUS (/)
|
||||
Parse error unless this is a permitted slash. Switch to the before
|
||||
attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Append the current input character to the current tag token's tag name.
|
||||
Stay in the tag name state. */
|
||||
$this->token['name'] .= strtolower($char);
|
||||
$this->state = 'tagName';
|
||||
}
|
||||
}
|
||||
|
||||
private function beforeAttributeNameState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
/* U+0009 CHARACTER TABULATION
|
||||
U+000A LINE FEED (LF)
|
||||
U+000B LINE TABULATION
|
||||
U+000C FORM FEED (FF)
|
||||
U+0020 SPACE
|
||||
Stay in the before attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Emit the current tag token. Switch to the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($char === '/') {
|
||||
/* U+002F SOLIDUS (/)
|
||||
Parse error unless this is a permitted slash. Stay in the before
|
||||
attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Parse error. Emit the current tag token. Reconsume the EOF
|
||||
character in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Start a new attribute in the current tag token. Set that attribute's
|
||||
name to the current input character, and its value to the empty string.
|
||||
Switch to the attribute name state. */
|
||||
$this->token['attr'][] = array(
|
||||
'name' => strtolower($char),
|
||||
'value' => null
|
||||
);
|
||||
|
||||
$this->state = 'attributeName';
|
||||
}
|
||||
}
|
||||
|
||||
private function attributeNameState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
/* U+0009 CHARACTER TABULATION
|
||||
U+000A LINE FEED (LF)
|
||||
U+000B LINE TABULATION
|
||||
U+000C FORM FEED (FF)
|
||||
U+0020 SPACE
|
||||
Stay in the before attribute name state. */
|
||||
$this->state = 'afterAttributeName';
|
||||
|
||||
} elseif($char === '=') {
|
||||
/* U+003D EQUALS SIGN (=)
|
||||
Switch to the before attribute value state. */
|
||||
$this->state = 'beforeAttributeValue';
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Emit the current tag token. Switch to the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($char === '/' && $this->character($this->char + 1) !== '>') {
|
||||
/* U+002F SOLIDUS (/)
|
||||
Parse error unless this is a permitted slash. Switch to the before
|
||||
attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Parse error. Emit the current tag token. Reconsume the EOF
|
||||
character in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Append the current input character to the current attribute's name.
|
||||
Stay in the attribute name state. */
|
||||
$last = count($this->token['attr']) - 1;
|
||||
$this->token['attr'][$last]['name'] .= strtolower($char);
|
||||
|
||||
$this->state = 'attributeName';
|
||||
}
|
||||
}
|
||||
|
||||
private function afterAttributeNameState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
/* U+0009 CHARACTER TABULATION
|
||||
U+000A LINE FEED (LF)
|
||||
U+000B LINE TABULATION
|
||||
U+000C FORM FEED (FF)
|
||||
U+0020 SPACE
|
||||
Stay in the after attribute name state. */
|
||||
$this->state = 'afterAttributeName';
|
||||
|
||||
} elseif($char === '=') {
|
||||
/* U+003D EQUALS SIGN (=)
|
||||
Switch to the before attribute value state. */
|
||||
$this->state = 'beforeAttributeValue';
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Emit the current tag token. Switch to the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($char === '/' && $this->character($this->char + 1) !== '>') {
|
||||
/* U+002F SOLIDUS (/)
|
||||
Parse error unless this is a permitted slash. Switch to the
|
||||
before attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Parse error. Emit the current tag token. Reconsume the EOF
|
||||
character in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Start a new attribute in the current tag token. Set that attribute's
|
||||
name to the current input character, and its value to the empty string.
|
||||
Switch to the attribute name state. */
|
||||
$this->token['attr'][] = array(
|
||||
'name' => strtolower($char),
|
||||
'value' => null
|
||||
);
|
||||
|
||||
$this->state = 'attributeName';
|
||||
}
|
||||
}
|
||||
|
||||
private function beforeAttributeValueState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
/* U+0009 CHARACTER TABULATION
|
||||
U+000A LINE FEED (LF)
|
||||
U+000B LINE TABULATION
|
||||
U+000C FORM FEED (FF)
|
||||
U+0020 SPACE
|
||||
Stay in the before attribute value state. */
|
||||
$this->state = 'beforeAttributeValue';
|
||||
|
||||
} elseif($char === '"') {
|
||||
/* U+0022 QUOTATION MARK (")
|
||||
Switch to the attribute value (double-quoted) state. */
|
||||
$this->state = 'attributeValueDoubleQuoted';
|
||||
|
||||
} elseif($char === '&') {
|
||||
/* U+0026 AMPERSAND (&)
|
||||
Switch to the attribute value (unquoted) state and reconsume
|
||||
this input character. */
|
||||
$this->char--;
|
||||
$this->state = 'attributeValueUnquoted';
|
||||
|
||||
} elseif($char === '\'') {
|
||||
/* U+0027 APOSTROPHE (')
|
||||
Switch to the attribute value (single-quoted) state. */
|
||||
$this->state = 'attributeValueSingleQuoted';
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Emit the current tag token. Switch to the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Append the current input character to the current attribute's value.
|
||||
Switch to the attribute value (unquoted) state. */
|
||||
$last = count($this->token['attr']) - 1;
|
||||
$this->token['attr'][$last]['value'] .= $char;
|
||||
|
||||
$this->state = 'attributeValueUnquoted';
|
||||
}
|
||||
}
|
||||
|
||||
private function attributeValueDoubleQuotedState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if($char === '"') {
|
||||
/* U+0022 QUOTATION MARK (")
|
||||
Switch to the before attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($char === '&') {
|
||||
/* U+0026 AMPERSAND (&)
|
||||
Switch to the entity in attribute value state. */
|
||||
$this->entityInAttributeValueState('double');
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Parse error. Emit the current tag token. Reconsume the character
|
||||
in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Append the current input character to the current attribute's value.
|
||||
Stay in the attribute value (double-quoted) state. */
|
||||
$last = count($this->token['attr']) - 1;
|
||||
$this->token['attr'][$last]['value'] .= $char;
|
||||
|
||||
$this->state = 'attributeValueDoubleQuoted';
|
||||
}
|
||||
}
|
||||
|
||||
private function attributeValueSingleQuotedState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if($char === '\'') {
|
||||
/* U+0022 QUOTATION MARK (')
|
||||
Switch to the before attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($char === '&') {
|
||||
/* U+0026 AMPERSAND (&)
|
||||
Switch to the entity in attribute value state. */
|
||||
$this->entityInAttributeValueState('single');
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* EOF
|
||||
Parse error. Emit the current tag token. Reconsume the character
|
||||
in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Append the current input character to the current attribute's value.
|
||||
Stay in the attribute value (single-quoted) state. */
|
||||
$last = count($this->token['attr']) - 1;
|
||||
$this->token['attr'][$last]['value'] .= $char;
|
||||
|
||||
$this->state = 'attributeValueSingleQuoted';
|
||||
}
|
||||
}
|
||||
|
||||
private function attributeValueUnquotedState() {
|
||||
// Consume the next input character:
|
||||
$this->char++;
|
||||
$char = $this->character($this->char);
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
/* U+0009 CHARACTER TABULATION
|
||||
U+000A LINE FEED (LF)
|
||||
U+000B LINE TABULATION
|
||||
U+000C FORM FEED (FF)
|
||||
U+0020 SPACE
|
||||
Switch to the before attribute name state. */
|
||||
$this->state = 'beforeAttributeName';
|
||||
|
||||
} elseif($char === '&') {
|
||||
/* U+0026 AMPERSAND (&)
|
||||
Switch to the entity in attribute value state. */
|
||||
$this->entityInAttributeValueState();
|
||||
|
||||
} elseif($char === '>') {
|
||||
/* U+003E GREATER-THAN SIGN (>)
|
||||
Emit the current tag token. Switch to the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
/* Anything else
|
||||
Append the current input character to the current attribute's value.
|
||||
Stay in the attribute value (unquoted) state. */
|
||||
$last = count($this->token['attr']) - 1;
|
||||
$this->token['attr'][$last]['value'] .= $char;
|
||||
|
||||
$this->state = 'attributeValueUnquoted';
|
||||
}
|
||||
}
|
||||
|
||||
private function entityInAttributeValueState() {
|
||||
// Attempt to consume an entity.
|
||||
$entity = $this->entity();
|
||||
|
||||
// If nothing is returned, append a U+0026 AMPERSAND character to the
|
||||
// current attribute's value. Otherwise, emit the character token that
|
||||
// was returned.
|
||||
$char = (!$entity)
|
||||
? '&'
|
||||
: $entity;
|
||||
|
||||
$last = count($this->token['attr']) - 1;
|
||||
$this->token['attr'][$last]['value'] .= $char;
|
||||
}
|
||||
|
||||
private function bogusCommentState() {
|
||||
/* Consume every character up to the first U+003E GREATER-THAN SIGN
|
||||
character (>) or the end of the file (EOF), whichever comes first. Emit
|
||||
a comment token whose data is the concatenation of all the characters
|
||||
starting from and including the character that caused the state machine
|
||||
to switch into the bogus comment state, up to and including the last
|
||||
consumed character before the U+003E character, if any, or up to the
|
||||
end of the file otherwise. (If the comment was started by the end of
|
||||
the file (EOF), the token is empty.) */
|
||||
$data = $this->characters('^>', $this->char);
|
||||
$this->emitToken(array(
|
||||
'data' => $data,
|
||||
'type' => self::COMMENT
|
||||
));
|
||||
|
||||
$this->char += strlen($data);
|
||||
|
||||
/* Switch to the data state. */
|
||||
$this->state = 'data';
|
||||
|
||||
/* If the end of the file was reached, reconsume the EOF character. */
|
||||
if($this->char === $this->EOF) {
|
||||
$this->char = $this->EOF - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private function markupDeclarationOpenState() {
|
||||
/* If the next two characters are both U+002D HYPHEN-MINUS (-)
|
||||
characters, consume those two characters, create a comment token whose
|
||||
data is the empty string, and switch to the comment state. */
|
||||
if($this->character($this->char + 1, 2) === '--') {
|
||||
$this->char += 2;
|
||||
$this->state = 'comment';
|
||||
$this->token = array(
|
||||
'data' => null,
|
||||
'type' => self::COMMENT
|
||||
);
|
||||
|
||||
/* Otherwise if the next seven chacacters are a case-insensitive match
|
||||
for the word "DOCTYPE", then consume those characters and switch to the
|
||||
DOCTYPE state. */
|
||||
} elseif(strtolower($this->character($this->char + 1, 7)) === 'doctype') {
|
||||
$this->char += 7;
|
||||
$this->state = 'doctype';
|
||||
|
||||
/* Otherwise, is is a parse error. Switch to the bogus comment state.
|
||||
The next character that is consumed, if any, is the first character
|
||||
that will be in the comment. */
|
||||
} else {
|
||||
$this->char++;
|
||||
$this->state = 'bogusComment';
|
||||
}
|
||||
}
|
||||
|
||||
private function commentState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
/* U+002D HYPHEN-MINUS (-) */
|
||||
if($char === '-') {
|
||||
/* Switch to the comment dash state */
|
||||
$this->state = 'commentDash';
|
||||
|
||||
/* EOF */
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* Parse error. Emit the comment token. Reconsume the EOF character
|
||||
in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Append the input character to the comment token's data. Stay in
|
||||
the comment state. */
|
||||
$this->token['data'] .= $char;
|
||||
}
|
||||
}
|
||||
|
||||
private function commentDashState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
/* U+002D HYPHEN-MINUS (-) */
|
||||
if($char === '-') {
|
||||
/* Switch to the comment end state */
|
||||
$this->state = 'commentEnd';
|
||||
|
||||
/* EOF */
|
||||
} elseif($this->char === $this->EOF) {
|
||||
/* Parse error. Emit the comment token. Reconsume the EOF character
|
||||
in the data state. */
|
||||
$this->emitToken($this->token);
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Append a U+002D HYPHEN-MINUS (-) character and the input
|
||||
character to the comment token's data. Switch to the comment state. */
|
||||
$this->token['data'] .= '-'.$char;
|
||||
$this->state = 'comment';
|
||||
}
|
||||
}
|
||||
|
||||
private function commentEndState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if($char === '>') {
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($char === '-') {
|
||||
$this->token['data'] .= '-';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
$this->emitToken($this->token);
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
$this->token['data'] .= '--'.$char;
|
||||
$this->state = 'comment';
|
||||
}
|
||||
}
|
||||
|
||||
private function doctypeState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
$this->state = 'beforeDoctypeName';
|
||||
|
||||
} else {
|
||||
$this->char--;
|
||||
$this->state = 'beforeDoctypeName';
|
||||
}
|
||||
}
|
||||
|
||||
private function beforeDoctypeNameState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
// Stay in the before DOCTYPE name state.
|
||||
|
||||
} elseif(preg_match('/^[a-z]$/', $char)) {
|
||||
$this->token = array(
|
||||
'name' => strtoupper($char),
|
||||
'type' => self::DOCTYPE,
|
||||
'error' => true
|
||||
);
|
||||
|
||||
$this->state = 'doctypeName';
|
||||
|
||||
} elseif($char === '>') {
|
||||
$this->emitToken(array(
|
||||
'name' => null,
|
||||
'type' => self::DOCTYPE,
|
||||
'error' => true
|
||||
));
|
||||
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
$this->emitToken(array(
|
||||
'name' => null,
|
||||
'type' => self::DOCTYPE,
|
||||
'error' => true
|
||||
));
|
||||
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
$this->token = array(
|
||||
'name' => $char,
|
||||
'type' => self::DOCTYPE,
|
||||
'error' => true
|
||||
);
|
||||
|
||||
$this->state = 'doctypeName';
|
||||
}
|
||||
}
|
||||
|
||||
private function doctypeNameState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
$this->state = 'AfterDoctypeName';
|
||||
|
||||
} elseif($char === '>') {
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif(preg_match('/^[a-z]$/', $char)) {
|
||||
$this->token['name'] .= strtoupper($char);
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
$this->emitToken($this->token);
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
$this->token['name'] .= $char;
|
||||
}
|
||||
|
||||
$this->token['error'] = ($this->token['name'] === 'HTML')
|
||||
? false
|
||||
: true;
|
||||
}
|
||||
|
||||
private function afterDoctypeNameState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
|
||||
// Stay in the DOCTYPE name state.
|
||||
|
||||
} elseif($char === '>') {
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
$this->emitToken($this->token);
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
$this->token['error'] = true;
|
||||
$this->state = 'bogusDoctype';
|
||||
}
|
||||
}
|
||||
|
||||
private function bogusDoctypeState() {
|
||||
/* Consume the next input character: */
|
||||
$this->char++;
|
||||
$char = $this->char();
|
||||
|
||||
if($char === '>') {
|
||||
$this->emitToken($this->token);
|
||||
$this->state = 'data';
|
||||
|
||||
} elseif($this->char === $this->EOF) {
|
||||
$this->emitToken($this->token);
|
||||
$this->char--;
|
||||
$this->state = 'data';
|
||||
|
||||
} else {
|
||||
// Stay in the bogus DOCTYPE state.
|
||||
}
|
||||
}
|
||||
|
||||
private function entity() {
|
||||
$start = $this->char;
|
||||
|
||||
// This section defines how to consume an entity. This definition is
|
||||
// used when parsing entities in text and in attributes.
|
||||
|
||||
// The behaviour depends on the identity of the next character (the
|
||||
// one immediately after the U+0026 AMPERSAND character):
|
||||
|
||||
switch($this->character($this->char + 1)) {
|
||||
// U+0023 NUMBER SIGN (#)
|
||||
case '#':
|
||||
|
||||
// The behaviour further depends on the character after the
|
||||
// U+0023 NUMBER SIGN:
|
||||
switch($this->character($this->char + 1)) {
|
||||
// U+0078 LATIN SMALL LETTER X
|
||||
// U+0058 LATIN CAPITAL LETTER X
|
||||
case 'x':
|
||||
case 'X':
|
||||
// Follow the steps below, but using the range of
|
||||
// characters U+0030 DIGIT ZERO through to U+0039 DIGIT
|
||||
// NINE, U+0061 LATIN SMALL LETTER A through to U+0066
|
||||
// LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER
|
||||
// A, through to U+0046 LATIN CAPITAL LETTER F (in other
|
||||
// words, 0-9, A-F, a-f).
|
||||
$char = 1;
|
||||
$char_class = '0-9A-Fa-f';
|
||||
break;
|
||||
|
||||
// Anything else
|
||||
default:
|
||||
// Follow the steps below, but using the range of
|
||||
// characters U+0030 DIGIT ZERO through to U+0039 DIGIT
|
||||
// NINE (i.e. just 0-9).
|
||||
$char = 0;
|
||||
$char_class = '0-9';
|
||||
break;
|
||||
}
|
||||
|
||||
// Consume as many characters as match the range of characters
|
||||
// given above.
|
||||
$this->char++;
|
||||
$e_name = $this->characters($char_class, $this->char + $char + 1);
|
||||
$entity = $this->character($start, $this->char);
|
||||
$cond = strlen($e_name) > 0;
|
||||
|
||||
// The rest of the parsing happens bellow.
|
||||
break;
|
||||
|
||||
// Anything else
|
||||
default:
|
||||
// Consume the maximum number of characters possible, with the
|
||||
// consumed characters case-sensitively matching one of the
|
||||
// identifiers in the first column of the entities table.
|
||||
$e_name = $this->characters('0-9A-Za-z;', $this->char + 1);
|
||||
$len = strlen($e_name);
|
||||
|
||||
for($c = 1; $c <= $len; $c++) {
|
||||
$id = substr($e_name, 0, $c);
|
||||
$this->char++;
|
||||
|
||||
if(in_array($id, $this->entities)) {
|
||||
if ($e_name[$c-1] !== ';') {
|
||||
if ($c < $len && $e_name[$c] == ';') {
|
||||
$this->char++; // consume extra semicolon
|
||||
}
|
||||
}
|
||||
$entity = $id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$cond = isset($entity);
|
||||
// The rest of the parsing happens bellow.
|
||||
break;
|
||||
}
|
||||
|
||||
if(!$cond) {
|
||||
// If no match can be made, then this is a parse error. No
|
||||
// characters are consumed, and nothing is returned.
|
||||
$this->char = $start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return a character token for the character corresponding to the
|
||||
// entity name (as given by the second column of the entities table).
|
||||
return html_entity_decode('&'.$entity.';', ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
private function emitToken($token) {
|
||||
$emit = $this->tree->emitToken($token);
|
||||
|
||||
if(is_int($emit)) {
|
||||
$this->content_model = $emit;
|
||||
|
||||
} elseif($token['type'] === self::ENDTAG) {
|
||||
$this->content_model = self::PCDATA;
|
||||
}
|
||||
}
|
||||
|
||||
private function EOF() {
|
||||
$this->state = null;
|
||||
$this->tree->emitToken(array(
|
||||
'type' => self::EOF
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class HTML5TreeConstructer {
|
||||
public $stack = array();
|
||||
|
||||
private $phase;
|
||||
private $mode;
|
||||
private $dom;
|
||||
private $foster_parent = null;
|
||||
private $a_formatting = array();
|
||||
|
||||
private $head_pointer = null;
|
||||
private $form_pointer = null;
|
||||
|
||||
private $scoping = array('button','caption','html','marquee','object','table','td','th');
|
||||
private $formatting = array('a','b','big','em','font','i','nobr','s','small','strike','strong','tt','u');
|
||||
private $special = array('address','area','base','basefont','bgsound',
|
||||
'blockquote','body','br','center','col','colgroup','dd','dir','div','dl',
|
||||
'dt','embed','fieldset','form','frame','frameset','h1','h2','h3','h4','h5',
|
||||
'h6','head','hr','iframe','image','img','input','isindex','li','link',
|
||||
'listing','menu','meta','noembed','noframes','noscript','ol','optgroup',
|
||||
'option','p','param','plaintext','pre','script','select','spacer','style',
|
||||
'tbody','textarea','tfoot','thead','title','tr','ul','wbr');
|
||||
|
||||
// The different phases.
|
||||
const INIT_PHASE = 0;
|
||||
const ROOT_PHASE = 1;
|
||||
const MAIN_PHASE = 2;
|
||||
const END_PHASE = 3;
|
||||
|
||||
// The different insertion modes for the main phase.
|
||||
const BEFOR_HEAD = 0;
|
||||
const IN_HEAD = 1;
|
||||
const AFTER_HEAD = 2;
|
||||
const IN_BODY = 3;
|
||||
const IN_TABLE = 4;
|
||||
const IN_CAPTION = 5;
|
||||
const IN_CGROUP = 6;
|
||||
const IN_TBODY = 7;
|
||||
const IN_ROW = 8;
|
||||
const IN_CELL = 9;
|
||||
const IN_SELECT = 10;
|
||||
const AFTER_BODY = 11;
|
||||
const IN_FRAME = 12;
|
||||
const AFTR_FRAME = 13;
|
||||
|
||||
// The different types of elements.
|
||||
const SPECIAL = 0;
|
||||
const SCOPING = 1;
|
||||
const FORMATTING = 2;
|
||||
const PHRASING = 3;
|
||||
|
||||
const MARKER = 0;
|
||||
|
||||
public function __construct() {
|
||||
$this->phase = self::INIT_PHASE;
|
||||
$this->mode = self::BEFOR_HEAD;
|
||||
$this->dom = new DOMDocument;
|
||||
|
||||
$this->dom->encoding = 'UTF-8';
|
||||
$this->dom->preserveWhiteSpace = true;
|
||||
$this->dom->substituteEntities = true;
|
||||
$this->dom->strictErrorChecking = false;
|
||||
}
|
||||
|
||||
// Process tag tokens
|
||||
public function emitToken($token) {
|
||||
switch($this->phase) {
|
||||
case self::INIT_PHASE: return $this->initPhase($token); break;
|
||||
case self::ROOT_PHASE: return $this->rootElementPhase($token); break;
|
||||
case self::MAIN_PHASE: return $this->mainPhase($token); break;
|
||||
case self::END_PHASE : return $this->trailingEndPhase($token); break;
|
||||
}
|
||||
}
|
||||
|
||||
private function initPhase($token) {
|
||||
/* Initially, the tree construction stage must handle each token
|
||||
emitted from the tokenisation stage as follows: */
|
||||
|
||||
/* A DOCTYPE token that is marked as being in error
|
||||
A comment token
|
||||
A start tag token
|
||||
An end tag token
|
||||
A character token that is not one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE
|
||||
An end-of-file token */
|
||||
if((isset($token['error']) && $token['error']) ||
|
||||
$token['type'] === HTML5::COMMENT ||
|
||||
$token['type'] === HTML5::STARTTAG ||
|
||||
$token['type'] === HTML5::ENDTAG ||
|
||||
$token['type'] === HTML5::EOF ||
|
||||
($token['type'] === HTML5::CHARACTR && isset($token['data']) &&
|
||||
!preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))) {
|
||||
/* This specification does not define how to handle this case. In
|
||||
particular, user agents may ignore the entirety of this specification
|
||||
altogether for such documents, and instead invoke special parse modes
|
||||
with a greater emphasis on backwards compatibility. */
|
||||
|
||||
$this->phase = self::ROOT_PHASE;
|
||||
return $this->rootElementPhase($token);
|
||||
|
||||
/* A DOCTYPE token marked as being correct */
|
||||
} elseif(isset($token['error']) && !$token['error']) {
|
||||
/* Append a DocumentType node to the Document node, with the name
|
||||
attribute set to the name given in the DOCTYPE token (which will be
|
||||
"HTML"), and the other attributes specific to DocumentType objects
|
||||
set to null, empty lists, or the empty string as appropriate. */
|
||||
$doctype = new DOMDocumentType(null, null, 'HTML');
|
||||
|
||||
/* Then, switch to the root element phase of the tree construction
|
||||
stage. */
|
||||
$this->phase = self::ROOT_PHASE;
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
} elseif(isset($token['data']) && preg_match('/^[\t\n\x0b\x0c ]+$/',
|
||||
$token['data'])) {
|
||||
/* Append that character to the Document node. */
|
||||
$text = $this->dom->createTextNode($token['data']);
|
||||
$this->dom->appendChild($text);
|
||||
}
|
||||
}
|
||||
|
||||
private function rootElementPhase($token) {
|
||||
/* After the initial phase, as each token is emitted from the tokenisation
|
||||
stage, it must be processed as described in this section. */
|
||||
|
||||
/* A DOCTYPE token */
|
||||
if($token['type'] === HTML5::DOCTYPE) {
|
||||
// Parse error. Ignore the token.
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the Document object with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$comment = $this->dom->createComment($token['data']);
|
||||
$this->dom->appendChild($comment);
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
} elseif($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Append that character to the Document node. */
|
||||
$text = $this->dom->createTextNode($token['data']);
|
||||
$this->dom->appendChild($text);
|
||||
|
||||
/* A character token that is not one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED
|
||||
(FF), or U+0020 SPACE
|
||||
A start tag token
|
||||
An end tag token
|
||||
An end-of-file token */
|
||||
} elseif(($token['type'] === HTML5::CHARACTR &&
|
||||
!preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
|
||||
$token['type'] === HTML5::STARTTAG ||
|
||||
$token['type'] === HTML5::ENDTAG ||
|
||||
$token['type'] === HTML5::EOF) {
|
||||
/* Create an HTMLElement node with the tag name html, in the HTML
|
||||
namespace. Append it to the Document object. Switch to the main
|
||||
phase and reprocess the current token. */
|
||||
$html = $this->dom->createElement('html');
|
||||
$this->dom->appendChild($html);
|
||||
$this->stack[] = $html;
|
||||
|
||||
$this->phase = self::MAIN_PHASE;
|
||||
return $this->mainPhase($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function mainPhase($token) {
|
||||
/* Tokens in the main phase must be handled as follows: */
|
||||
|
||||
/* A DOCTYPE token */
|
||||
if($token['type'] === HTML5::DOCTYPE) {
|
||||
// Parse error. Ignore the token.
|
||||
|
||||
/* A start tag token with the tag name "html" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') {
|
||||
/* If this start tag token was not the first start tag token, then
|
||||
it is a parse error. */
|
||||
|
||||
/* For each attribute on the token, check to see if the attribute
|
||||
is already present on the top element of the stack of open elements.
|
||||
If it is not, add the attribute and its corresponding value to that
|
||||
element. */
|
||||
foreach($token['attr'] as $attr) {
|
||||
if(!$this->stack[0]->hasAttribute($attr['name'])) {
|
||||
$this->stack[0]->setAttribute($attr['name'], $attr['value']);
|
||||
}
|
||||
}
|
||||
|
||||
/* An end-of-file token */
|
||||
} elseif($token['type'] === HTML5::EOF) {
|
||||
/* Generate implied end tags. */
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
/* Anything else. */
|
||||
} else {
|
||||
/* Depends on the insertion mode: */
|
||||
switch($this->mode) {
|
||||
case self::BEFOR_HEAD: return $this->beforeHead($token); break;
|
||||
case self::IN_HEAD: return $this->inHead($token); break;
|
||||
case self::AFTER_HEAD: return $this->afterHead($token); break;
|
||||
case self::IN_BODY: return $this->inBody($token); break;
|
||||
case self::IN_TABLE: return $this->inTable($token); break;
|
||||
case self::IN_CAPTION: return $this->inCaption($token); break;
|
||||
case self::IN_CGROUP: return $this->inColumnGroup($token); break;
|
||||
case self::IN_TBODY: return $this->inTableBody($token); break;
|
||||
case self::IN_ROW: return $this->inRow($token); break;
|
||||
case self::IN_CELL: return $this->inCell($token); break;
|
||||
case self::IN_SELECT: return $this->inSelect($token); break;
|
||||
case self::AFTER_BODY: return $this->afterBody($token); break;
|
||||
case self::IN_FRAME: return $this->inFrameset($token); break;
|
||||
case self::AFTR_FRAME: return $this->afterFrameset($token); break;
|
||||
case self::END_PHASE: return $this->trailingEndPhase($token); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function beforeHead($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
if($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Append the character to the current node. */
|
||||
$this->insertText($token['data']);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data attribute
|
||||
set to the data given in the comment token. */
|
||||
$this->insertComment($token['data']);
|
||||
|
||||
/* A start tag token with the tag name "head" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') {
|
||||
/* Create an element for the token, append the new element to the
|
||||
current node and push it onto the stack of open elements. */
|
||||
$element = $this->insertElement($token);
|
||||
|
||||
/* Set the head element pointer to this new element node. */
|
||||
$this->head_pointer = $element;
|
||||
|
||||
/* Change the insertion mode to "in head". */
|
||||
$this->mode = self::IN_HEAD;
|
||||
|
||||
/* A start tag token whose tag name is one of: "base", "link", "meta",
|
||||
"script", "style", "title". Or an end tag with the tag name "html".
|
||||
Or a character token that is not one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE. Or any other start tag token */
|
||||
} elseif($token['type'] === HTML5::STARTTAG ||
|
||||
($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') ||
|
||||
($token['type'] === HTML5::CHARACTR && !preg_match('/^[\t\n\x0b\x0c ]$/',
|
||||
$token['data']))) {
|
||||
/* Act as if a start tag token with the tag name "head" and no
|
||||
attributes had been seen, then reprocess the current token. */
|
||||
$this->beforeHead(array(
|
||||
'name' => 'head',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
return $this->inHead($token);
|
||||
|
||||
/* Any other end tag */
|
||||
} elseif($token['type'] === HTML5::ENDTAG) {
|
||||
/* Parse error. Ignore the token. */
|
||||
}
|
||||
}
|
||||
|
||||
private function inHead($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE.
|
||||
|
||||
THIS DIFFERS FROM THE SPEC: If the current node is either a title, style
|
||||
or script element, append the character to the current node regardless
|
||||
of its content. */
|
||||
if(($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || (
|
||||
$token['type'] === HTML5::CHARACTR && in_array(end($this->stack)->nodeName,
|
||||
array('title', 'style', 'script')))) {
|
||||
/* Append the character to the current node. */
|
||||
$this->insertText($token['data']);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data attribute
|
||||
set to the data given in the comment token. */
|
||||
$this->insertComment($token['data']);
|
||||
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
in_array($token['name'], array('title', 'style', 'script'))) {
|
||||
array_pop($this->stack);
|
||||
return HTML5::PCDATA;
|
||||
|
||||
/* A start tag with the tag name "title" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') {
|
||||
/* Create an element for the token and append the new element to the
|
||||
node pointed to by the head element pointer, or, if that is null
|
||||
(innerHTML case), to the current node. */
|
||||
if($this->head_pointer !== null) {
|
||||
$element = $this->insertElement($token, false);
|
||||
$this->head_pointer->appendChild($element);
|
||||
|
||||
} else {
|
||||
$element = $this->insertElement($token);
|
||||
}
|
||||
|
||||
/* Switch the tokeniser's content model flag to the RCDATA state. */
|
||||
return HTML5::RCDATA;
|
||||
|
||||
/* A start tag with the tag name "style" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') {
|
||||
/* Create an element for the token and append the new element to the
|
||||
node pointed to by the head element pointer, or, if that is null
|
||||
(innerHTML case), to the current node. */
|
||||
if($this->head_pointer !== null) {
|
||||
$element = $this->insertElement($token, false);
|
||||
$this->head_pointer->appendChild($element);
|
||||
|
||||
} else {
|
||||
$this->insertElement($token);
|
||||
}
|
||||
|
||||
/* Switch the tokeniser's content model flag to the CDATA state. */
|
||||
return HTML5::CDATA;
|
||||
|
||||
/* A start tag with the tag name "script" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') {
|
||||
/* Create an element for the token. */
|
||||
$element = $this->insertElement($token, false);
|
||||
$this->head_pointer->appendChild($element);
|
||||
|
||||
/* Switch the tokeniser's content model flag to the CDATA state. */
|
||||
return HTML5::CDATA;
|
||||
|
||||
/* A start tag with the tag name "base", "link", or "meta" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('base', 'link', 'meta'))) {
|
||||
/* Create an element for the token and append the new element to the
|
||||
node pointed to by the head element pointer, or, if that is null
|
||||
(innerHTML case), to the current node. */
|
||||
if($this->head_pointer !== null) {
|
||||
$element = $this->insertElement($token, false);
|
||||
$this->head_pointer->appendChild($element);
|
||||
array_pop($this->stack);
|
||||
|
||||
} else {
|
||||
$this->insertElement($token);
|
||||
}
|
||||
|
||||
/* An end tag with the tag name "head" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') {
|
||||
/* If the current node is a head element, pop the current node off
|
||||
the stack of open elements. */
|
||||
if($this->head_pointer->isSameNode(end($this->stack))) {
|
||||
array_pop($this->stack);
|
||||
|
||||
/* Otherwise, this is a parse error. */
|
||||
} else {
|
||||
// k
|
||||
}
|
||||
|
||||
/* Change the insertion mode to "after head". */
|
||||
$this->mode = self::AFTER_HEAD;
|
||||
|
||||
/* A start tag with the tag name "head" or an end tag except "html". */
|
||||
} elseif(($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') ||
|
||||
($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')) {
|
||||
// Parse error. Ignore the token.
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* If the current node is a head element, act as if an end tag
|
||||
token with the tag name "head" had been seen. */
|
||||
if($this->head_pointer->isSameNode(end($this->stack))) {
|
||||
$this->inHead(array(
|
||||
'name' => 'head',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
/* Otherwise, change the insertion mode to "after head". */
|
||||
} else {
|
||||
$this->mode = self::AFTER_HEAD;
|
||||
}
|
||||
|
||||
/* Then, reprocess the current token. */
|
||||
return $this->afterHead($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function afterHead($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
if($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Append the character to the current node. */
|
||||
$this->insertText($token['data']);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data attribute
|
||||
set to the data given in the comment token. */
|
||||
$this->insertComment($token['data']);
|
||||
|
||||
/* A start tag token with the tag name "body" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') {
|
||||
/* Insert a body element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Change the insertion mode to "in body". */
|
||||
$this->mode = self::IN_BODY;
|
||||
|
||||
/* A start tag token with the tag name "frameset" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') {
|
||||
/* Insert a frameset element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Change the insertion mode to "in frameset". */
|
||||
$this->mode = self::IN_FRAME;
|
||||
|
||||
/* A start tag token whose tag name is one of: "base", "link", "meta",
|
||||
"script", "style", "title" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('base', 'link', 'meta', 'script', 'style', 'title'))) {
|
||||
/* Parse error. Switch the insertion mode back to "in head" and
|
||||
reprocess the token. */
|
||||
$this->mode = self::IN_HEAD;
|
||||
return $this->inHead($token);
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Act as if a start tag token with the tag name "body" and no
|
||||
attributes had been seen, and then reprocess the current token. */
|
||||
$this->afterHead(array(
|
||||
'name' => 'body',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
return $this->inBody($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inBody($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
switch($token['type']) {
|
||||
/* A character token */
|
||||
case HTML5::CHARACTR:
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Append the token's character to the current node. */
|
||||
$this->insertText($token['data']);
|
||||
break;
|
||||
|
||||
/* A comment token */
|
||||
case HTML5::COMMENT:
|
||||
/* Append a Comment node to the current node with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$this->insertComment($token['data']);
|
||||
break;
|
||||
|
||||
case HTML5::STARTTAG:
|
||||
switch($token['name']) {
|
||||
/* A start tag token whose tag name is one of: "script",
|
||||
"style" */
|
||||
case 'script': case 'style':
|
||||
/* Process the token as if the insertion mode had been "in
|
||||
head". */
|
||||
return $this->inHead($token);
|
||||
break;
|
||||
|
||||
/* A start tag token whose tag name is one of: "base", "link",
|
||||
"meta", "title" */
|
||||
case 'base': case 'link': case 'meta': case 'title':
|
||||
/* Parse error. Process the token as if the insertion mode
|
||||
had been "in head". */
|
||||
return $this->inHead($token);
|
||||
break;
|
||||
|
||||
/* A start tag token with the tag name "body" */
|
||||
case 'body':
|
||||
/* Parse error. If the second element on the stack of open
|
||||
elements is not a body element, or, if the stack of open
|
||||
elements has only one node on it, then ignore the token.
|
||||
(innerHTML case) */
|
||||
if(count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') {
|
||||
// Ignore
|
||||
|
||||
/* Otherwise, for each attribute on the token, check to see
|
||||
if the attribute is already present on the body element (the
|
||||
second element) on the stack of open elements. If it is not,
|
||||
add the attribute and its corresponding value to that
|
||||
element. */
|
||||
} else {
|
||||
foreach($token['attr'] as $attr) {
|
||||
if(!$this->stack[1]->hasAttribute($attr['name'])) {
|
||||
$this->stack[1]->setAttribute($attr['name'], $attr['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is one of: "address",
|
||||
"blockquote", "center", "dir", "div", "dl", "fieldset",
|
||||
"listing", "menu", "ol", "p", "ul" */
|
||||
case 'address': case 'blockquote': case 'center': case 'dir':
|
||||
case 'div': case 'dl': case 'fieldset': case 'listing':
|
||||
case 'menu': case 'ol': case 'p': case 'ul':
|
||||
/* If the stack of open elements has a p element in scope,
|
||||
then act as if an end tag with the tag name p had been
|
||||
seen. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->emitToken(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "form" */
|
||||
case 'form':
|
||||
/* If the form element pointer is not null, ignore the
|
||||
token with a parse error. */
|
||||
if($this->form_pointer !== null) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* If the stack of open elements has a p element in
|
||||
scope, then act as if an end tag with the tag name p
|
||||
had been seen. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->emitToken(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token, and set the
|
||||
form element pointer to point to the element created. */
|
||||
$element = $this->insertElement($token);
|
||||
$this->form_pointer = $element;
|
||||
}
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "li", "dd" or "dt" */
|
||||
case 'li': case 'dd': case 'dt':
|
||||
/* If the stack of open elements has a p element in scope,
|
||||
then act as if an end tag with the tag name p had been
|
||||
seen. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->emitToken(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
$stack_length = count($this->stack) - 1;
|
||||
|
||||
for($n = $stack_length; 0 <= $n; $n--) {
|
||||
/* 1. Initialise node to be the current node (the
|
||||
bottommost node of the stack). */
|
||||
$stop = false;
|
||||
$node = $this->stack[$n];
|
||||
$cat = $this->getElementCategory($node->tagName);
|
||||
|
||||
/* 2. If node is an li, dd or dt element, then pop all
|
||||
the nodes from the current node up to node, including
|
||||
node, then stop this algorithm. */
|
||||
if($token['name'] === $node->tagName || ($token['name'] !== 'li'
|
||||
&& ($node->tagName === 'dd' || $node->tagName === 'dt'))) {
|
||||
for($x = $stack_length; $x >= $n ; $x--) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* 3. If node is not in the formatting category, and is
|
||||
not in the phrasing category, and is not an address or
|
||||
div element, then stop this algorithm. */
|
||||
if($cat !== self::FORMATTING && $cat !== self::PHRASING &&
|
||||
$node->tagName !== 'address' && $node->tagName !== 'div') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, insert an HTML element with the same tag
|
||||
name as the token's. */
|
||||
$this->insertElement($token);
|
||||
break;
|
||||
|
||||
/* A start tag token whose tag name is "plaintext" */
|
||||
case 'plaintext':
|
||||
/* If the stack of open elements has a p element in scope,
|
||||
then act as if an end tag with the tag name p had been
|
||||
seen. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->emitToken(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
return HTML5::PLAINTEXT;
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is one of: "h1", "h2", "h3", "h4",
|
||||
"h5", "h6" */
|
||||
case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
|
||||
/* If the stack of open elements has a p element in scope,
|
||||
then act as if an end tag with the tag name p had been seen. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->emitToken(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* If the stack of open elements has in scope an element whose
|
||||
tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
|
||||
this is a parse error; pop elements from the stack until an
|
||||
element with one of those tag names has been popped from the
|
||||
stack. */
|
||||
while($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "a" */
|
||||
case 'a':
|
||||
/* If the list of active formatting elements contains
|
||||
an element whose tag name is "a" between the end of the
|
||||
list and the last marker on the list (or the start of
|
||||
the list if there is no marker on the list), then this
|
||||
is a parse error; act as if an end tag with the tag name
|
||||
"a" had been seen, then remove that element from the list
|
||||
of active formatting elements and the stack of open
|
||||
elements if the end tag didn't already remove it (it
|
||||
might not have if the element is not in table scope). */
|
||||
$leng = count($this->a_formatting);
|
||||
|
||||
for($n = $leng - 1; $n >= 0; $n--) {
|
||||
if($this->a_formatting[$n] === self::MARKER) {
|
||||
break;
|
||||
|
||||
} elseif($this->a_formatting[$n]->nodeName === 'a') {
|
||||
$this->emitToken(array(
|
||||
'name' => 'a',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$el = $this->insertElement($token);
|
||||
|
||||
/* Add that element to the list of active formatting
|
||||
elements. */
|
||||
$this->a_formatting[] = $el;
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is one of: "b", "big", "em", "font",
|
||||
"i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
|
||||
case 'b': case 'big': case 'em': case 'font': case 'i':
|
||||
case 'nobr': case 's': case 'small': case 'strike':
|
||||
case 'strong': case 'tt': case 'u':
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$el = $this->insertElement($token);
|
||||
|
||||
/* Add that element to the list of active formatting
|
||||
elements. */
|
||||
$this->a_formatting[] = $el;
|
||||
break;
|
||||
|
||||
/* A start tag token whose tag name is "button" */
|
||||
case 'button':
|
||||
/* If the stack of open elements has a button element in scope,
|
||||
then this is a parse error; act as if an end tag with the tag
|
||||
name "button" had been seen, then reprocess the token. (We don't
|
||||
do that. Unnecessary.) */
|
||||
if($this->elementInScope('button')) {
|
||||
$this->inBody(array(
|
||||
'name' => 'button',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Insert a marker at the end of the list of active
|
||||
formatting elements. */
|
||||
$this->a_formatting[] = self::MARKER;
|
||||
break;
|
||||
|
||||
/* A start tag token whose tag name is one of: "marquee", "object" */
|
||||
case 'marquee': case 'object':
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Insert a marker at the end of the list of active
|
||||
formatting elements. */
|
||||
$this->a_formatting[] = self::MARKER;
|
||||
break;
|
||||
|
||||
/* A start tag token whose tag name is "xmp" */
|
||||
case 'xmp':
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Switch the content model flag to the CDATA state. */
|
||||
return HTML5::CDATA;
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "table" */
|
||||
case 'table':
|
||||
/* If the stack of open elements has a p element in scope,
|
||||
then act as if an end tag with the tag name p had been seen. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->emitToken(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Change the insertion mode to "in table". */
|
||||
$this->mode = self::IN_TABLE;
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is one of: "area", "basefont",
|
||||
"bgsound", "br", "embed", "img", "param", "spacer", "wbr" */
|
||||
case 'area': case 'basefont': case 'bgsound': case 'br':
|
||||
case 'embed': case 'img': case 'param': case 'spacer':
|
||||
case 'wbr':
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Immediately pop the current node off the stack of open elements. */
|
||||
array_pop($this->stack);
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "hr" */
|
||||
case 'hr':
|
||||
/* If the stack of open elements has a p element in scope,
|
||||
then act as if an end tag with the tag name p had been seen. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->emitToken(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Immediately pop the current node off the stack of open elements. */
|
||||
array_pop($this->stack);
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "image" */
|
||||
case 'image':
|
||||
/* Parse error. Change the token's tag name to "img" and
|
||||
reprocess it. (Don't ask.) */
|
||||
$token['name'] = 'img';
|
||||
return $this->inBody($token);
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "input" */
|
||||
case 'input':
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an input element for the token. */
|
||||
$element = $this->insertElement($token, false);
|
||||
|
||||
/* If the form element pointer is not null, then associate the
|
||||
input element with the form element pointed to by the form
|
||||
element pointer. */
|
||||
$this->form_pointer !== null
|
||||
? $this->form_pointer->appendChild($element)
|
||||
: end($this->stack)->appendChild($element);
|
||||
|
||||
/* Pop that input element off the stack of open elements. */
|
||||
array_pop($this->stack);
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "isindex" */
|
||||
case 'isindex':
|
||||
/* Parse error. */
|
||||
// w/e
|
||||
|
||||
/* If the form element pointer is not null,
|
||||
then ignore the token. */
|
||||
if($this->form_pointer === null) {
|
||||
/* Act as if a start tag token with the tag name "form" had
|
||||
been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'body',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
/* Act as if a start tag token with the tag name "hr" had
|
||||
been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'hr',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
/* Act as if a start tag token with the tag name "p" had
|
||||
been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
/* Act as if a start tag token with the tag name "label"
|
||||
had been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'label',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
/* Act as if a stream of character tokens had been seen. */
|
||||
$this->insertText('This is a searchable index. '.
|
||||
'Insert your search keywords here: ');
|
||||
|
||||
/* Act as if a start tag token with the tag name "input"
|
||||
had been seen, with all the attributes from the "isindex"
|
||||
token, except with the "name" attribute set to the value
|
||||
"isindex" (ignoring any explicit "name" attribute). */
|
||||
$attr = $token['attr'];
|
||||
$attr[] = array('name' => 'name', 'value' => 'isindex');
|
||||
|
||||
$this->inBody(array(
|
||||
'name' => 'input',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => $attr
|
||||
));
|
||||
|
||||
/* Act as if a stream of character tokens had been seen
|
||||
(see below for what they should say). */
|
||||
$this->insertText('This is a searchable index. '.
|
||||
'Insert your search keywords here: ');
|
||||
|
||||
/* Act as if an end tag token with the tag name "label"
|
||||
had been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'label',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
/* Act as if an end tag token with the tag name "p" had
|
||||
been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'p',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
/* Act as if a start tag token with the tag name "hr" had
|
||||
been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'hr',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
/* Act as if an end tag token with the tag name "form" had
|
||||
been seen. */
|
||||
$this->inBody(array(
|
||||
'name' => 'form',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "textarea" */
|
||||
case 'textarea':
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Switch the tokeniser's content model flag to the
|
||||
RCDATA state. */
|
||||
return HTML5::RCDATA;
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is one of: "iframe", "noembed",
|
||||
"noframes" */
|
||||
case 'iframe': case 'noembed': case 'noframes':
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Switch the tokeniser's content model flag to the CDATA state. */
|
||||
return HTML5::CDATA;
|
||||
break;
|
||||
|
||||
/* A start tag whose tag name is "select" */
|
||||
case 'select':
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Change the insertion mode to "in select". */
|
||||
$this->mode = self::IN_SELECT;
|
||||
break;
|
||||
|
||||
/* A start or end tag whose tag name is one of: "caption", "col",
|
||||
"colgroup", "frame", "frameset", "head", "option", "optgroup",
|
||||
"tbody", "td", "tfoot", "th", "thead", "tr". */
|
||||
case 'caption': case 'col': case 'colgroup': case 'frame':
|
||||
case 'frameset': case 'head': case 'option': case 'optgroup':
|
||||
case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead':
|
||||
case 'tr':
|
||||
// Parse error. Ignore the token.
|
||||
break;
|
||||
|
||||
/* A start or end tag whose tag name is one of: "event-source",
|
||||
"section", "nav", "article", "aside", "header", "footer",
|
||||
"datagrid", "command" */
|
||||
case 'event-source': case 'section': case 'nav': case 'article':
|
||||
case 'aside': case 'header': case 'footer': case 'datagrid':
|
||||
case 'command':
|
||||
// Work in progress!
|
||||
break;
|
||||
|
||||
/* A start tag token not covered by the previous entries */
|
||||
default:
|
||||
/* Reconstruct the active formatting elements, if any. */
|
||||
$this->reconstructActiveFormattingElements();
|
||||
|
||||
$this->insertElement($token, true, true);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case HTML5::ENDTAG:
|
||||
switch($token['name']) {
|
||||
/* An end tag with the tag name "body" */
|
||||
case 'body':
|
||||
/* If the second element in the stack of open elements is
|
||||
not a body element, this is a parse error. Ignore the token.
|
||||
(innerHTML case) */
|
||||
if(count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') {
|
||||
// Ignore.
|
||||
|
||||
/* If the current node is not the body element, then this
|
||||
is a parse error. */
|
||||
} elseif(end($this->stack)->nodeName !== 'body') {
|
||||
// Parse error.
|
||||
}
|
||||
|
||||
/* Change the insertion mode to "after body". */
|
||||
$this->mode = self::AFTER_BODY;
|
||||
break;
|
||||
|
||||
/* An end tag with the tag name "html" */
|
||||
case 'html':
|
||||
/* Act as if an end tag with tag name "body" had been seen,
|
||||
then, if that token wasn't ignored, reprocess the current
|
||||
token. */
|
||||
$this->inBody(array(
|
||||
'name' => 'body',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
return $this->afterBody($token);
|
||||
break;
|
||||
|
||||
/* An end tag whose tag name is one of: "address", "blockquote",
|
||||
"center", "dir", "div", "dl", "fieldset", "listing", "menu",
|
||||
"ol", "pre", "ul" */
|
||||
case 'address': case 'blockquote': case 'center': case 'dir':
|
||||
case 'div': case 'dl': case 'fieldset': case 'listing':
|
||||
case 'menu': case 'ol': case 'pre': case 'ul':
|
||||
/* If the stack of open elements has an element in scope
|
||||
with the same tag name as that of the token, then generate
|
||||
implied end tags. */
|
||||
if($this->elementInScope($token['name'])) {
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
/* Now, if the current node is not an element with
|
||||
the same tag name as that of the token, then this
|
||||
is a parse error. */
|
||||
// w/e
|
||||
|
||||
/* If the stack of open elements has an element in
|
||||
scope with the same tag name as that of the token,
|
||||
then pop elements from this stack until an element
|
||||
with that tag name has been popped from the stack. */
|
||||
for($n = count($this->stack) - 1; $n >= 0; $n--) {
|
||||
if($this->stack[$n]->nodeName === $token['name']) {
|
||||
$n = -1;
|
||||
}
|
||||
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* An end tag whose tag name is "form" */
|
||||
case 'form':
|
||||
/* If the stack of open elements has an element in scope
|
||||
with the same tag name as that of the token, then generate
|
||||
implied end tags. */
|
||||
if($this->elementInScope($token['name'])) {
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
}
|
||||
|
||||
if(end($this->stack)->nodeName !== $token['name']) {
|
||||
/* Now, if the current node is not an element with the
|
||||
same tag name as that of the token, then this is a parse
|
||||
error. */
|
||||
// w/e
|
||||
|
||||
} else {
|
||||
/* Otherwise, if the current node is an element with
|
||||
the same tag name as that of the token pop that element
|
||||
from the stack. */
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
/* In any case, set the form element pointer to null. */
|
||||
$this->form_pointer = null;
|
||||
break;
|
||||
|
||||
/* An end tag whose tag name is "p" */
|
||||
case 'p':
|
||||
/* If the stack of open elements has a p element in scope,
|
||||
then generate implied end tags, except for p elements. */
|
||||
if($this->elementInScope('p')) {
|
||||
$this->generateImpliedEndTags(array('p'));
|
||||
|
||||
/* If the current node is not a p element, then this is
|
||||
a parse error. */
|
||||
// k
|
||||
|
||||
/* If the stack of open elements has a p element in
|
||||
scope, then pop elements from this stack until the stack
|
||||
no longer has a p element in scope. */
|
||||
for($n = count($this->stack) - 1; $n >= 0; $n--) {
|
||||
if($this->elementInScope('p')) {
|
||||
array_pop($this->stack);
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* An end tag whose tag name is "dd", "dt", or "li" */
|
||||
case 'dd': case 'dt': case 'li':
|
||||
/* If the stack of open elements has an element in scope
|
||||
whose tag name matches the tag name of the token, then
|
||||
generate implied end tags, except for elements with the
|
||||
same tag name as the token. */
|
||||
if($this->elementInScope($token['name'])) {
|
||||
$this->generateImpliedEndTags(array($token['name']));
|
||||
|
||||
/* If the current node is not an element with the same
|
||||
tag name as the token, then this is a parse error. */
|
||||
// w/e
|
||||
|
||||
/* If the stack of open elements has an element in scope
|
||||
whose tag name matches the tag name of the token, then
|
||||
pop elements from this stack until an element with that
|
||||
tag name has been popped from the stack. */
|
||||
for($n = count($this->stack) - 1; $n >= 0; $n--) {
|
||||
if($this->stack[$n]->nodeName === $token['name']) {
|
||||
$n = -1;
|
||||
}
|
||||
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* An end tag whose tag name is one of: "h1", "h2", "h3", "h4",
|
||||
"h5", "h6" */
|
||||
case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
|
||||
$elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6');
|
||||
|
||||
/* If the stack of open elements has in scope an element whose
|
||||
tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
|
||||
generate implied end tags. */
|
||||
if($this->elementInScope($elements)) {
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
/* Now, if the current node is not an element with the same
|
||||
tag name as that of the token, then this is a parse error. */
|
||||
// w/e
|
||||
|
||||
/* If the stack of open elements has in scope an element
|
||||
whose tag name is one of "h1", "h2", "h3", "h4", "h5", or
|
||||
"h6", then pop elements from the stack until an element
|
||||
with one of those tag names has been popped from the stack. */
|
||||
while($this->elementInScope($elements)) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* An end tag whose tag name is one of: "a", "b", "big", "em",
|
||||
"font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
|
||||
case 'a': case 'b': case 'big': case 'em': case 'font':
|
||||
case 'i': case 'nobr': case 's': case 'small': case 'strike':
|
||||
case 'strong': case 'tt': case 'u':
|
||||
/* 1. Let the formatting element be the last element in
|
||||
the list of active formatting elements that:
|
||||
* is between the end of the list and the last scope
|
||||
marker in the list, if any, or the start of the list
|
||||
otherwise, and
|
||||
* has the same tag name as the token.
|
||||
*/
|
||||
while(true) {
|
||||
for($a = count($this->a_formatting) - 1; $a >= 0; $a--) {
|
||||
if($this->a_formatting[$a] === self::MARKER) {
|
||||
break;
|
||||
|
||||
} elseif($this->a_formatting[$a]->tagName === $token['name']) {
|
||||
$formatting_element = $this->a_formatting[$a];
|
||||
$in_stack = in_array($formatting_element, $this->stack, true);
|
||||
$fe_af_pos = $a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there is no such node, or, if that node is
|
||||
also in the stack of open elements but the element
|
||||
is not in scope, then this is a parse error. Abort
|
||||
these steps. The token is ignored. */
|
||||
if(!isset($formatting_element) || ($in_stack &&
|
||||
!$this->elementInScope($token['name']))) {
|
||||
break;
|
||||
|
||||
/* Otherwise, if there is such a node, but that node
|
||||
is not in the stack of open elements, then this is a
|
||||
parse error; remove the element from the list, and
|
||||
abort these steps. */
|
||||
} elseif(isset($formatting_element) && !$in_stack) {
|
||||
unset($this->a_formatting[$fe_af_pos]);
|
||||
$this->a_formatting = array_merge($this->a_formatting);
|
||||
break;
|
||||
}
|
||||
|
||||
/* 2. Let the furthest block be the topmost node in the
|
||||
stack of open elements that is lower in the stack
|
||||
than the formatting element, and is not an element in
|
||||
the phrasing or formatting categories. There might
|
||||
not be one. */
|
||||
$fe_s_pos = array_search($formatting_element, $this->stack, true);
|
||||
$length = count($this->stack);
|
||||
|
||||
for($s = $fe_s_pos + 1; $s < $length; $s++) {
|
||||
$category = $this->getElementCategory($this->stack[$s]->nodeName);
|
||||
|
||||
if($category !== self::PHRASING && $category !== self::FORMATTING) {
|
||||
$furthest_block = $this->stack[$s];
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. If there is no furthest block, then the UA must
|
||||
skip the subsequent steps and instead just pop all
|
||||
the nodes from the bottom of the stack of open
|
||||
elements, from the current node up to the formatting
|
||||
element, and remove the formatting element from the
|
||||
list of active formatting elements. */
|
||||
if(!isset($furthest_block)) {
|
||||
for($n = $length - 1; $n >= $fe_s_pos; $n--) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
unset($this->a_formatting[$fe_af_pos]);
|
||||
$this->a_formatting = array_merge($this->a_formatting);
|
||||
break;
|
||||
}
|
||||
|
||||
/* 4. Let the common ancestor be the element
|
||||
immediately above the formatting element in the stack
|
||||
of open elements. */
|
||||
$common_ancestor = $this->stack[$fe_s_pos - 1];
|
||||
|
||||
/* 5. If the furthest block has a parent node, then
|
||||
remove the furthest block from its parent node. */
|
||||
if($furthest_block->parentNode !== null) {
|
||||
$furthest_block->parentNode->removeChild($furthest_block);
|
||||
}
|
||||
|
||||
/* 6. Let a bookmark note the position of the
|
||||
formatting element in the list of active formatting
|
||||
elements relative to the elements on either side
|
||||
of it in the list. */
|
||||
$bookmark = $fe_af_pos;
|
||||
|
||||
/* 7. Let node and last node be the furthest block.
|
||||
Follow these steps: */
|
||||
$node = $furthest_block;
|
||||
$last_node = $furthest_block;
|
||||
|
||||
while(true) {
|
||||
for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) {
|
||||
/* 7.1 Let node be the element immediately
|
||||
prior to node in the stack of open elements. */
|
||||
$node = $this->stack[$n];
|
||||
|
||||
/* 7.2 If node is not in the list of active
|
||||
formatting elements, then remove node from
|
||||
the stack of open elements and then go back
|
||||
to step 1. */
|
||||
if(!in_array($node, $this->a_formatting, true)) {
|
||||
unset($this->stack[$n]);
|
||||
$this->stack = array_merge($this->stack);
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 7.3 Otherwise, if node is the formatting
|
||||
element, then go to the next step in the overall
|
||||
algorithm. */
|
||||
if($node === $formatting_element) {
|
||||
break;
|
||||
|
||||
/* 7.4 Otherwise, if last node is the furthest
|
||||
block, then move the aforementioned bookmark to
|
||||
be immediately after the node in the list of
|
||||
active formatting elements. */
|
||||
} elseif($last_node === $furthest_block) {
|
||||
$bookmark = array_search($node, $this->a_formatting, true) + 1;
|
||||
}
|
||||
|
||||
/* 7.5 If node has any children, perform a
|
||||
shallow clone of node, replace the entry for
|
||||
node in the list of active formatting elements
|
||||
with an entry for the clone, replace the entry
|
||||
for node in the stack of open elements with an
|
||||
entry for the clone, and let node be the clone. */
|
||||
if($node->hasChildNodes()) {
|
||||
$clone = $node->cloneNode();
|
||||
$s_pos = array_search($node, $this->stack, true);
|
||||
$a_pos = array_search($node, $this->a_formatting, true);
|
||||
|
||||
$this->stack[$s_pos] = $clone;
|
||||
$this->a_formatting[$a_pos] = $clone;
|
||||
$node = $clone;
|
||||
}
|
||||
|
||||
/* 7.6 Insert last node into node, first removing
|
||||
it from its previous parent node if any. */
|
||||
if($last_node->parentNode !== null) {
|
||||
$last_node->parentNode->removeChild($last_node);
|
||||
}
|
||||
|
||||
$node->appendChild($last_node);
|
||||
|
||||
/* 7.7 Let last node be node. */
|
||||
$last_node = $node;
|
||||
}
|
||||
|
||||
/* 8. Insert whatever last node ended up being in
|
||||
the previous step into the common ancestor node,
|
||||
first removing it from its previous parent node if
|
||||
any. */
|
||||
if($last_node->parentNode !== null) {
|
||||
$last_node->parentNode->removeChild($last_node);
|
||||
}
|
||||
|
||||
$common_ancestor->appendChild($last_node);
|
||||
|
||||
/* 9. Perform a shallow clone of the formatting
|
||||
element. */
|
||||
$clone = $formatting_element->cloneNode();
|
||||
|
||||
/* 10. Take all of the child nodes of the furthest
|
||||
block and append them to the clone created in the
|
||||
last step. */
|
||||
while($furthest_block->hasChildNodes()) {
|
||||
$child = $furthest_block->firstChild;
|
||||
$furthest_block->removeChild($child);
|
||||
$clone->appendChild($child);
|
||||
}
|
||||
|
||||
/* 11. Append that clone to the furthest block. */
|
||||
$furthest_block->appendChild($clone);
|
||||
|
||||
/* 12. Remove the formatting element from the list
|
||||
of active formatting elements, and insert the clone
|
||||
into the list of active formatting elements at the
|
||||
position of the aforementioned bookmark. */
|
||||
$fe_af_pos = array_search($formatting_element, $this->a_formatting, true);
|
||||
unset($this->a_formatting[$fe_af_pos]);
|
||||
$this->a_formatting = array_merge($this->a_formatting);
|
||||
|
||||
$af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1);
|
||||
$af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting));
|
||||
$this->a_formatting = array_merge($af_part1, array($clone), $af_part2);
|
||||
|
||||
/* 13. Remove the formatting element from the stack
|
||||
of open elements, and insert the clone into the stack
|
||||
of open elements immediately after (i.e. in a more
|
||||
deeply nested position than) the position of the
|
||||
furthest block in that stack. */
|
||||
$fe_s_pos = array_search($formatting_element, $this->stack, true);
|
||||
$fb_s_pos = array_search($furthest_block, $this->stack, true);
|
||||
unset($this->stack[$fe_s_pos]);
|
||||
|
||||
$s_part1 = array_slice($this->stack, 0, $fb_s_pos);
|
||||
$s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack));
|
||||
$this->stack = array_merge($s_part1, array($clone), $s_part2);
|
||||
|
||||
/* 14. Jump back to step 1 in this series of steps. */
|
||||
unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block);
|
||||
}
|
||||
break;
|
||||
|
||||
/* An end tag token whose tag name is one of: "button",
|
||||
"marquee", "object" */
|
||||
case 'button': case 'marquee': case 'object':
|
||||
/* If the stack of open elements has an element in scope whose
|
||||
tag name matches the tag name of the token, then generate implied
|
||||
tags. */
|
||||
if($this->elementInScope($token['name'])) {
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
/* Now, if the current node is not an element with the same
|
||||
tag name as the token, then this is a parse error. */
|
||||
// k
|
||||
|
||||
/* Now, if the stack of open elements has an element in scope
|
||||
whose tag name matches the tag name of the token, then pop
|
||||
elements from the stack until that element has been popped from
|
||||
the stack, and clear the list of active formatting elements up
|
||||
to the last marker. */
|
||||
for($n = count($this->stack) - 1; $n >= 0; $n--) {
|
||||
if($this->stack[$n]->nodeName === $token['name']) {
|
||||
$n = -1;
|
||||
}
|
||||
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
$marker = end(array_keys($this->a_formatting, self::MARKER, true));
|
||||
|
||||
for($n = count($this->a_formatting) - 1; $n > $marker; $n--) {
|
||||
array_pop($this->a_formatting);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* Or an end tag whose tag name is one of: "area", "basefont",
|
||||
"bgsound", "br", "embed", "hr", "iframe", "image", "img",
|
||||
"input", "isindex", "noembed", "noframes", "param", "select",
|
||||
"spacer", "table", "textarea", "wbr" */
|
||||
case 'area': case 'basefont': case 'bgsound': case 'br':
|
||||
case 'embed': case 'hr': case 'iframe': case 'image':
|
||||
case 'img': case 'input': case 'isindex': case 'noembed':
|
||||
case 'noframes': case 'param': case 'select': case 'spacer':
|
||||
case 'table': case 'textarea': case 'wbr':
|
||||
// Parse error. Ignore the token.
|
||||
break;
|
||||
|
||||
/* An end tag token not covered by the previous entries */
|
||||
default:
|
||||
for($n = count($this->stack) - 1; $n >= 0; $n--) {
|
||||
/* Initialise node to be the current node (the bottommost
|
||||
node of the stack). */
|
||||
$node = end($this->stack);
|
||||
|
||||
/* If node has the same tag name as the end tag token,
|
||||
then: */
|
||||
if($token['name'] === $node->nodeName) {
|
||||
/* Generate implied end tags. */
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
/* If the tag name of the end tag token does not
|
||||
match the tag name of the current node, this is a
|
||||
parse error. */
|
||||
// k
|
||||
|
||||
/* Pop all the nodes from the current node up to
|
||||
node, including node, then stop this algorithm. */
|
||||
for($x = count($this->stack) - $n; $x >= $n; $x--) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
} else {
|
||||
$category = $this->getElementCategory($node);
|
||||
|
||||
if($category !== self::SPECIAL && $category !== self::SCOPING) {
|
||||
/* Otherwise, if node is in neither the formatting
|
||||
category nor the phrasing category, then this is a
|
||||
parse error. Stop this algorithm. The end tag token
|
||||
is ignored. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function inTable($token) {
|
||||
$clear = array('html', 'table');
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
if($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Append the character to the current node. */
|
||||
$text = $this->dom->createTextNode($token['data']);
|
||||
end($this->stack)->appendChild($text);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$comment = $this->dom->createComment($token['data']);
|
||||
end($this->stack)->appendChild($comment);
|
||||
|
||||
/* A start tag whose tag name is "caption" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
$token['name'] === 'caption') {
|
||||
/* Clear the stack back to a table context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Insert a marker at the end of the list of active
|
||||
formatting elements. */
|
||||
$this->a_formatting[] = self::MARKER;
|
||||
|
||||
/* Insert an HTML element for the token, then switch the
|
||||
insertion mode to "in caption". */
|
||||
$this->insertElement($token);
|
||||
$this->mode = self::IN_CAPTION;
|
||||
|
||||
/* A start tag whose tag name is "colgroup" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
$token['name'] === 'colgroup') {
|
||||
/* Clear the stack back to a table context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Insert an HTML element for the token, then switch the
|
||||
insertion mode to "in column group". */
|
||||
$this->insertElement($token);
|
||||
$this->mode = self::IN_CGROUP;
|
||||
|
||||
/* A start tag whose tag name is "col" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
$token['name'] === 'col') {
|
||||
$this->inTable(array(
|
||||
'name' => 'colgroup',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
$this->inColumnGroup($token);
|
||||
|
||||
/* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('tbody', 'tfoot', 'thead'))) {
|
||||
/* Clear the stack back to a table context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Insert an HTML element for the token, then switch the insertion
|
||||
mode to "in table body". */
|
||||
$this->insertElement($token);
|
||||
$this->mode = self::IN_TBODY;
|
||||
|
||||
/* A start tag whose tag name is one of: "td", "th", "tr" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
in_array($token['name'], array('td', 'th', 'tr'))) {
|
||||
/* Act as if a start tag token with the tag name "tbody" had been
|
||||
seen, then reprocess the current token. */
|
||||
$this->inTable(array(
|
||||
'name' => 'tbody',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
return $this->inTableBody($token);
|
||||
|
||||
/* A start tag whose tag name is "table" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
$token['name'] === 'table') {
|
||||
/* Parse error. Act as if an end tag token with the tag name "table"
|
||||
had been seen, then, if that token wasn't ignored, reprocess the
|
||||
current token. */
|
||||
$this->inTable(array(
|
||||
'name' => 'table',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
return $this->mainPhase($token);
|
||||
|
||||
/* An end tag whose tag name is "table" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
$token['name'] === 'table') {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as the token, this is a parse error.
|
||||
Ignore the token. (innerHTML case) */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
return false;
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Generate implied end tags. */
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
/* Now, if the current node is not a table element, then this
|
||||
is a parse error. */
|
||||
// w/e
|
||||
|
||||
/* Pop elements from this stack until a table element has been
|
||||
popped from the stack. */
|
||||
while(true) {
|
||||
$current = end($this->stack)->nodeName;
|
||||
array_pop($this->stack);
|
||||
|
||||
if($current === 'table') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset the insertion mode appropriately. */
|
||||
$this->resetInsertionMode();
|
||||
}
|
||||
|
||||
/* An end tag whose tag name is one of: "body", "caption", "col",
|
||||
"colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
|
||||
array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td',
|
||||
'tfoot', 'th', 'thead', 'tr'))) {
|
||||
// Parse error. Ignore the token.
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Parse error. Process the token as if the insertion mode was "in
|
||||
body", with the following exception: */
|
||||
|
||||
/* If the current node is a table, tbody, tfoot, thead, or tr
|
||||
element, then, whenever a node would be inserted into the current
|
||||
node, it must instead be inserted into the foster parent element. */
|
||||
if(in_array(end($this->stack)->nodeName,
|
||||
array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
|
||||
/* The foster parent element is the parent element of the last
|
||||
table element in the stack of open elements, if there is a
|
||||
table element and it has such a parent element. If there is no
|
||||
table element in the stack of open elements (innerHTML case),
|
||||
then the foster parent element is the first element in the
|
||||
stack of open elements (the html element). Otherwise, if there
|
||||
is a table element in the stack of open elements, but the last
|
||||
table element in the stack of open elements has no parent, or
|
||||
its parent node is not an element, then the foster parent
|
||||
element is the element before the last table element in the
|
||||
stack of open elements. */
|
||||
for($n = count($this->stack) - 1; $n >= 0; $n--) {
|
||||
if($this->stack[$n]->nodeName === 'table') {
|
||||
$table = $this->stack[$n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($table) && $table->parentNode !== null) {
|
||||
$this->foster_parent = $table->parentNode;
|
||||
|
||||
} elseif(!isset($table)) {
|
||||
$this->foster_parent = $this->stack[0];
|
||||
|
||||
} elseif(isset($table) && ($table->parentNode === null ||
|
||||
$table->parentNode->nodeType !== XML_ELEMENT_NODE)) {
|
||||
$this->foster_parent = $this->stack[$n - 1];
|
||||
}
|
||||
}
|
||||
|
||||
$this->inBody($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inCaption($token) {
|
||||
/* An end tag whose tag name is "caption" */
|
||||
if($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as the token, this is a parse error.
|
||||
Ignore the token. (innerHTML case) */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
// Ignore
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Generate implied end tags. */
|
||||
$this->generateImpliedEndTags();
|
||||
|
||||
/* Now, if the current node is not a caption element, then this
|
||||
is a parse error. */
|
||||
// w/e
|
||||
|
||||
/* Pop elements from this stack until a caption element has
|
||||
been popped from the stack. */
|
||||
while(true) {
|
||||
$node = end($this->stack)->nodeName;
|
||||
array_pop($this->stack);
|
||||
|
||||
if($node === 'caption') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the list of active formatting elements up to the last
|
||||
marker. */
|
||||
$this->clearTheActiveFormattingElementsUpToTheLastMarker();
|
||||
|
||||
/* Switch the insertion mode to "in table". */
|
||||
$this->mode = self::IN_TABLE;
|
||||
}
|
||||
|
||||
/* A start tag whose tag name is one of: "caption", "col", "colgroup",
|
||||
"tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag
|
||||
name is "table" */
|
||||
} elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
|
||||
'thead', 'tr'))) || ($token['type'] === HTML5::ENDTAG &&
|
||||
$token['name'] === 'table')) {
|
||||
/* Parse error. Act as if an end tag with the tag name "caption"
|
||||
had been seen, then, if that token wasn't ignored, reprocess the
|
||||
current token. */
|
||||
$this->inCaption(array(
|
||||
'name' => 'caption',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
return $this->inTable($token);
|
||||
|
||||
/* An end tag whose tag name is one of: "body", "col", "colgroup",
|
||||
"html", "tbody", "td", "tfoot", "th", "thead", "tr" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
|
||||
array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th',
|
||||
'thead', 'tr'))) {
|
||||
// Parse error. Ignore the token.
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Process the token as if the insertion mode was "in body". */
|
||||
$this->inBody($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inColumnGroup($token) {
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
if($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Append the character to the current node. */
|
||||
$text = $this->dom->createTextNode($token['data']);
|
||||
end($this->stack)->appendChild($text);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$comment = $this->dom->createComment($token['data']);
|
||||
end($this->stack)->appendChild($comment);
|
||||
|
||||
/* A start tag whose tag name is "col" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') {
|
||||
/* Insert a col element for the token. Immediately pop the current
|
||||
node off the stack of open elements. */
|
||||
$this->insertElement($token);
|
||||
array_pop($this->stack);
|
||||
|
||||
/* An end tag whose tag name is "colgroup" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
$token['name'] === 'colgroup') {
|
||||
/* If the current node is the root html element, then this is a
|
||||
parse error, ignore the token. (innerHTML case) */
|
||||
if(end($this->stack)->nodeName === 'html') {
|
||||
// Ignore
|
||||
|
||||
/* Otherwise, pop the current node (which will be a colgroup
|
||||
element) from the stack of open elements. Switch the insertion
|
||||
mode to "in table". */
|
||||
} else {
|
||||
array_pop($this->stack);
|
||||
$this->mode = self::IN_TABLE;
|
||||
}
|
||||
|
||||
/* An end tag whose tag name is "col" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') {
|
||||
/* Parse error. Ignore the token. */
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Act as if an end tag with the tag name "colgroup" had been seen,
|
||||
and then, if that token wasn't ignored, reprocess the current token. */
|
||||
$this->inColumnGroup(array(
|
||||
'name' => 'colgroup',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
return $this->inTable($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inTableBody($token) {
|
||||
$clear = array('tbody', 'tfoot', 'thead', 'html');
|
||||
|
||||
/* A start tag whose tag name is "tr" */
|
||||
if($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') {
|
||||
/* Clear the stack back to a table body context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Insert a tr element for the token, then switch the insertion
|
||||
mode to "in row". */
|
||||
$this->insertElement($token);
|
||||
$this->mode = self::IN_ROW;
|
||||
|
||||
/* A start tag whose tag name is one of: "th", "td" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
($token['name'] === 'th' || $token['name'] === 'td')) {
|
||||
/* Parse error. Act as if a start tag with the tag name "tr" had
|
||||
been seen, then reprocess the current token. */
|
||||
$this->inTableBody(array(
|
||||
'name' => 'tr',
|
||||
'type' => HTML5::STARTTAG,
|
||||
'attr' => array()
|
||||
));
|
||||
|
||||
return $this->inRow($token);
|
||||
|
||||
/* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as the token, this is a parse error.
|
||||
Ignore the token. */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
// Ignore
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Clear the stack back to a table body context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Pop the current node from the stack of open elements. Switch
|
||||
the insertion mode to "in table". */
|
||||
array_pop($this->stack);
|
||||
$this->mode = self::IN_TABLE;
|
||||
}
|
||||
|
||||
/* A start tag whose tag name is one of: "caption", "col", "colgroup",
|
||||
"tbody", "tfoot", "thead", or an end tag whose tag name is "table" */
|
||||
} elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead'))) ||
|
||||
($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')) {
|
||||
/* If the stack of open elements does not have a tbody, thead, or
|
||||
tfoot element in table scope, this is a parse error. Ignore the
|
||||
token. (innerHTML case) */
|
||||
if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Clear the stack back to a table body context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Act as if an end tag with the same tag name as the current
|
||||
node ("tbody", "tfoot", or "thead") had been seen, then
|
||||
reprocess the current token. */
|
||||
$this->inTableBody(array(
|
||||
'name' => end($this->stack)->nodeName,
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
return $this->mainPhase($token);
|
||||
}
|
||||
|
||||
/* An end tag whose tag name is one of: "body", "caption", "col",
|
||||
"colgroup", "html", "td", "th", "tr" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
|
||||
array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) {
|
||||
/* Parse error. Ignore the token. */
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Process the token as if the insertion mode was "in table". */
|
||||
$this->inTable($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inRow($token) {
|
||||
$clear = array('tr', 'html');
|
||||
|
||||
/* A start tag whose tag name is one of: "th", "td" */
|
||||
if($token['type'] === HTML5::STARTTAG &&
|
||||
($token['name'] === 'th' || $token['name'] === 'td')) {
|
||||
/* Clear the stack back to a table row context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Insert an HTML element for the token, then switch the insertion
|
||||
mode to "in cell". */
|
||||
$this->insertElement($token);
|
||||
$this->mode = self::IN_CELL;
|
||||
|
||||
/* Insert a marker at the end of the list of active formatting
|
||||
elements. */
|
||||
$this->a_formatting[] = self::MARKER;
|
||||
|
||||
/* An end tag whose tag name is "tr" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as the token, this is a parse error.
|
||||
Ignore the token. (innerHTML case) */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Clear the stack back to a table row context. */
|
||||
$this->clearStackToTableContext($clear);
|
||||
|
||||
/* Pop the current node (which will be a tr element) from the
|
||||
stack of open elements. Switch the insertion mode to "in table
|
||||
body". */
|
||||
array_pop($this->stack);
|
||||
$this->mode = self::IN_TBODY;
|
||||
}
|
||||
|
||||
/* A start tag whose tag name is one of: "caption", "col", "colgroup",
|
||||
"tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) {
|
||||
/* Act as if an end tag with the tag name "tr" had been seen, then,
|
||||
if that token wasn't ignored, reprocess the current token. */
|
||||
$this->inRow(array(
|
||||
'name' => 'tr',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
return $this->inCell($token);
|
||||
|
||||
/* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as the token, this is a parse error.
|
||||
Ignore the token. */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Otherwise, act as if an end tag with the tag name "tr" had
|
||||
been seen, then reprocess the current token. */
|
||||
$this->inRow(array(
|
||||
'name' => 'tr',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
return $this->inCell($token);
|
||||
}
|
||||
|
||||
/* An end tag whose tag name is one of: "body", "caption", "col",
|
||||
"colgroup", "html", "td", "th" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
|
||||
array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) {
|
||||
/* Parse error. Ignore the token. */
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Process the token as if the insertion mode was "in table". */
|
||||
$this->inTable($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inCell($token) {
|
||||
/* An end tag whose tag name is one of: "td", "th" */
|
||||
if($token['type'] === HTML5::ENDTAG &&
|
||||
($token['name'] === 'td' || $token['name'] === 'th')) {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as that of the token, then this is a
|
||||
parse error and the token must be ignored. */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Generate implied end tags, except for elements with the same
|
||||
tag name as the token. */
|
||||
$this->generateImpliedEndTags(array($token['name']));
|
||||
|
||||
/* Now, if the current node is not an element with the same tag
|
||||
name as the token, then this is a parse error. */
|
||||
// k
|
||||
|
||||
/* Pop elements from this stack until an element with the same
|
||||
tag name as the token has been popped from the stack. */
|
||||
while(true) {
|
||||
$node = end($this->stack)->nodeName;
|
||||
array_pop($this->stack);
|
||||
|
||||
if($node === $token['name']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the list of active formatting elements up to the last
|
||||
marker. */
|
||||
$this->clearTheActiveFormattingElementsUpToTheLastMarker();
|
||||
|
||||
/* Switch the insertion mode to "in row". (The current node
|
||||
will be a tr element at this point.) */
|
||||
$this->mode = self::IN_ROW;
|
||||
}
|
||||
|
||||
/* A start tag whose tag name is one of: "caption", "col", "colgroup",
|
||||
"tbody", "td", "tfoot", "th", "thead", "tr" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
|
||||
'thead', 'tr'))) {
|
||||
/* If the stack of open elements does not have a td or th element
|
||||
in table scope, then this is a parse error; ignore the token.
|
||||
(innerHTML case) */
|
||||
if(!$this->elementInScope(array('td', 'th'), true)) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise, close the cell (see below) and reprocess the current
|
||||
token. */
|
||||
} else {
|
||||
$this->closeCell();
|
||||
return $this->inRow($token);
|
||||
}
|
||||
|
||||
/* A start tag whose tag name is one of: "caption", "col", "colgroup",
|
||||
"tbody", "td", "tfoot", "th", "thead", "tr" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
|
||||
array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
|
||||
'thead', 'tr'))) {
|
||||
/* If the stack of open elements does not have a td or th element
|
||||
in table scope, then this is a parse error; ignore the token.
|
||||
(innerHTML case) */
|
||||
if(!$this->elementInScope(array('td', 'th'), true)) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise, close the cell (see below) and reprocess the current
|
||||
token. */
|
||||
} else {
|
||||
$this->closeCell();
|
||||
return $this->inRow($token);
|
||||
}
|
||||
|
||||
/* An end tag whose tag name is one of: "body", "caption", "col",
|
||||
"colgroup", "html" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
|
||||
array('body', 'caption', 'col', 'colgroup', 'html'))) {
|
||||
/* Parse error. Ignore the token. */
|
||||
|
||||
/* An end tag whose tag name is one of: "table", "tbody", "tfoot",
|
||||
"thead", "tr" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
|
||||
array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as that of the token (which can only
|
||||
happen for "tbody", "tfoot" and "thead", or, in the innerHTML case),
|
||||
then this is a parse error and the token must be ignored. */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
// Ignore.
|
||||
|
||||
/* Otherwise, close the cell (see below) and reprocess the current
|
||||
token. */
|
||||
} else {
|
||||
$this->closeCell();
|
||||
return $this->inRow($token);
|
||||
}
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Process the token as if the insertion mode was "in body". */
|
||||
$this->inBody($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inSelect($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
/* A character token */
|
||||
if($token['type'] === HTML5::CHARACTR) {
|
||||
/* Append the token's character to the current node. */
|
||||
$this->insertText($token['data']);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$this->insertComment($token['data']);
|
||||
|
||||
/* A start tag token whose tag name is "option" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
$token['name'] === 'option') {
|
||||
/* If the current node is an option element, act as if an end tag
|
||||
with the tag name "option" had been seen. */
|
||||
if(end($this->stack)->nodeName === 'option') {
|
||||
$this->inSelect(array(
|
||||
'name' => 'option',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* A start tag token whose tag name is "optgroup" */
|
||||
} elseif($token['type'] === HTML5::STARTTAG &&
|
||||
$token['name'] === 'optgroup') {
|
||||
/* If the current node is an option element, act as if an end tag
|
||||
with the tag name "option" had been seen. */
|
||||
if(end($this->stack)->nodeName === 'option') {
|
||||
$this->inSelect(array(
|
||||
'name' => 'option',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* If the current node is an optgroup element, act as if an end tag
|
||||
with the tag name "optgroup" had been seen. */
|
||||
if(end($this->stack)->nodeName === 'optgroup') {
|
||||
$this->inSelect(array(
|
||||
'name' => 'optgroup',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* An end tag token whose tag name is "optgroup" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
$token['name'] === 'optgroup') {
|
||||
/* First, if the current node is an option element, and the node
|
||||
immediately before it in the stack of open elements is an optgroup
|
||||
element, then act as if an end tag with the tag name "option" had
|
||||
been seen. */
|
||||
$elements_in_stack = count($this->stack);
|
||||
|
||||
if($this->stack[$elements_in_stack - 1]->nodeName === 'option' &&
|
||||
$this->stack[$elements_in_stack - 2]->nodeName === 'optgroup') {
|
||||
$this->inSelect(array(
|
||||
'name' => 'option',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
}
|
||||
|
||||
/* If the current node is an optgroup element, then pop that node
|
||||
from the stack of open elements. Otherwise, this is a parse error,
|
||||
ignore the token. */
|
||||
if($this->stack[$elements_in_stack - 1] === 'optgroup') {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
/* An end tag token whose tag name is "option" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
$token['name'] === 'option') {
|
||||
/* If the current node is an option element, then pop that node
|
||||
from the stack of open elements. Otherwise, this is a parse error,
|
||||
ignore the token. */
|
||||
if(end($this->stack)->nodeName === 'option') {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
/* An end tag whose tag name is "select" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG &&
|
||||
$token['name'] === 'select') {
|
||||
/* If the stack of open elements does not have an element in table
|
||||
scope with the same tag name as the token, this is a parse error.
|
||||
Ignore the token. (innerHTML case) */
|
||||
if(!$this->elementInScope($token['name'], true)) {
|
||||
// w/e
|
||||
|
||||
/* Otherwise: */
|
||||
} else {
|
||||
/* Pop elements from the stack of open elements until a select
|
||||
element has been popped from the stack. */
|
||||
while(true) {
|
||||
$current = end($this->stack)->nodeName;
|
||||
array_pop($this->stack);
|
||||
|
||||
if($current === 'select') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset the insertion mode appropriately. */
|
||||
$this->resetInsertionMode();
|
||||
}
|
||||
|
||||
/* A start tag whose tag name is "select" */
|
||||
} elseif($token['name'] === 'select' &&
|
||||
$token['type'] === HTML5::STARTTAG) {
|
||||
/* Parse error. Act as if the token had been an end tag with the
|
||||
tag name "select" instead. */
|
||||
$this->inSelect(array(
|
||||
'name' => 'select',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
/* An end tag whose tag name is one of: "caption", "table", "tbody",
|
||||
"tfoot", "thead", "tr", "td", "th" */
|
||||
} elseif(in_array($token['name'], array('caption', 'table', 'tbody',
|
||||
'tfoot', 'thead', 'tr', 'td', 'th')) && $token['type'] === HTML5::ENDTAG) {
|
||||
/* Parse error. */
|
||||
// w/e
|
||||
|
||||
/* If the stack of open elements has an element in table scope with
|
||||
the same tag name as that of the token, then act as if an end tag
|
||||
with the tag name "select" had been seen, and reprocess the token.
|
||||
Otherwise, ignore the token. */
|
||||
if($this->elementInScope($token['name'], true)) {
|
||||
$this->inSelect(array(
|
||||
'name' => 'select',
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
$this->mainPhase($token);
|
||||
}
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Parse error. Ignore the token. */
|
||||
}
|
||||
}
|
||||
|
||||
private function afterBody($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
if($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Process the token as it would be processed if the insertion mode
|
||||
was "in body". */
|
||||
$this->inBody($token);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the first element in the stack of open
|
||||
elements (the html element), with the data attribute set to the
|
||||
data given in the comment token. */
|
||||
$comment = $this->dom->createComment($token['data']);
|
||||
$this->stack[0]->appendChild($comment);
|
||||
|
||||
/* An end tag with the tag name "html" */
|
||||
} elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') {
|
||||
/* If the parser was originally created in order to handle the
|
||||
setting of an element's innerHTML attribute, this is a parse error;
|
||||
ignore the token. (The element will be an html element in this
|
||||
case.) (innerHTML case) */
|
||||
|
||||
/* Otherwise, switch to the trailing end phase. */
|
||||
$this->phase = self::END_PHASE;
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Parse error. Set the insertion mode to "in body" and reprocess
|
||||
the token. */
|
||||
$this->mode = self::IN_BODY;
|
||||
return $this->inBody($token);
|
||||
}
|
||||
}
|
||||
|
||||
private function inFrameset($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
|
||||
if($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Append the character to the current node. */
|
||||
$this->insertText($token['data']);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$this->insertComment($token['data']);
|
||||
|
||||
/* A start tag with the tag name "frameset" */
|
||||
} elseif($token['name'] === 'frameset' &&
|
||||
$token['type'] === HTML5::STARTTAG) {
|
||||
$this->insertElement($token);
|
||||
|
||||
/* An end tag with the tag name "frameset" */
|
||||
} elseif($token['name'] === 'frameset' &&
|
||||
$token['type'] === HTML5::ENDTAG) {
|
||||
/* If the current node is the root html element, then this is a
|
||||
parse error; ignore the token. (innerHTML case) */
|
||||
if(end($this->stack)->nodeName === 'html') {
|
||||
// Ignore
|
||||
|
||||
} else {
|
||||
/* Otherwise, pop the current node from the stack of open
|
||||
elements. */
|
||||
array_pop($this->stack);
|
||||
|
||||
/* If the parser was not originally created in order to handle
|
||||
the setting of an element's innerHTML attribute (innerHTML case),
|
||||
and the current node is no longer a frameset element, then change
|
||||
the insertion mode to "after frameset". */
|
||||
$this->mode = self::AFTR_FRAME;
|
||||
}
|
||||
|
||||
/* A start tag with the tag name "frame" */
|
||||
} elseif($token['name'] === 'frame' &&
|
||||
$token['type'] === HTML5::STARTTAG) {
|
||||
/* Insert an HTML element for the token. */
|
||||
$this->insertElement($token);
|
||||
|
||||
/* Immediately pop the current node off the stack of open elements. */
|
||||
array_pop($this->stack);
|
||||
|
||||
/* A start tag with the tag name "noframes" */
|
||||
} elseif($token['name'] === 'noframes' &&
|
||||
$token['type'] === HTML5::STARTTAG) {
|
||||
/* Process the token as if the insertion mode had been "in body". */
|
||||
$this->inBody($token);
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Parse error. Ignore the token. */
|
||||
}
|
||||
}
|
||||
|
||||
private function afterFrameset($token) {
|
||||
/* Handle the token as follows: */
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
|
||||
if($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Append the character to the current node. */
|
||||
$this->insertText($token['data']);
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the current node with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$this->insertComment($token['data']);
|
||||
|
||||
/* An end tag with the tag name "html" */
|
||||
} elseif($token['name'] === 'html' &&
|
||||
$token['type'] === HTML5::ENDTAG) {
|
||||
/* Switch to the trailing end phase. */
|
||||
$this->phase = self::END_PHASE;
|
||||
|
||||
/* A start tag with the tag name "noframes" */
|
||||
} elseif($token['name'] === 'noframes' &&
|
||||
$token['type'] === HTML5::STARTTAG) {
|
||||
/* Process the token as if the insertion mode had been "in body". */
|
||||
$this->inBody($token);
|
||||
|
||||
/* Anything else */
|
||||
} else {
|
||||
/* Parse error. Ignore the token. */
|
||||
}
|
||||
}
|
||||
|
||||
private function trailingEndPhase($token) {
|
||||
/* After the main phase, as each token is emitted from the tokenisation
|
||||
stage, it must be processed as described in this section. */
|
||||
|
||||
/* A DOCTYPE token */
|
||||
if($token['type'] === HTML5::DOCTYPE) {
|
||||
// Parse error. Ignore the token.
|
||||
|
||||
/* A comment token */
|
||||
} elseif($token['type'] === HTML5::COMMENT) {
|
||||
/* Append a Comment node to the Document object with the data
|
||||
attribute set to the data given in the comment token. */
|
||||
$comment = $this->dom->createComment($token['data']);
|
||||
$this->dom->appendChild($comment);
|
||||
|
||||
/* A character token that is one of one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE */
|
||||
} elseif($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
|
||||
/* Process the token as it would be processed in the main phase. */
|
||||
$this->mainPhase($token);
|
||||
|
||||
/* A character token that is not one of U+0009 CHARACTER TABULATION,
|
||||
U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
|
||||
or U+0020 SPACE. Or a start tag token. Or an end tag token. */
|
||||
} elseif(($token['type'] === HTML5::CHARACTR &&
|
||||
preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
|
||||
$token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG) {
|
||||
/* Parse error. Switch back to the main phase and reprocess the
|
||||
token. */
|
||||
$this->phase = self::MAIN_PHASE;
|
||||
return $this->mainPhase($token);
|
||||
|
||||
/* An end-of-file token */
|
||||
} elseif($token['type'] === HTML5::EOF) {
|
||||
/* OMG DONE!! */
|
||||
}
|
||||
}
|
||||
|
||||
private function insertElement($token, $append = true, $check = false) {
|
||||
// Proprietary workaround for libxml2's limitations with tag names
|
||||
if ($check) {
|
||||
// Slightly modified HTML5 tag-name modification,
|
||||
// removing anything that's not an ASCII letter, digit, or hyphen
|
||||
$token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']);
|
||||
// Remove leading hyphens and numbers
|
||||
$token['name'] = ltrim($token['name'], '-0..9');
|
||||
// In theory, this should ever be needed, but just in case
|
||||
if ($token['name'] === '') $token['name'] = 'span'; // arbitrary generic choice
|
||||
}
|
||||
|
||||
$el = $this->dom->createElement($token['name']);
|
||||
|
||||
foreach($token['attr'] as $attr) {
|
||||
if(!$el->hasAttribute($attr['name'])) {
|
||||
$el->setAttribute($attr['name'], $attr['value']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->appendToRealParent($el);
|
||||
$this->stack[] = $el;
|
||||
|
||||
return $el;
|
||||
}
|
||||
|
||||
private function insertText($data) {
|
||||
$text = $this->dom->createTextNode($data);
|
||||
$this->appendToRealParent($text);
|
||||
}
|
||||
|
||||
private function insertComment($data) {
|
||||
$comment = $this->dom->createComment($data);
|
||||
$this->appendToRealParent($comment);
|
||||
}
|
||||
|
||||
private function appendToRealParent($node) {
|
||||
if($this->foster_parent === null) {
|
||||
end($this->stack)->appendChild($node);
|
||||
|
||||
} elseif($this->foster_parent !== null) {
|
||||
/* If the foster parent element is the parent element of the
|
||||
last table element in the stack of open elements, then the new
|
||||
node must be inserted immediately before the last table element
|
||||
in the stack of open elements in the foster parent element;
|
||||
otherwise, the new node must be appended to the foster parent
|
||||
element. */
|
||||
for($n = count($this->stack) - 1; $n >= 0; $n--) {
|
||||
if($this->stack[$n]->nodeName === 'table' &&
|
||||
$this->stack[$n]->parentNode !== null) {
|
||||
$table = $this->stack[$n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($table) && $this->foster_parent->isSameNode($table->parentNode))
|
||||
$this->foster_parent->insertBefore($node, $table);
|
||||
else
|
||||
$this->foster_parent->appendChild($node);
|
||||
|
||||
$this->foster_parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function elementInScope($el, $table = false) {
|
||||
if(is_array($el)) {
|
||||
foreach($el as $element) {
|
||||
if($this->elementInScope($element, $table)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$leng = count($this->stack);
|
||||
|
||||
for($n = 0; $n < $leng; $n++) {
|
||||
/* 1. Initialise node to be the current node (the bottommost node of
|
||||
the stack). */
|
||||
$node = $this->stack[$leng - 1 - $n];
|
||||
|
||||
if($node->tagName === $el) {
|
||||
/* 2. If node is the target node, terminate in a match state. */
|
||||
return true;
|
||||
|
||||
} elseif($node->tagName === 'table') {
|
||||
/* 3. Otherwise, if node is a table element, terminate in a failure
|
||||
state. */
|
||||
return false;
|
||||
|
||||
} elseif($table === true && in_array($node->tagName, array('caption', 'td',
|
||||
'th', 'button', 'marquee', 'object'))) {
|
||||
/* 4. Otherwise, if the algorithm is the "has an element in scope"
|
||||
variant (rather than the "has an element in table scope" variant),
|
||||
and node is one of the following, terminate in a failure state. */
|
||||
return false;
|
||||
|
||||
} elseif($node === $node->ownerDocument->documentElement) {
|
||||
/* 5. Otherwise, if node is an html element (root element), terminate
|
||||
in a failure state. (This can only happen if the node is the topmost
|
||||
node of the stack of open elements, and prevents the next step from
|
||||
being invoked if there are no more elements in the stack.) */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Otherwise, set node to the previous entry in the stack of open
|
||||
elements and return to step 2. (This will never fail, since the loop
|
||||
will always terminate in the previous step if the top of the stack
|
||||
is reached.) */
|
||||
}
|
||||
}
|
||||
|
||||
private function reconstructActiveFormattingElements() {
|
||||
/* 1. If there are no entries in the list of active formatting elements,
|
||||
then there is nothing to reconstruct; stop this algorithm. */
|
||||
$formatting_elements = count($this->a_formatting);
|
||||
|
||||
if($formatting_elements === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 3. Let entry be the last (most recently added) element in the list
|
||||
of active formatting elements. */
|
||||
$entry = end($this->a_formatting);
|
||||
|
||||
/* 2. If the last (most recently added) entry in the list of active
|
||||
formatting elements is a marker, or if it is an element that is in the
|
||||
stack of open elements, then there is nothing to reconstruct; stop this
|
||||
algorithm. */
|
||||
if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for($a = $formatting_elements - 1; $a >= 0; true) {
|
||||
/* 4. If there are no entries before entry in the list of active
|
||||
formatting elements, then jump to step 8. */
|
||||
if($a === 0) {
|
||||
$step_seven = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 5. Let entry be the entry one earlier than entry in the list of
|
||||
active formatting elements. */
|
||||
$a--;
|
||||
$entry = $this->a_formatting[$a];
|
||||
|
||||
/* 6. If entry is neither a marker nor an element that is also in
|
||||
thetack of open elements, go to step 4. */
|
||||
if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while(true) {
|
||||
/* 7. Let entry be the element one later than entry in the list of
|
||||
active formatting elements. */
|
||||
if(isset($step_seven) && $step_seven === true) {
|
||||
$a++;
|
||||
$entry = $this->a_formatting[$a];
|
||||
}
|
||||
|
||||
/* 8. Perform a shallow clone of the element entry to obtain clone. */
|
||||
$clone = $entry->cloneNode();
|
||||
|
||||
/* 9. Append clone to the current node and push it onto the stack
|
||||
of open elements so that it is the new current node. */
|
||||
end($this->stack)->appendChild($clone);
|
||||
$this->stack[] = $clone;
|
||||
|
||||
/* 10. Replace the entry for entry in the list with an entry for
|
||||
clone. */
|
||||
$this->a_formatting[$a] = $clone;
|
||||
|
||||
/* 11. If the entry for clone in the list of active formatting
|
||||
elements is not the last entry in the list, return to step 7. */
|
||||
if(end($this->a_formatting) !== $clone) {
|
||||
$step_seven = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function clearTheActiveFormattingElementsUpToTheLastMarker() {
|
||||
/* When the steps below require the UA to clear the list of active
|
||||
formatting elements up to the last marker, the UA must perform the
|
||||
following steps: */
|
||||
|
||||
while(true) {
|
||||
/* 1. Let entry be the last (most recently added) entry in the list
|
||||
of active formatting elements. */
|
||||
$entry = end($this->a_formatting);
|
||||
|
||||
/* 2. Remove entry from the list of active formatting elements. */
|
||||
array_pop($this->a_formatting);
|
||||
|
||||
/* 3. If entry was a marker, then stop the algorithm at this point.
|
||||
The list has been cleared up to the last marker. */
|
||||
if($entry === self::MARKER) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateImpliedEndTags($exclude = array()) {
|
||||
/* When the steps below require the UA to generate implied end tags,
|
||||
then, if the current node is a dd element, a dt element, an li element,
|
||||
a p element, a td element, a th element, or a tr element, the UA must
|
||||
act as if an end tag with the respective tag name had been seen and
|
||||
then generate implied end tags again. */
|
||||
$node = end($this->stack);
|
||||
$elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude);
|
||||
|
||||
while(in_array(end($this->stack)->nodeName, $elements)) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
|
||||
private function getElementCategory($node) {
|
||||
$name = $node->tagName;
|
||||
if(in_array($name, $this->special))
|
||||
return self::SPECIAL;
|
||||
|
||||
elseif(in_array($name, $this->scoping))
|
||||
return self::SCOPING;
|
||||
|
||||
elseif(in_array($name, $this->formatting))
|
||||
return self::FORMATTING;
|
||||
|
||||
else
|
||||
return self::PHRASING;
|
||||
}
|
||||
|
||||
private function clearStackToTableContext($elements) {
|
||||
/* When the steps above require the UA to clear the stack back to a
|
||||
table context, it means that the UA must, while the current node is not
|
||||
a table element or an html element, pop elements from the stack of open
|
||||
elements. If this causes any elements to be popped from the stack, then
|
||||
this is a parse error. */
|
||||
while(true) {
|
||||
$node = end($this->stack)->nodeName;
|
||||
|
||||
if(in_array($node, $elements)) {
|
||||
break;
|
||||
} else {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function resetInsertionMode() {
|
||||
/* 1. Let last be false. */
|
||||
$last = false;
|
||||
$leng = count($this->stack);
|
||||
|
||||
for($n = $leng - 1; $n >= 0; $n--) {
|
||||
/* 2. Let node be the last node in the stack of open elements. */
|
||||
$node = $this->stack[$n];
|
||||
|
||||
/* 3. If node is the first node in the stack of open elements, then
|
||||
set last to true. If the element whose innerHTML attribute is being
|
||||
set is neither a td element nor a th element, then set node to the
|
||||
element whose innerHTML attribute is being set. (innerHTML case) */
|
||||
if($this->stack[0]->isSameNode($node)) {
|
||||
$last = true;
|
||||
}
|
||||
|
||||
/* 4. If node is a select element, then switch the insertion mode to
|
||||
"in select" and abort these steps. (innerHTML case) */
|
||||
if($node->nodeName === 'select') {
|
||||
$this->mode = self::IN_SELECT;
|
||||
break;
|
||||
|
||||
/* 5. If node is a td or th element, then switch the insertion mode
|
||||
to "in cell" and abort these steps. */
|
||||
} elseif($node->nodeName === 'td' || $node->nodeName === 'th') {
|
||||
$this->mode = self::IN_CELL;
|
||||
break;
|
||||
|
||||
/* 6. If node is a tr element, then switch the insertion mode to
|
||||
"in row" and abort these steps. */
|
||||
} elseif($node->nodeName === 'tr') {
|
||||
$this->mode = self::IN_ROW;
|
||||
break;
|
||||
|
||||
/* 7. If node is a tbody, thead, or tfoot element, then switch the
|
||||
insertion mode to "in table body" and abort these steps. */
|
||||
} elseif(in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) {
|
||||
$this->mode = self::IN_TBODY;
|
||||
break;
|
||||
|
||||
/* 8. If node is a caption element, then switch the insertion mode
|
||||
to "in caption" and abort these steps. */
|
||||
} elseif($node->nodeName === 'caption') {
|
||||
$this->mode = self::IN_CAPTION;
|
||||
break;
|
||||
|
||||
/* 9. If node is a colgroup element, then switch the insertion mode
|
||||
to "in column group" and abort these steps. (innerHTML case) */
|
||||
} elseif($node->nodeName === 'colgroup') {
|
||||
$this->mode = self::IN_CGROUP;
|
||||
break;
|
||||
|
||||
/* 10. If node is a table element, then switch the insertion mode
|
||||
to "in table" and abort these steps. */
|
||||
} elseif($node->nodeName === 'table') {
|
||||
$this->mode = self::IN_TABLE;
|
||||
break;
|
||||
|
||||
/* 11. If node is a head element, then switch the insertion mode
|
||||
to "in body" ("in body"! not "in head"!) and abort these steps.
|
||||
(innerHTML case) */
|
||||
} elseif($node->nodeName === 'head') {
|
||||
$this->mode = self::IN_BODY;
|
||||
break;
|
||||
|
||||
/* 12. If node is a body element, then switch the insertion mode to
|
||||
"in body" and abort these steps. */
|
||||
} elseif($node->nodeName === 'body') {
|
||||
$this->mode = self::IN_BODY;
|
||||
break;
|
||||
|
||||
/* 13. If node is a frameset element, then switch the insertion
|
||||
mode to "in frameset" and abort these steps. (innerHTML case) */
|
||||
} elseif($node->nodeName === 'frameset') {
|
||||
$this->mode = self::IN_FRAME;
|
||||
break;
|
||||
|
||||
/* 14. If node is an html element, then: if the head element
|
||||
pointer is null, switch the insertion mode to "before head",
|
||||
otherwise, switch the insertion mode to "after head". In either
|
||||
case, abort these steps. (innerHTML case) */
|
||||
} elseif($node->nodeName === 'html') {
|
||||
$this->mode = ($this->head_pointer === null)
|
||||
? self::BEFOR_HEAD
|
||||
: self::AFTER_HEAD;
|
||||
|
||||
break;
|
||||
|
||||
/* 15. If last is true, then set the insertion mode to "in body"
|
||||
and abort these steps. (innerHTML case) */
|
||||
} elseif($last) {
|
||||
$this->mode = self::IN_BODY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function closeCell() {
|
||||
/* If the stack of open elements has a td or th element in table scope,
|
||||
then act as if an end tag token with that tag name had been seen. */
|
||||
foreach(array('td', 'th') as $cell) {
|
||||
if($this->elementInScope($cell, true)) {
|
||||
$this->inCell(array(
|
||||
'name' => $cell,
|
||||
'type' => HTML5::ENDTAG
|
||||
));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function save() {
|
||||
return $this->dom;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Takes a well formed list of tokens and fixes their nesting.
|
||||
*
|
||||
* HTML elements dictate which elements are allowed to be their children,
|
||||
* for example, you can't have a p tag in a span tag. Other elements have
|
||||
* much more rigorous definitions: tables, for instance, require a specific
|
||||
* order for their elements. There are also constraints not expressible by
|
||||
* document type definitions, such as the chameleon nature of ins/del
|
||||
* tags and global child exclusions.
|
||||
*
|
||||
* The first major objective of this strategy is to iterate through all the
|
||||
* nodes (not tokens) of the list of tokens and determine whether or not
|
||||
* their children conform to the element's definition. If they do not, the
|
||||
* child definition may optionally supply an amended list of elements that
|
||||
* is valid or require that the entire node be deleted (and the previous
|
||||
* node rescanned).
|
||||
*
|
||||
* The second objective is to ensure that explicitly excluded elements of
|
||||
* an element do not appear in its children. Code that accomplishes this
|
||||
* task is pervasive through the strategy, though the two are distinct tasks
|
||||
* and could, theoretically, be seperated (although it's not recommended).
|
||||
*
|
||||
* @note Whether or not unrecognized children are silently dropped or
|
||||
* translated into text depends on the child definitions.
|
||||
*
|
||||
* @todo Enable nodes to be bubbled out of the structure.
|
||||
*/
|
||||
|
||||
class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
|
||||
{
|
||||
|
||||
public function execute($tokens, $config, $context) {
|
||||
//####################################################################//
|
||||
// Pre-processing
|
||||
|
||||
// get a copy of the HTML definition
|
||||
$definition = $config->getHTMLDefinition();
|
||||
|
||||
// insert implicit "parent" node, will be removed at end.
|
||||
// DEFINITION CALL
|
||||
$parent_name = $definition->info_parent;
|
||||
array_unshift($tokens, new HTMLPurifier_Token_Start($parent_name));
|
||||
$tokens[] = new HTMLPurifier_Token_End($parent_name);
|
||||
|
||||
// setup the context variable 'IsInline', for chameleon processing
|
||||
// is 'false' when we are not inline, 'true' when it must always
|
||||
// be inline, and an integer when it is inline for a certain
|
||||
// branch of the document tree
|
||||
$is_inline = $definition->info_parent_def->descendants_are_inline;
|
||||
$context->register('IsInline', $is_inline);
|
||||
|
||||
// setup error collector
|
||||
$e =& $context->get('ErrorCollector', true);
|
||||
|
||||
//####################################################################//
|
||||
// Loop initialization
|
||||
|
||||
// stack that contains the indexes of all parents,
|
||||
// $stack[count($stack)-1] being the current parent
|
||||
$stack = array();
|
||||
|
||||
// stack that contains all elements that are excluded
|
||||
// it is organized by parent elements, similar to $stack,
|
||||
// but it is only populated when an element with exclusions is
|
||||
// processed, i.e. there won't be empty exclusions.
|
||||
$exclude_stack = array();
|
||||
|
||||
// variable that contains the start token while we are processing
|
||||
// nodes. This enables error reporting to do its job
|
||||
$start_token = false;
|
||||
$context->register('CurrentToken', $start_token);
|
||||
|
||||
//####################################################################//
|
||||
// Loop
|
||||
|
||||
// iterate through all start nodes. Determining the start node
|
||||
// is complicated so it has been omitted from the loop construct
|
||||
for ($i = 0, $size = count($tokens) ; $i < $size; ) {
|
||||
|
||||
//################################################################//
|
||||
// Gather information on children
|
||||
|
||||
// child token accumulator
|
||||
$child_tokens = array();
|
||||
|
||||
// scroll to the end of this node, report number, and collect
|
||||
// all children
|
||||
for ($j = $i, $depth = 0; ; $j++) {
|
||||
if ($tokens[$j] instanceof HTMLPurifier_Token_Start) {
|
||||
$depth++;
|
||||
// skip token assignment on first iteration, this is the
|
||||
// token we currently are on
|
||||
if ($depth == 1) continue;
|
||||
} elseif ($tokens[$j] instanceof HTMLPurifier_Token_End) {
|
||||
$depth--;
|
||||
// skip token assignment on last iteration, this is the
|
||||
// end token of the token we're currently on
|
||||
if ($depth == 0) break;
|
||||
}
|
||||
$child_tokens[] = $tokens[$j];
|
||||
}
|
||||
|
||||
// $i is index of start token
|
||||
// $j is index of end token
|
||||
|
||||
$start_token = $tokens[$i]; // to make token available via CurrentToken
|
||||
|
||||
//################################################################//
|
||||
// Gather information on parent
|
||||
|
||||
// calculate parent information
|
||||
if ($count = count($stack)) {
|
||||
$parent_index = $stack[$count-1];
|
||||
$parent_name = $tokens[$parent_index]->name;
|
||||
if ($parent_index == 0) {
|
||||
$parent_def = $definition->info_parent_def;
|
||||
} else {
|
||||
$parent_def = $definition->info[$parent_name];
|
||||
}
|
||||
} else {
|
||||
// processing as if the parent were the "root" node
|
||||
// unknown info, it won't be used anyway, in the future,
|
||||
// we may want to enforce one element only (this is
|
||||
// necessary for HTML Purifier to clean entire documents
|
||||
$parent_index = $parent_name = $parent_def = null;
|
||||
}
|
||||
|
||||
// calculate context
|
||||
if ($is_inline === false) {
|
||||
// check if conditions make it inline
|
||||
if (!empty($parent_def) && $parent_def->descendants_are_inline) {
|
||||
$is_inline = $count - 1;
|
||||
}
|
||||
} else {
|
||||
// check if we're out of inline
|
||||
if ($count === $is_inline) {
|
||||
$is_inline = false;
|
||||
}
|
||||
}
|
||||
|
||||
//################################################################//
|
||||
// Determine whether element is explicitly excluded SGML-style
|
||||
|
||||
// determine whether or not element is excluded by checking all
|
||||
// parent exclusions. The array should not be very large, two
|
||||
// elements at most.
|
||||
$excluded = false;
|
||||
if (!empty($exclude_stack)) {
|
||||
foreach ($exclude_stack as $lookup) {
|
||||
if (isset($lookup[$tokens[$i]->name])) {
|
||||
$excluded = true;
|
||||
// no need to continue processing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//################################################################//
|
||||
// Perform child validation
|
||||
|
||||
if ($excluded) {
|
||||
// there is an exclusion, remove the entire node
|
||||
$result = false;
|
||||
$excludes = array(); // not used, but good to initialize anyway
|
||||
} else {
|
||||
// DEFINITION CALL
|
||||
if ($i === 0) {
|
||||
// special processing for the first node
|
||||
$def = $definition->info_parent_def;
|
||||
} else {
|
||||
$def = $definition->info[$tokens[$i]->name];
|
||||
|
||||
}
|
||||
|
||||
if (!empty($def->child)) {
|
||||
// have DTD child def validate children
|
||||
$result = $def->child->validateChildren(
|
||||
$child_tokens, $config, $context);
|
||||
} else {
|
||||
// weird, no child definition, get rid of everything
|
||||
$result = false;
|
||||
}
|
||||
|
||||
// determine whether or not this element has any exclusions
|
||||
$excludes = $def->excludes;
|
||||
}
|
||||
|
||||
// $result is now a bool or array
|
||||
|
||||
//################################################################//
|
||||
// Process result by interpreting $result
|
||||
|
||||
if ($result === true || $child_tokens === $result) {
|
||||
// leave the node as is
|
||||
|
||||
// register start token as a parental node start
|
||||
$stack[] = $i;
|
||||
|
||||
// register exclusions if there are any
|
||||
if (!empty($excludes)) $exclude_stack[] = $excludes;
|
||||
|
||||
// move cursor to next possible start node
|
||||
$i++;
|
||||
|
||||
} elseif($result === false) {
|
||||
// remove entire node
|
||||
|
||||
if ($e) {
|
||||
if ($excluded) {
|
||||
$e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
|
||||
} else {
|
||||
$e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
|
||||
}
|
||||
}
|
||||
|
||||
// calculate length of inner tokens and current tokens
|
||||
$length = $j - $i + 1;
|
||||
|
||||
// perform removal
|
||||
array_splice($tokens, $i, $length);
|
||||
|
||||
// update size
|
||||
$size -= $length;
|
||||
|
||||
// there is no start token to register,
|
||||
// current node is now the next possible start node
|
||||
// unless it turns out that we need to do a double-check
|
||||
|
||||
// this is a rought heuristic that covers 100% of HTML's
|
||||
// cases and 99% of all other cases. A child definition
|
||||
// that would be tricked by this would be something like:
|
||||
// ( | a b c) where it's all or nothing. Fortunately,
|
||||
// our current implementation claims that that case would
|
||||
// not allow empty, even if it did
|
||||
if (!$parent_def->child->allow_empty) {
|
||||
// we need to do a double-check
|
||||
$i = $parent_index;
|
||||
array_pop($stack);
|
||||
}
|
||||
|
||||
// PROJECTED OPTIMIZATION: Process all children elements before
|
||||
// reprocessing parent node.
|
||||
|
||||
} else {
|
||||
// replace node with $result
|
||||
|
||||
// calculate length of inner tokens
|
||||
$length = $j - $i - 1;
|
||||
|
||||
if ($e) {
|
||||
if (empty($result) && $length) {
|
||||
$e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
|
||||
} else {
|
||||
$e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
|
||||
}
|
||||
}
|
||||
|
||||
// perform replacement
|
||||
array_splice($tokens, $i + 1, $length, $result);
|
||||
|
||||
// update size
|
||||
$size -= $length;
|
||||
$size += count($result);
|
||||
|
||||
// register start token as a parental node start
|
||||
$stack[] = $i;
|
||||
|
||||
// register exclusions if there are any
|
||||
if (!empty($excludes)) $exclude_stack[] = $excludes;
|
||||
|
||||
// move cursor to next possible start node
|
||||
$i++;
|
||||
|
||||
}
|
||||
|
||||
//################################################################//
|
||||
// Scroll to next start node
|
||||
|
||||
// We assume, at this point, that $i is the index of the token
|
||||
// that is the first possible new start point for a node.
|
||||
|
||||
// Test if the token indeed is a start tag, if not, move forward
|
||||
// and test again.
|
||||
$size = count($tokens);
|
||||
while ($i < $size and !$tokens[$i] instanceof HTMLPurifier_Token_Start) {
|
||||
if ($tokens[$i] instanceof HTMLPurifier_Token_End) {
|
||||
// pop a token index off the stack if we ended a node
|
||||
array_pop($stack);
|
||||
// pop an exclusion lookup off exclusion stack if
|
||||
// we ended node and that node had exclusions
|
||||
if ($i == 0 || $i == $size - 1) {
|
||||
// use specialized var if it's the super-parent
|
||||
$s_excludes = $definition->info_parent_def->excludes;
|
||||
} else {
|
||||
$s_excludes = $definition->info[$tokens[$i]->name]->excludes;
|
||||
}
|
||||
if ($s_excludes) {
|
||||
array_pop($exclude_stack);
|
||||
}
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//####################################################################//
|
||||
// Post-processing
|
||||
|
||||
// remove implicit parent tokens at the beginning and end
|
||||
array_shift($tokens);
|
||||
array_pop($tokens);
|
||||
|
||||
// remove context variables
|
||||
$context->destroy('IsInline');
|
||||
$context->destroy('CurrentToken');
|
||||
|
||||
//####################################################################//
|
||||
// Return
|
||||
|
||||
return $tokens;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Abstract base token class that all others inherit from.
|
||||
*/
|
||||
class HTMLPurifier_Token {
|
||||
public $line; /**< Line number node was on in source document. Null if unknown. */
|
||||
public $col; /**< Column of line node was on in source document. Null if unknown. */
|
||||
|
||||
/**
|
||||
* Lookup array of processing that this token is exempt from.
|
||||
* Currently, valid values are "ValidateAttributes" and
|
||||
* "MakeWellFormed_TagClosedError"
|
||||
*/
|
||||
public $armor = array();
|
||||
|
||||
/**
|
||||
* Used during MakeWellFormed.
|
||||
*/
|
||||
public $skip;
|
||||
public $rewind;
|
||||
public $carryover;
|
||||
|
||||
public function __get($n) {
|
||||
if ($n === 'type') {
|
||||
trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
|
||||
switch (get_class($this)) {
|
||||
case 'HTMLPurifier_Token_Start': return 'start';
|
||||
case 'HTMLPurifier_Token_Empty': return 'empty';
|
||||
case 'HTMLPurifier_Token_End': return 'end';
|
||||
case 'HTMLPurifier_Token_Text': return 'text';
|
||||
case 'HTMLPurifier_Token_Comment': return 'comment';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the token in the source document.
|
||||
*/
|
||||
public function position($l = null, $c = null) {
|
||||
$this->line = $l;
|
||||
$this->col = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for DirectLex settings line/col position.
|
||||
*/
|
||||
public function rawPosition($l, $c) {
|
||||
if ($c === -1) $l++;
|
||||
$this->line = $l;
|
||||
$this->col = $c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Concrete comment token class. Generally will be ignored.
|
||||
*/
|
||||
class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
|
||||
{
|
||||
public $data; /**< Character data within comment. */
|
||||
public $is_whitespace = true;
|
||||
/**
|
||||
* Transparent constructor.
|
||||
*
|
||||
* @param $data String comment data.
|
||||
*/
|
||||
public function __construct($data, $line = null, $col = null) {
|
||||
$this->data = $data;
|
||||
$this->line = $line;
|
||||
$this->col = $col;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Factory for token generation.
|
||||
*
|
||||
* @note Doing some benchmarking indicates that the new operator is much
|
||||
* slower than the clone operator (even discounting the cost of the
|
||||
* constructor). This class is for that optimization.
|
||||
* Other then that, there's not much point as we don't
|
||||
* maintain parallel HTMLPurifier_Token hierarchies (the main reason why
|
||||
* you'd want to use an abstract factory).
|
||||
* @todo Port DirectLex to use this
|
||||
*/
|
||||
class HTMLPurifier_TokenFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* Prototypes that will be cloned.
|
||||
* @private
|
||||
*/
|
||||
// p stands for prototype
|
||||
private $p_start, $p_end, $p_empty, $p_text, $p_comment;
|
||||
|
||||
/**
|
||||
* Generates blank prototypes for cloning.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->p_start = new HTMLPurifier_Token_Start('', array());
|
||||
$this->p_end = new HTMLPurifier_Token_End('');
|
||||
$this->p_empty = new HTMLPurifier_Token_Empty('', array());
|
||||
$this->p_text = new HTMLPurifier_Token_Text('');
|
||||
$this->p_comment= new HTMLPurifier_Token_Comment('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTMLPurifier_Token_Start.
|
||||
* @param $name Tag name
|
||||
* @param $attr Associative array of attributes
|
||||
* @return Generated HTMLPurifier_Token_Start
|
||||
*/
|
||||
public function createStart($name, $attr = array()) {
|
||||
$p = clone $this->p_start;
|
||||
$p->__construct($name, $attr);
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTMLPurifier_Token_End.
|
||||
* @param $name Tag name
|
||||
* @return Generated HTMLPurifier_Token_End
|
||||
*/
|
||||
public function createEnd($name) {
|
||||
$p = clone $this->p_end;
|
||||
$p->__construct($name);
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTMLPurifier_Token_Empty.
|
||||
* @param $name Tag name
|
||||
* @param $attr Associative array of attributes
|
||||
* @return Generated HTMLPurifier_Token_Empty
|
||||
*/
|
||||
public function createEmpty($name, $attr = array()) {
|
||||
$p = clone $this->p_empty;
|
||||
$p->__construct($name, $attr);
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTMLPurifier_Token_Text.
|
||||
* @param $data Data of text token
|
||||
* @return Generated HTMLPurifier_Token_Text
|
||||
*/
|
||||
public function createText($data) {
|
||||
$p = clone $this->p_text;
|
||||
$p->__construct($data);
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTMLPurifier_Token_Comment.
|
||||
* @param $data Data of comment token
|
||||
* @return Generated HTMLPurifier_Token_Comment
|
||||
*/
|
||||
public function createComment($data) {
|
||||
$p = clone $this->p_comment;
|
||||
$p->__construct($data);
|
||||
return $p;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* HTML Purifier's internal representation of a URI.
|
||||
* @note
|
||||
* Internal data-structures are completely escaped. If the data needs
|
||||
* to be used in a non-URI context (which is very unlikely), be sure
|
||||
* to decode it first. The URI may not necessarily be well-formed until
|
||||
* validate() is called.
|
||||
*/
|
||||
class HTMLPurifier_URI
|
||||
{
|
||||
|
||||
public $scheme, $userinfo, $host, $port, $path, $query, $fragment;
|
||||
|
||||
/**
|
||||
* @note Automatically normalizes scheme and port
|
||||
*/
|
||||
public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment) {
|
||||
$this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
|
||||
$this->userinfo = $userinfo;
|
||||
$this->host = $host;
|
||||
$this->port = is_null($port) ? $port : (int) $port;
|
||||
$this->path = $path;
|
||||
$this->query = $query;
|
||||
$this->fragment = $fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a scheme object corresponding to the URI's scheme/default
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return Scheme object appropriate for validating this URI
|
||||
*/
|
||||
public function getSchemeObj($config, $context) {
|
||||
$registry = HTMLPurifier_URISchemeRegistry::instance();
|
||||
if ($this->scheme !== null) {
|
||||
$scheme_obj = $registry->getScheme($this->scheme, $config, $context);
|
||||
if (!$scheme_obj) return false; // invalid scheme, clean it out
|
||||
} else {
|
||||
// no scheme: retrieve the default one
|
||||
$def = $config->getDefinition('URI');
|
||||
$scheme_obj = $registry->getScheme($def->defaultScheme, $config, $context);
|
||||
if (!$scheme_obj) {
|
||||
// something funky happened to the default scheme object
|
||||
trigger_error(
|
||||
'Default scheme object "' . $def->defaultScheme . '" was not readable',
|
||||
E_USER_WARNING
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $scheme_obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic validation method applicable for all schemes. May modify
|
||||
* this URI in order to get it into a compliant form.
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return True if validation/filtering succeeds, false if failure
|
||||
*/
|
||||
public function validate($config, $context) {
|
||||
|
||||
// ABNF definitions from RFC 3986
|
||||
$chars_sub_delims = '!$&\'()*+,;=';
|
||||
$chars_gen_delims = ':/?#[]@';
|
||||
$chars_pchar = $chars_sub_delims . ':@';
|
||||
|
||||
// validate scheme (MUST BE FIRST!)
|
||||
if (!is_null($this->scheme) && is_null($this->host)) {
|
||||
$def = $config->getDefinition('URI');
|
||||
if ($def->defaultScheme === $this->scheme) {
|
||||
$this->scheme = null;
|
||||
}
|
||||
}
|
||||
|
||||
// validate host
|
||||
if (!is_null($this->host)) {
|
||||
$host_def = new HTMLPurifier_AttrDef_URI_Host();
|
||||
$this->host = $host_def->validate($this->host, $config, $context);
|
||||
if ($this->host === false) $this->host = null;
|
||||
}
|
||||
|
||||
// validate username
|
||||
if (!is_null($this->userinfo)) {
|
||||
$encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
|
||||
$this->userinfo = $encoder->encode($this->userinfo);
|
||||
}
|
||||
|
||||
// validate port
|
||||
if (!is_null($this->port)) {
|
||||
if ($this->port < 1 || $this->port > 65535) $this->port = null;
|
||||
}
|
||||
|
||||
// validate path
|
||||
$path_parts = array();
|
||||
$segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
|
||||
if (!is_null($this->host)) {
|
||||
// path-abempty (hier and relative)
|
||||
$this->path = $segments_encoder->encode($this->path);
|
||||
} elseif ($this->path !== '' && $this->path[0] === '/') {
|
||||
// path-absolute (hier and relative)
|
||||
if (strlen($this->path) >= 2 && $this->path[1] === '/') {
|
||||
// This shouldn't ever happen!
|
||||
$this->path = '';
|
||||
} else {
|
||||
$this->path = $segments_encoder->encode($this->path);
|
||||
}
|
||||
} elseif (!is_null($this->scheme) && $this->path !== '') {
|
||||
// path-rootless (hier)
|
||||
// Short circuit evaluation means we don't need to check nz
|
||||
$this->path = $segments_encoder->encode($this->path);
|
||||
} elseif (is_null($this->scheme) && $this->path !== '') {
|
||||
// path-noscheme (relative)
|
||||
// (once again, not checking nz)
|
||||
$segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
|
||||
$c = strpos($this->path, '/');
|
||||
if ($c !== false) {
|
||||
$this->path =
|
||||
$segment_nc_encoder->encode(substr($this->path, 0, $c)) .
|
||||
$segments_encoder->encode(substr($this->path, $c));
|
||||
} else {
|
||||
$this->path = $segment_nc_encoder->encode($this->path);
|
||||
}
|
||||
} else {
|
||||
// path-empty (hier and relative)
|
||||
$this->path = ''; // just to be safe
|
||||
}
|
||||
|
||||
// qf = query and fragment
|
||||
$qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
|
||||
|
||||
if (!is_null($this->query)) {
|
||||
$this->query = $qf_encoder->encode($this->query);
|
||||
}
|
||||
|
||||
if (!is_null($this->fragment)) {
|
||||
$this->fragment = $qf_encoder->encode($this->fragment);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert URI back to string
|
||||
* @return String URI appropriate for output
|
||||
*/
|
||||
public function toString() {
|
||||
// reconstruct authority
|
||||
$authority = null;
|
||||
if (!is_null($this->host)) {
|
||||
$authority = '';
|
||||
if(!is_null($this->userinfo)) $authority .= $this->userinfo . '@';
|
||||
$authority .= $this->host;
|
||||
if(!is_null($this->port)) $authority .= ':' . $this->port;
|
||||
}
|
||||
|
||||
// reconstruct the result
|
||||
$result = '';
|
||||
if (!is_null($this->scheme)) $result .= $this->scheme . ':';
|
||||
if (!is_null($authority)) $result .= '//' . $authority;
|
||||
$result .= $this->path;
|
||||
if (!is_null($this->query)) $result .= '?' . $this->query;
|
||||
if (!is_null($this->fragment)) $result .= '#' . $this->fragment;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Chainable filters for custom URI processing.
|
||||
*
|
||||
* These filters can perform custom actions on a URI filter object,
|
||||
* including transformation or blacklisting.
|
||||
*
|
||||
* @warning This filter is called before scheme object validation occurs.
|
||||
* Make sure, if you require a specific scheme object, you
|
||||
* you check that it exists. This allows filters to convert
|
||||
* proprietary URI schemes into regular ones.
|
||||
*/
|
||||
abstract class HTMLPurifier_URIFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Unique identifier of filter
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* True if this filter should be run after scheme validation.
|
||||
*/
|
||||
public $post = false;
|
||||
|
||||
/**
|
||||
* Performs initialization for the filter
|
||||
*/
|
||||
public function prepare($config) {return true;}
|
||||
|
||||
/**
|
||||
* Filter a URI object
|
||||
* @param $uri Reference to URI object variable
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return bool Whether or not to continue processing: false indicates
|
||||
* URL is no good, true indicates continue processing. Note that
|
||||
* all changes are committed directly on the URI object
|
||||
*/
|
||||
abstract public function filter(&$uri, $config, $context);
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
|
||||
{
|
||||
public $name = 'DisableExternal';
|
||||
protected $ourHostParts = false;
|
||||
public function prepare($config) {
|
||||
$our_host = $config->getDefinition('URI')->host;
|
||||
if ($our_host !== null) $this->ourHostParts = array_reverse(explode('.', $our_host));
|
||||
}
|
||||
public function filter(&$uri, $config, $context) {
|
||||
if (is_null($uri->host)) return true;
|
||||
if ($this->ourHostParts === false) return false;
|
||||
$host_parts = array_reverse(explode('.', $uri->host));
|
||||
foreach ($this->ourHostParts as $i => $x) {
|
||||
if (!isset($host_parts[$i])) return false;
|
||||
if ($host_parts[$i] != $this->ourHostParts[$i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
|
||||
{
|
||||
public $name = 'DisableExternalResources';
|
||||
public function filter(&$uri, $config, $context) {
|
||||
if (!$context->get('EmbeddedURI', true)) return true;
|
||||
return parent::filter($uri, $config, $context);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
|
||||
{
|
||||
public $name = 'HostBlacklist';
|
||||
protected $blacklist = array();
|
||||
public function prepare($config) {
|
||||
$this->blacklist = $config->get('URI.HostBlacklist');
|
||||
return true;
|
||||
}
|
||||
public function filter(&$uri, $config, $context) {
|
||||
foreach($this->blacklist as $blacklisted_host_fragment) {
|
||||
if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
|
||||
{
|
||||
public $name = 'Munge';
|
||||
public $post = true;
|
||||
private $target, $parser, $doEmbed, $secretKey;
|
||||
|
||||
protected $replace = array();
|
||||
|
||||
public function prepare($config) {
|
||||
$this->target = $config->get('URI.' . $this->name);
|
||||
$this->parser = new HTMLPurifier_URIParser();
|
||||
$this->doEmbed = $config->get('URI.MungeResources');
|
||||
$this->secretKey = $config->get('URI.MungeSecretKey');
|
||||
return true;
|
||||
}
|
||||
public function filter(&$uri, $config, $context) {
|
||||
if ($context->get('EmbeddedURI', true) && !$this->doEmbed) return true;
|
||||
|
||||
$scheme_obj = $uri->getSchemeObj($config, $context);
|
||||
if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it
|
||||
if (is_null($uri->host) || empty($scheme_obj->browsable)) {
|
||||
return true;
|
||||
}
|
||||
// don't redirect if target host is our host
|
||||
if ($uri->host === $config->getDefinition('URI')->host) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->makeReplace($uri, $config, $context);
|
||||
$this->replace = array_map('rawurlencode', $this->replace);
|
||||
|
||||
$new_uri = strtr($this->target, $this->replace);
|
||||
$new_uri = $this->parser->parse($new_uri);
|
||||
// don't redirect if the target host is the same as the
|
||||
// starting host
|
||||
if ($uri->host === $new_uri->host) return true;
|
||||
$uri = $new_uri; // overwrite
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function makeReplace($uri, $config, $context) {
|
||||
$string = $uri->toString();
|
||||
// always available
|
||||
$this->replace['%s'] = $string;
|
||||
$this->replace['%r'] = $context->get('EmbeddedURI', true);
|
||||
$token = $context->get('CurrentToken', true);
|
||||
$this->replace['%n'] = $token ? $token->name : null;
|
||||
$this->replace['%m'] = $context->get('CurrentAttr', true);
|
||||
$this->replace['%p'] = $context->get('CurrentCSSProperty', true);
|
||||
// not always available
|
||||
if ($this->secretKey) $this->replace['%t'] = sha1($this->secretKey . ':' . $string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validator for the components of a URI for a specific scheme
|
||||
*/
|
||||
class HTMLPurifier_URIScheme
|
||||
{
|
||||
|
||||
/**
|
||||
* Scheme's default port (integer)
|
||||
*/
|
||||
public $default_port = null;
|
||||
|
||||
/**
|
||||
* Whether or not URIs of this schem are locatable by a browser
|
||||
* http and ftp are accessible, while mailto and news are not.
|
||||
*/
|
||||
public $browsable = false;
|
||||
|
||||
/**
|
||||
* Whether or not the URI always uses <hier_part>, resolves edge cases
|
||||
* with making relative URIs absolute
|
||||
*/
|
||||
public $hierarchical = false;
|
||||
|
||||
/**
|
||||
* Validates the components of a URI
|
||||
* @note This implementation should be called by children if they define
|
||||
* a default port, as it does port processing.
|
||||
* @param $uri Instance of HTMLPurifier_URI
|
||||
* @param $config HTMLPurifier_Config object
|
||||
* @param $context HTMLPurifier_Context object
|
||||
* @return Bool success or failure
|
||||
*/
|
||||
public function validate(&$uri, $config, $context) {
|
||||
if ($this->default_port == $uri->port) $uri->port = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates news (Usenet) as defined by generic RFC 1738
|
||||
*/
|
||||
class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme {
|
||||
|
||||
public $browsable = false;
|
||||
|
||||
public function validate(&$uri, $config, $context) {
|
||||
parent::validate($uri, $config, $context);
|
||||
$uri->userinfo = null;
|
||||
$uri->host = null;
|
||||
$uri->port = null;
|
||||
$uri->query = null;
|
||||
// typecode check needed on path
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
|
||||
*/
|
||||
class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme {
|
||||
|
||||
public $default_port = 119;
|
||||
public $browsable = false;
|
||||
|
||||
public function validate(&$uri, $config, $context) {
|
||||
parent::validate($uri, $config, $context);
|
||||
$uri->userinfo = null;
|
||||
$uri->query = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Parses string representations into their corresponding native PHP
|
||||
* variable type. The base implementation does a simple type-check.
|
||||
*/
|
||||
class HTMLPurifier_VarParser
|
||||
{
|
||||
|
||||
const STRING = 1;
|
||||
const ISTRING = 2;
|
||||
const TEXT = 3;
|
||||
const ITEXT = 4;
|
||||
const INT = 5;
|
||||
const FLOAT = 6;
|
||||
const BOOL = 7;
|
||||
const LOOKUP = 8;
|
||||
const ALIST = 9;
|
||||
const HASH = 10;
|
||||
const MIXED = 11;
|
||||
|
||||
/**
|
||||
* Lookup table of allowed types. Mainly for backwards compatibility, but
|
||||
* also convenient for transforming string type names to the integer constants.
|
||||
*/
|
||||
static public $types = array(
|
||||
'string' => self::STRING,
|
||||
'istring' => self::ISTRING,
|
||||
'text' => self::TEXT,
|
||||
'itext' => self::ITEXT,
|
||||
'int' => self::INT,
|
||||
'float' => self::FLOAT,
|
||||
'bool' => self::BOOL,
|
||||
'lookup' => self::LOOKUP,
|
||||
'list' => self::ALIST,
|
||||
'hash' => self::HASH,
|
||||
'mixed' => self::MIXED
|
||||
);
|
||||
|
||||
/**
|
||||
* Lookup table of types that are string, and can have aliases or
|
||||
* allowed value lists.
|
||||
*/
|
||||
static public $stringTypes = array(
|
||||
self::STRING => true,
|
||||
self::ISTRING => true,
|
||||
self::TEXT => true,
|
||||
self::ITEXT => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Validate a variable according to type. Throws
|
||||
* HTMLPurifier_VarParserException if invalid.
|
||||
* It may return NULL as a valid type if $allow_null is true.
|
||||
*
|
||||
* @param $var Variable to validate
|
||||
* @param $type Type of variable, see HTMLPurifier_VarParser->types
|
||||
* @param $allow_null Whether or not to permit null as a value
|
||||
* @return Validated and type-coerced variable
|
||||
*/
|
||||
final public function parse($var, $type, $allow_null = false) {
|
||||
if (is_string($type)) {
|
||||
if (!isset(HTMLPurifier_VarParser::$types[$type])) {
|
||||
throw new HTMLPurifier_VarParserException("Invalid type '$type'");
|
||||
} else {
|
||||
$type = HTMLPurifier_VarParser::$types[$type];
|
||||
}
|
||||
}
|
||||
$var = $this->parseImplementation($var, $type, $allow_null);
|
||||
if ($allow_null && $var === null) return null;
|
||||
// These are basic checks, to make sure nothing horribly wrong
|
||||
// happened in our implementations.
|
||||
switch ($type) {
|
||||
case (self::STRING):
|
||||
case (self::ISTRING):
|
||||
case (self::TEXT):
|
||||
case (self::ITEXT):
|
||||
if (!is_string($var)) break;
|
||||
if ($type == self::ISTRING || $type == self::ITEXT) $var = strtolower($var);
|
||||
return $var;
|
||||
case (self::INT):
|
||||
if (!is_int($var)) break;
|
||||
return $var;
|
||||
case (self::FLOAT):
|
||||
if (!is_float($var)) break;
|
||||
return $var;
|
||||
case (self::BOOL):
|
||||
if (!is_bool($var)) break;
|
||||
return $var;
|
||||
case (self::LOOKUP):
|
||||
case (self::ALIST):
|
||||
case (self::HASH):
|
||||
if (!is_array($var)) break;
|
||||
if ($type === self::LOOKUP) {
|
||||
foreach ($var as $k) if ($k !== true) $this->error('Lookup table contains value other than true');
|
||||
} elseif ($type === self::ALIST) {
|
||||
$keys = array_keys($var);
|
||||
if (array_keys($keys) !== $keys) $this->error('Indices for list are not uniform');
|
||||
}
|
||||
return $var;
|
||||
case (self::MIXED):
|
||||
return $var;
|
||||
default:
|
||||
$this->errorInconsistent(get_class($this), $type);
|
||||
}
|
||||
$this->errorGeneric($var, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually implements the parsing. Base implementation is to not
|
||||
* do anything to $var. Subclasses should overload this!
|
||||
*/
|
||||
protected function parseImplementation($var, $type, $allow_null) {
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception.
|
||||
*/
|
||||
protected function error($msg) {
|
||||
throw new HTMLPurifier_VarParserException($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an inconsistency exception.
|
||||
* @note This should not ever be called. It would be called if we
|
||||
* extend the allowed values of HTMLPurifier_VarParser without
|
||||
* updating subclasses.
|
||||
*/
|
||||
protected function errorInconsistent($class, $type) {
|
||||
throw new HTMLPurifier_Exception("Inconsistency in $class: ".HTMLPurifier_VarParser::getTypeName($type)." not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic error for if a type didn't work.
|
||||
*/
|
||||
protected function errorGeneric($var, $type) {
|
||||
$vtype = gettype($var);
|
||||
$this->error("Expected type ".HTMLPurifier_VarParser::getTypeName($type).", got $vtype");
|
||||
}
|
||||
|
||||
static public function getTypeName($type) {
|
||||
static $lookup;
|
||||
if (!$lookup) {
|
||||
// Lazy load the alternative lookup table
|
||||
$lookup = array_flip(HTMLPurifier_VarParser::$types);
|
||||
}
|
||||
if (!isset($lookup[$type])) return 'unknown';
|
||||
return $lookup[$type];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
9
library/ezyang/htmlpurifier/CREDITS
Normal file
9
library/ezyang/htmlpurifier/CREDITS
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
CREDITS
|
||||
|
||||
Almost everything written by Edward Z. Yang (Ambush Commander). Lots of thanks
|
||||
to the DevNetwork Community for their help (see docs/ref-devnetwork.html for
|
||||
more details), Feyd especially (namely IPv6 and optimization). Thanks to RSnake
|
||||
for letting me package his fantastic XSS cheatsheet for a smoketest.
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
374
library/ezyang/htmlpurifier/INSTALL
Normal file
374
library/ezyang/htmlpurifier/INSTALL
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
|
||||
Install
|
||||
How to install HTML Purifier
|
||||
|
||||
HTML Purifier is designed to run out of the box, so actually using the
|
||||
library is extremely easy. (Although... if you were looking for a
|
||||
step-by-step installation GUI, you've downloaded the wrong software!)
|
||||
|
||||
While the impatient can get going immediately with some of the sample
|
||||
code at the bottom of this library, it's well worth reading this entire
|
||||
document--most of the other documentation assumes that you are familiar
|
||||
with these contents.
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
1. Compatibility
|
||||
|
||||
HTML Purifier is PHP 5 only, and is actively tested from PHP 5.0.5 and
|
||||
up. It has no core dependencies with other libraries. PHP
|
||||
4 support was deprecated on December 31, 2007 with HTML Purifier 3.0.0.
|
||||
HTML Purifier is not compatible with zend.ze1_compatibility_mode.
|
||||
|
||||
These optional extensions can enhance the capabilities of HTML Purifier:
|
||||
|
||||
* iconv : Converts text to and from non-UTF-8 encodings
|
||||
* bcmath : Used for unit conversion and imagecrash protection
|
||||
* tidy : Used for pretty-printing HTML
|
||||
|
||||
These optional libraries can enhance the capabilities of HTML Purifier:
|
||||
|
||||
* CSSTidy : Clean CSS stylesheets using %Core.ExtractStyleBlocks
|
||||
* Net_IDNA2 (PEAR) : IRI support using %Core.EnableIDNA
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
2. Reconnaissance
|
||||
|
||||
A big plus of HTML Purifier is its inerrant support of standards, so
|
||||
your web-pages should be standards-compliant. (They should also use
|
||||
semantic markup, but that's another issue altogether, one HTML Purifier
|
||||
cannot fix without reading your mind.)
|
||||
|
||||
HTML Purifier can process these doctypes:
|
||||
|
||||
* XHTML 1.0 Transitional (default)
|
||||
* XHTML 1.0 Strict
|
||||
* HTML 4.01 Transitional
|
||||
* HTML 4.01 Strict
|
||||
* XHTML 1.1
|
||||
|
||||
...and these character encodings:
|
||||
|
||||
* UTF-8 (default)
|
||||
* Any encoding iconv supports (with crippled internationalization support)
|
||||
|
||||
These defaults reflect what my choices would be if I were authoring an
|
||||
HTML document, however, what you choose depends on the nature of your
|
||||
codebase. If you don't know what doctype you are using, you can determine
|
||||
the doctype from this identifier at the top of your source code:
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
...and the character encoding from this code:
|
||||
|
||||
<meta http-equiv="Content-type" content="text/html;charset=ENCODING">
|
||||
|
||||
If the character encoding declaration is missing, STOP NOW, and
|
||||
read 'docs/enduser-utf8.html' (web accessible at
|
||||
http://htmlpurifier.org/docs/enduser-utf8.html). In fact, even if it is
|
||||
present, read this document anyway, as many websites specify their
|
||||
document's character encoding incorrectly.
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
3. Including the library
|
||||
|
||||
The procedure is quite simple:
|
||||
|
||||
require_once '/path/to/library/HTMLPurifier.auto.php';
|
||||
|
||||
This will setup an autoloader, so the library's files are only included
|
||||
when you use them.
|
||||
|
||||
Only the contents in the library/ folder are necessary, so you can remove
|
||||
everything else when using HTML Purifier in a production environment.
|
||||
|
||||
If you installed HTML Purifier via PEAR, all you need to do is:
|
||||
|
||||
require_once 'HTMLPurifier.auto.php';
|
||||
|
||||
Please note that the usual PEAR practice of including just the classes you
|
||||
want will not work with HTML Purifier's autoloading scheme.
|
||||
|
||||
Advanced users, read on; other users can skip to section 4.
|
||||
|
||||
Autoload compatibility
|
||||
----------------------
|
||||
|
||||
HTML Purifier attempts to be as smart as possible when registering an
|
||||
autoloader, but there are some cases where you will need to change
|
||||
your own code to accomodate HTML Purifier. These are those cases:
|
||||
|
||||
PHP VERSION IS LESS THAN 5.1.2, AND YOU'VE DEFINED __autoload
|
||||
Because spl_autoload_register() doesn't exist in early versions
|
||||
of PHP 5, HTML Purifier has no way of adding itself to the autoload
|
||||
stack. Modify your __autoload function to test
|
||||
HTMLPurifier_Bootstrap::autoload($class)
|
||||
|
||||
For example, suppose your autoload function looks like this:
|
||||
|
||||
function __autoload($class) {
|
||||
require str_replace('_', '/', $class) . '.php';
|
||||
return true;
|
||||
}
|
||||
|
||||
A modified version with HTML Purifier would look like this:
|
||||
|
||||
function __autoload($class) {
|
||||
if (HTMLPurifier_Bootstrap::autoload($class)) return true;
|
||||
require str_replace('_', '/', $class) . '.php';
|
||||
return true;
|
||||
}
|
||||
|
||||
Note that there *is* some custom behavior in our autoloader; the
|
||||
original autoloader in our example would work for 99% of the time,
|
||||
but would fail when including language files.
|
||||
|
||||
AN __autoload FUNCTION IS DECLARED AFTER OUR AUTOLOADER IS REGISTERED
|
||||
spl_autoload_register() has the curious behavior of disabling
|
||||
the existing __autoload() handler. Users need to explicitly
|
||||
spl_autoload_register('__autoload'). Because we use SPL when it
|
||||
is available, __autoload() will ALWAYS be disabled. If __autoload()
|
||||
is declared before HTML Purifier is loaded, this is not a problem:
|
||||
HTML Purifier will register the function for you. But if it is
|
||||
declared afterwards, it will mysteriously not work. This
|
||||
snippet of code (after your autoloader is defined) will fix it:
|
||||
|
||||
spl_autoload_register('__autoload')
|
||||
|
||||
Users should also be on guard if they use a version of PHP previous
|
||||
to 5.1.2 without an autoloader--HTML Purifier will define __autoload()
|
||||
for you, which can collide with an autoloader that was added by *you*
|
||||
later.
|
||||
|
||||
|
||||
For better performance
|
||||
----------------------
|
||||
|
||||
Opcode caches, which greatly speed up PHP initialization for scripts
|
||||
with large amounts of code (HTML Purifier included), don't like
|
||||
autoloaders. We offer an include file that includes all of HTML Purifier's
|
||||
files in one go in an opcode cache friendly manner:
|
||||
|
||||
// If /path/to/library isn't already in your include path, uncomment
|
||||
// the below line:
|
||||
// require '/path/to/library/HTMLPurifier.path.php';
|
||||
|
||||
require 'HTMLPurifier.includes.php';
|
||||
|
||||
Optional components still need to be included--you'll know if you try to
|
||||
use a feature and you get a class doesn't exists error! The autoloader
|
||||
can be used in conjunction with this approach to catch classes that are
|
||||
missing. Simply add this afterwards:
|
||||
|
||||
require 'HTMLPurifier.autoload.php';
|
||||
|
||||
Standalone version
|
||||
------------------
|
||||
|
||||
HTML Purifier has a standalone distribution; you can also generate
|
||||
a standalone file from the full version by running the script
|
||||
maintenance/generate-standalone.php . The standalone version has the
|
||||
benefit of having most of its code in one file, so parsing is much
|
||||
faster and the library is easier to manage.
|
||||
|
||||
If HTMLPurifier.standalone.php exists in the library directory, you
|
||||
can use it like this:
|
||||
|
||||
require '/path/to/HTMLPurifier.standalone.php';
|
||||
|
||||
This is equivalent to including HTMLPurifier.includes.php, except that
|
||||
the contents of standalone/ will be added to your path. To override this
|
||||
behavior, specify a new HTMLPURIFIER_PREFIX where standalone files can
|
||||
be found (usually, this will be one directory up, the "true" library
|
||||
directory in full distributions). Don't forget to set your path too!
|
||||
|
||||
The autoloader can be added to the end to ensure the classes are
|
||||
loaded when necessary; otherwise you can manually include them.
|
||||
To use the autoloader, use this:
|
||||
|
||||
require 'HTMLPurifier.autoload.php';
|
||||
|
||||
For advanced users
|
||||
------------------
|
||||
|
||||
HTMLPurifier.auto.php performs a number of operations that can be done
|
||||
individually. These are:
|
||||
|
||||
HTMLPurifier.path.php
|
||||
Puts /path/to/library in the include path. For high performance,
|
||||
this should be done in php.ini.
|
||||
|
||||
HTMLPurifier.autoload.php
|
||||
Registers our autoload handler HTMLPurifier_Bootstrap::autoload($class).
|
||||
|
||||
You can do these operations by yourself--in fact, you must modify your own
|
||||
autoload handler if you are using a version of PHP earlier than PHP 5.1.2
|
||||
(See "Autoload compatibility" above).
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
4. Configuration
|
||||
|
||||
HTML Purifier is designed to run out-of-the-box, but occasionally HTML
|
||||
Purifier needs to be told what to do. If you answer no to any of these
|
||||
questions, read on; otherwise, you can skip to the next section (or, if you're
|
||||
into configuring things just for the heck of it, skip to 4.3).
|
||||
|
||||
* Am I using UTF-8?
|
||||
* Am I using XHTML 1.0 Transitional?
|
||||
|
||||
If you answered no to any of these questions, instantiate a configuration
|
||||
object and read on:
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
|
||||
|
||||
4.1. Setting a different character encoding
|
||||
|
||||
You really shouldn't use any other encoding except UTF-8, especially if you
|
||||
plan to support multilingual websites (read section three for more details).
|
||||
However, switching to UTF-8 is not always immediately feasible, so we can
|
||||
adapt.
|
||||
|
||||
HTML Purifier uses iconv to support other character encodings, as such,
|
||||
any encoding that iconv supports <http://www.gnu.org/software/libiconv/>
|
||||
HTML Purifier supports with this code:
|
||||
|
||||
$config->set('Core.Encoding', /* put your encoding here */);
|
||||
|
||||
An example usage for Latin-1 websites (the most common encoding for English
|
||||
websites):
|
||||
|
||||
$config->set('Core.Encoding', 'ISO-8859-1');
|
||||
|
||||
Note that HTML Purifier's support for non-Unicode encodings is crippled by the
|
||||
fact that any character not supported by that encoding will be silently
|
||||
dropped, EVEN if it is ampersand escaped. If you want to work around
|
||||
this, you are welcome to read docs/enduser-utf8.html for a fix,
|
||||
but please be cognizant of the issues the "solution" creates (for this
|
||||
reason, I do not include the solution in this document).
|
||||
|
||||
|
||||
4.2. Setting a different doctype
|
||||
|
||||
For those of you using HTML 4.01 Transitional, you can disable
|
||||
XHTML output like this:
|
||||
|
||||
$config->set('HTML.Doctype', 'HTML 4.01 Transitional');
|
||||
|
||||
Other supported doctypes include:
|
||||
|
||||
* HTML 4.01 Strict
|
||||
* HTML 4.01 Transitional
|
||||
* XHTML 1.0 Strict
|
||||
* XHTML 1.0 Transitional
|
||||
* XHTML 1.1
|
||||
|
||||
|
||||
4.3. Other settings
|
||||
|
||||
There are more configuration directives which can be read about
|
||||
here: <http://htmlpurifier.org/live/configdoc/plain.html> They're a bit boring,
|
||||
but they can help out for those of you who like to exert maximum control over
|
||||
your code. Some of the more interesting ones are configurable at the
|
||||
demo <http://htmlpurifier.org/demo.php> and are well worth looking into
|
||||
for your own system.
|
||||
|
||||
For example, you can fine tune allowed elements and attributes, convert
|
||||
relative URLs to absolute ones, and even autoparagraph input text! These
|
||||
are, respectively, %HTML.Allowed, %URI.MakeAbsolute and %URI.Base, and
|
||||
%AutoFormat.AutoParagraph. The %Namespace.Directive naming convention
|
||||
translates to:
|
||||
|
||||
$config->set('Namespace.Directive', $value);
|
||||
|
||||
E.g.
|
||||
|
||||
$config->set('HTML.Allowed', 'p,b,a[href],i');
|
||||
$config->set('URI.Base', 'http://www.example.com');
|
||||
$config->set('URI.MakeAbsolute', true);
|
||||
$config->set('AutoFormat.AutoParagraph', true);
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
5. Caching
|
||||
|
||||
HTML Purifier generates some cache files (generally one or two) to speed up
|
||||
its execution. For maximum performance, make sure that
|
||||
library/HTMLPurifier/DefinitionCache/Serializer is writeable by the webserver.
|
||||
|
||||
If you are in the library/ folder of HTML Purifier, you can set the
|
||||
appropriate permissions using:
|
||||
|
||||
chmod -R 0755 HTMLPurifier/DefinitionCache/Serializer
|
||||
|
||||
If the above command doesn't work, you may need to assign write permissions
|
||||
to all. This may be necessary if your webserver runs as nobody, but is
|
||||
not recommended since it means any other user can write files in the
|
||||
directory. Use:
|
||||
|
||||
chmod -R 0777 HTMLPurifier/DefinitionCache/Serializer
|
||||
|
||||
You can also chmod files via your FTP client; this option
|
||||
is usually accessible by right clicking the corresponding directory and
|
||||
then selecting "chmod" or "file permissions".
|
||||
|
||||
Starting with 2.0.1, HTML Purifier will generate friendly error messages
|
||||
that will tell you exactly what you have to chmod the directory to, if in doubt,
|
||||
follow its advice.
|
||||
|
||||
If you are unable or unwilling to give write permissions to the cache
|
||||
directory, you can either disable the cache (and suffer a performance
|
||||
hit):
|
||||
|
||||
$config->set('Core.DefinitionCache', null);
|
||||
|
||||
Or move the cache directory somewhere else (no trailing slash):
|
||||
|
||||
$config->set('Cache.SerializerPath', '/home/user/absolute/path');
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
6. Using the code
|
||||
|
||||
The interface is mind-numbingly simple:
|
||||
|
||||
$purifier = new HTMLPurifier($config);
|
||||
$clean_html = $purifier->purify( $dirty_html );
|
||||
|
||||
That's it! For more examples, check out docs/examples/ (they aren't very
|
||||
different though). Also, docs/enduser-slow.html gives advice on what to
|
||||
do if HTML Purifier is slowing down your application.
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
7. Quick install
|
||||
|
||||
First, make sure library/HTMLPurifier/DefinitionCache/Serializer is
|
||||
writable by the webserver (see Section 5: Caching above for details).
|
||||
If your website is in UTF-8 and XHTML Transitional, use this code:
|
||||
|
||||
<?php
|
||||
require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php';
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$purifier = new HTMLPurifier($config);
|
||||
$clean_html = $purifier->purify($dirty_html);
|
||||
?>
|
||||
|
||||
If your website is in a different encoding or doctype, use this code:
|
||||
|
||||
<?php
|
||||
require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php';
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Core.Encoding', 'ISO-8859-1'); // replace with your encoding
|
||||
$config->set('HTML.Doctype', 'HTML 4.01 Transitional'); // replace with your doctype
|
||||
$purifier = new HTMLPurifier($config);
|
||||
|
||||
$clean_html = $purifier->purify($dirty_html);
|
||||
?>
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
60
library/ezyang/htmlpurifier/INSTALL.fr.utf8
Normal file
60
library/ezyang/htmlpurifier/INSTALL.fr.utf8
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
Installation
|
||||
Comment installer HTML Purifier
|
||||
|
||||
Attention : Ce document est encodé en UTF-8, si les lettres avec des accents
|
||||
ne s'affichent pas, prenez un meilleur éditeur de texte.
|
||||
|
||||
L'installation de HTML Purifier est très simple, parce qu'il n'a pas besoin
|
||||
de configuration. Pour les utilisateurs impatients, le code se trouve dans le
|
||||
pied de page, mais je recommande de lire le document.
|
||||
|
||||
1. Compatibilité
|
||||
|
||||
HTML Purifier fonctionne avec PHP 5. PHP 5.0.5 est la dernière version testée.
|
||||
Il ne dépend pas d'autres librairies.
|
||||
|
||||
Les extensions optionnelles sont iconv (généralement déjà installée) et tidy
|
||||
(répendue aussi). Si vous utilisez UTF-8 et que vous ne voulez pas l'indentation,
|
||||
vous pouvez utiliser HTML Purifier sans ces extensions.
|
||||
|
||||
|
||||
2. Inclure la librairie
|
||||
|
||||
Quand vous devez l'utilisez, incluez le :
|
||||
|
||||
require_once('/path/to/library/HTMLPurifier.auto.php');
|
||||
|
||||
Ne pas l'inclure si ce n'est pas nécessaire, car HTML Purifier est lourd.
|
||||
|
||||
HTML Purifier utilise "autoload". Si vous avez défini la fonction __autoload,
|
||||
vous devez ajouter cette fonction :
|
||||
|
||||
spl_autoload_register('__autoload')
|
||||
|
||||
Plus d'informations dans le document "INSTALL".
|
||||
|
||||
3. Installation rapide
|
||||
|
||||
Si votre site Web est en UTF-8 et XHTML Transitional, utilisez :
|
||||
|
||||
<?php
|
||||
require_once('/path/to/htmlpurifier/library/HTMLPurifier.auto.php');
|
||||
$purificateur = new HTMLPurifier();
|
||||
$html_propre = $purificateur->purify($html_a_purifier);
|
||||
?>
|
||||
|
||||
Sinon, utilisez :
|
||||
|
||||
<?php
|
||||
require_once('/path/to/html/purifier/library/HTMLPurifier.auto.load');
|
||||
$config = $HTMLPurifier_Config::createDefault();
|
||||
$config->set('Core', 'Encoding', 'ISO-8859-1'); //Remplacez par votre
|
||||
encodage
|
||||
$config->set('Core', 'XHTML', true); //Remplacer par false si HTML 4.01
|
||||
$purificateur = new HTMLPurifier($config);
|
||||
$html_propre = $purificateur->purify($html_a_purifier);
|
||||
?>
|
||||
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
504
library/ezyang/htmlpurifier/LICENSE
Normal file
504
library/ezyang/htmlpurifier/LICENSE
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
1094
library/ezyang/htmlpurifier/NEWS
Normal file
1094
library/ezyang/htmlpurifier/NEWS
Normal file
|
|
@ -0,0 +1,1094 @@
|
|||
NEWS ( CHANGELOG and HISTORY ) HTMLPurifier
|
||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
|
||||
= KEY ====================
|
||||
# Breaks back-compat
|
||||
! Feature
|
||||
- Bugfix
|
||||
+ Sub-comment
|
||||
. Internal change
|
||||
==========================
|
||||
|
||||
4.7.0, released 2015-08-04
|
||||
# opacity is now considered a "tricky" CSS property rather than a
|
||||
proprietary one.
|
||||
! %AutoFormat.RemoveEmpty.Predicate for specifying exactly when
|
||||
an element should be considered "empty" (maybe preserve if it
|
||||
has attributes), and modify iframe support so that the iframe
|
||||
is removed if it is missing a src attribute. Thanks meeva for
|
||||
reporting.
|
||||
- Don't truncate upon encountering </div> when using DOMLex. Thanks
|
||||
Myrto Christina for finally convincing me to fix this.
|
||||
- Update YouTube filter for new code.
|
||||
- Fix parsing of rgb() values with spaces in them for 'border'
|
||||
attribute.
|
||||
- Don't remove foo="" attributes if foo is a boolean attribute. Thanks
|
||||
valME for reporting.
|
||||
|
||||
4.6.0, released 2013-11-30
|
||||
# Secure URI munge hashing algorithm has changed to hash_hmac("sha256", $url, $secret).
|
||||
Please update any verification scripts you may have.
|
||||
# URI parsing algorithm was made more strict, so only prefixes which
|
||||
looks like schemes will actually be schemes. Thanks
|
||||
Michael Gusev <mgusev@sugarcrm.com> for fixing.
|
||||
# %Core.EscapeInvalidChildren is no longer supported, and no longer does
|
||||
anything.
|
||||
! New directive %Core.AllowHostnameUnderscore which allows underscores
|
||||
in hostnames.
|
||||
- Eliminate quadratic behavior in DOMLex by using a proper queue.
|
||||
Thanks Ole Laursen for noticing this.
|
||||
- Rewritten MakeWellFormed/FixNesting implementation eliminates quadratic
|
||||
behavior in the rest of the purificaiton pipeline. Thanks Chedburn
|
||||
Networks for sponsoring this work.
|
||||
- Made Linkify URL parser a bit less permissive, so that non-breaking
|
||||
spaces and commas are not included as part of URL. Thanks nAS for fixing.
|
||||
- Fix some bad interactions with %HTML.Allowed and injectors. Thanks
|
||||
David Hirtz for reporting.
|
||||
- Fix infinite loop in DirectLex. Thanks Ashar Javed (@soaj1664ashar)
|
||||
for reporting.
|
||||
|
||||
4.5.0, released 2013-02-17
|
||||
# Fix bug where stacked attribute transforms clobber each other;
|
||||
this also means it's no longer possible to override attribute
|
||||
transforms in later modules. No internal code was using this
|
||||
but this may break some clients.
|
||||
# We now use SHA-1 to identify cached definitions, instead of MD5.
|
||||
! Support display:inline-block
|
||||
! Support for more white-space CSS values.
|
||||
! Permit underscores in font families
|
||||
! Support for page-break-* CSS3 properties when proprietary properties
|
||||
are enabled.
|
||||
! New directive %Core.DisableExcludes; can be set to 'true' to turn off
|
||||
SGML excludes checking. If HTML Purifier is removing too much text
|
||||
and you don't care about full standards compliance, try setting this to
|
||||
'true'.
|
||||
- Use prepend for SPL autoloading on PHP 5.3 and later.
|
||||
- Fix bug with nofollow transform when pre-existing rel exists.
|
||||
- Fix bug where background:url() always gets lower-cased
|
||||
(but not background-image:url())
|
||||
- Fix bug with non lower-case color names in HTML
|
||||
- Fix bug where data URI validation doesn't remove temporary files.
|
||||
Thanks Javier Marín Ros <javiermarinros@gmail.com> for reporting.
|
||||
- Don't remove certain empty tags on RemoveEmpty.
|
||||
|
||||
4.4.0, released 2012-01-18
|
||||
# Removed PEARSax3 handler.
|
||||
# URI.Munge now munges URIs inside the same host that go from https
|
||||
to http. Reported by Neike Taika-Tessaro.
|
||||
# Core.EscapeNonASCIICharacters now always transforms entities to
|
||||
entities, even if target encoding is UTF-8.
|
||||
# Tighten up selector validation in ExtractStyleBlocks.
|
||||
Non-syntactically valid selectors are now rejected, along with
|
||||
some of the more obscure ones such as attribute selectors, the
|
||||
:lang pseudoselector, and anything not in CSS2.1. Furthermore,
|
||||
ID and class selectors now work properly with the relevant
|
||||
configuration attributes. Also, mute errors when parsing CSS
|
||||
with CSS Tidy. Reported by Mario Heiderich and Norman Hippert.
|
||||
! Added support for 'scope' attribute on tables.
|
||||
! Added %HTML.TargetBlank, which adds target="blank" to all outgoing links.
|
||||
! Properly handle sub-lists directly nested inside of lists in
|
||||
a standards compliant way, by moving them into the preceding <li>
|
||||
! Added %HTML.AllowedComments and %HTML.AllowedCommentsRegexp for
|
||||
limited allowed comments in untrusted situations.
|
||||
! Implement iframes, and allow them to be used in untrusted mode with
|
||||
%HTML.SafeIframe and %URI.SafeIframeRegexp. Thanks Bradley M. Froehle
|
||||
<brad.froehle@gmail.com> for submitting an initial version of the patch.
|
||||
! The Forms module now works properly for transitional doctypes.
|
||||
! Added support for internationalized domain names. You need the PEAR
|
||||
Net_IDNA2 module to be in your path; if it is installed, ensure the
|
||||
class can be loaded and then set %Core.EnableIDNA to true.
|
||||
- Color keywords are now case insensitive. Thanks Yzmir Ramirez
|
||||
<yramirez-htmlpurifier@adicio.com> for reporting.
|
||||
- Explicitly initialize anonModule variable to null.
|
||||
- Do not duplicate nofollow if already present. Thanks 178
|
||||
for reporting.
|
||||
- Do not add nofollow if hostname matches our current host. Thanks 178
|
||||
for reporting, and Neike Taika-Tessaro for helping diagnose.
|
||||
- Do not unset parser variable; this fixes intermittent serialization
|
||||
problems. Thanks Neike Taika-Tessaro for reporting, bill
|
||||
<10010tiger@gmail.com> for diagnosing.
|
||||
- Fix iconv truncation bug, where non-UTF-8 target encodings see
|
||||
output truncated after around 8000 characters. Thanks Jörg Ludwig
|
||||
<joerg.ludwig@iserv.eu> for reporting.
|
||||
- Fix broken table content model for XHTML1.1 (and also earlier
|
||||
versions, although the W3C validator doesn't catch those violations).
|
||||
Thanks GlitchMr <glitch.mr@gmail.com> for reporting.
|
||||
|
||||
4.3.0, released 2011-03-27
|
||||
# Fixed broken caching of customized raw definitions, but requires an
|
||||
API change. The old API still works but will emit a warning,
|
||||
see http://htmlpurifier.org/docs/enduser-customize.html#optimized
|
||||
for how to upgrade your code.
|
||||
# Protect against Internet Explorer innerHTML behavior by specially
|
||||
treating attributes with backticks but no angled brackets, quotes or
|
||||
spaces. This constitutes a slight semantic change, which can be
|
||||
reverted using %Output.FixInnerHTML. Reported by Neike Taika-Tessaro
|
||||
and Mario Heiderich.
|
||||
# Protect against cssText/innerHTML by restricting allowed characters
|
||||
used in fonts further than mandated by the specification and encoding
|
||||
some extra special characters in URLs. Reported by Neike
|
||||
Taika-Tessaro and Mario Heiderich.
|
||||
! Added %HTML.Nofollow to add rel="nofollow" to external links.
|
||||
! More types of SPL autoloaders allowed on later versions of PHP.
|
||||
! Implementations for position, top, left, right, bottom, z-index
|
||||
when %CSS.Trusted is on.
|
||||
! Add %Cache.SerializerPermissions option for custom serializer
|
||||
directory/file permissions
|
||||
! Fix longstanding bug in Flash support for non-IE browsers, and
|
||||
allow more wmode attributes.
|
||||
! Add %CSS.AllowedFonts to restrict permissible font names.
|
||||
- Switch to an iterative traversal of the DOM, which prevents us
|
||||
from running out of stack space for deeply nested documents.
|
||||
Thanks Maxim Krizhanovsky for contributing a patch.
|
||||
- Make removal of conditional IE comments ungreedy; thanks Bernd
|
||||
for reporting.
|
||||
- Escape CDATA before removing Internet Explorer comments.
|
||||
- Fix removal of id attributes under certain conditions by ensuring
|
||||
armor attributes are preserved when recreating tags.
|
||||
- Check if schema.ser was corrupted.
|
||||
- Check if zend.ze1_compatibility_mode is on, and error out if it is.
|
||||
This safety check is only done for HTMLPurifier.auto.php; if you
|
||||
are using standalone or the specialized includes files, you're
|
||||
expected to know what you're doing.
|
||||
- Stop repeatedly writing the cache file after I'm done customizing a
|
||||
raw definition. Reported by ajh.
|
||||
- Switch to using require_once in the Bootstrap to work around bad
|
||||
interaction with Zend Debugger and APC. Reported by Antonio Parraga.
|
||||
- Fix URI handling when hostname is missing but scheme is present.
|
||||
Reported by Neike Taika-Tessaro.
|
||||
- Fix missing numeric entities on DirectLex; thanks Neike Taika-Tessaro
|
||||
for reporting.
|
||||
- Fix harmless notice from indexing into empty string. Thanks Matthijs
|
||||
Kooijman <matthijs@stdin.nl> for reporting.
|
||||
- Don't autoclose no parent elements are able to support the element
|
||||
that triggered the autoclose. In particular fixes strange behavior
|
||||
of stray <li> tags. Thanks pkuliga@gmail.com for reporting and
|
||||
Neike Taika-Tessaro <pinkgothic@gmail.com> for debugging assistance.
|
||||
|
||||
4.2.0, released 2010-09-15
|
||||
! Added %Core.RemoveProcessingInstructions, which lets you remove
|
||||
<? ... ?> statements.
|
||||
! Added %URI.DisableResources functionality; the directive originally
|
||||
did nothing. Thanks David Rothstein for reporting.
|
||||
! Add documentation about configuration directive types.
|
||||
! Add %CSS.ForbiddenProperties configuration directive.
|
||||
! Add %HTML.FlashAllowFullScreen to permit embedded Flash objects
|
||||
to utilize full-screen mode.
|
||||
! Add optional support for the <code>file</code> URI scheme, enable
|
||||
by explicitly setting %URI.AllowedSchemes.
|
||||
! Add %Core.NormalizeNewlines options to allow turning off newline
|
||||
normalization.
|
||||
- Fix improper handling of Internet Explorer conditional comments
|
||||
by parser. Thanks zmonteca for reporting.
|
||||
- Fix missing attributes bug when running on Mac Snow Leopard and APC.
|
||||
Thanks sidepodcast for the fix.
|
||||
- Warn if an element is allowed, but an attribute it requires is
|
||||
not allowed.
|
||||
|
||||
4.1.1, released 2010-05-31
|
||||
- Fix undefined index warnings in maintenance scripts.
|
||||
- Fix bug in DirectLex for parsing elements with a single attribute
|
||||
with entities.
|
||||
- Rewrite CSS output logic for font-family and url(). Thanks Mario
|
||||
Heiderich <mario.heiderich@googlemail.com> for reporting and Takeshi
|
||||
Terada <t-terada@violet.plala.or.jp> for suggesting the fix.
|
||||
- Emit an error for CollectErrors if a body is extracted
|
||||
- Fix bug where in background-position for center keyword handling.
|
||||
- Fix infinite loop when a wrapper element is inserted in a context
|
||||
where it's not allowed. Thanks Lars <lars@renoz.dk> for reporting.
|
||||
- Remove +x bit and shebang from index.php; only supported mode is to
|
||||
explicitly call it with php.
|
||||
- Make test script less chatty when log_errors is on.
|
||||
|
||||
4.1.0, released 2010-04-26
|
||||
! Support proprietary height attribute on table element
|
||||
! Support YouTube slideshows that contain /cp/ in their URL.
|
||||
! Support for data: URI scheme; not enabled by default, add it using
|
||||
%URI.AllowedSchemes
|
||||
! Support flashvars when using %HTML.SafeObject and %HTML.SafeEmbed.
|
||||
! Support for Internet Explorer compatibility with %HTML.SafeObject
|
||||
using %Output.FlashCompat.
|
||||
! Handle <ol><ol> properly, by inserting the necessary <li> tag.
|
||||
- Always quote the insides of url(...) in CSS.
|
||||
|
||||
4.0.0, released 2009-07-07
|
||||
# APIs for ConfigSchema subsystem have substantially changed. See
|
||||
docs/dev-config-bcbreaks.txt for details; in essence, anything that
|
||||
had both namespace and directive now have a single unified key.
|
||||
# Some configuration directives were renamed, specifically:
|
||||
%AutoFormatParam.PurifierLinkifyDocURL -> %AutoFormat.PurifierLinkify.DocURL
|
||||
%FilterParam.ExtractStyleBlocksEscaping -> %Filter.ExtractStyleBlocks.Escaping
|
||||
%FilterParam.ExtractStyleBlocksScope -> %Filter.ExtractStyleBlocks.Scope
|
||||
%FilterParam.ExtractStyleBlocksTidyImpl -> %Filter.ExtractStyleBlocks.TidyImpl
|
||||
As usual, the old directive names will still work, but will throw E_NOTICE
|
||||
errors.
|
||||
# The allowed values for class have been relaxed to allow all of CDATA for
|
||||
doctypes that are not XHTML 1.1 or XHTML 2.0. For old behavior, set
|
||||
%Attr.ClassUseCDATA to false.
|
||||
# Instead of appending the content model to an old content model, a blank
|
||||
element will replace the old content model. You can use #SUPER to get
|
||||
the old content model.
|
||||
! More robust support for name="" and id=""
|
||||
! HTMLPurifier_Config::inherit($config) allows you to inherit one
|
||||
configuration, and have changes to that configuration be propagated
|
||||
to all of its children.
|
||||
! Implement %HTML.Attr.Name.UseCDATA, which relaxes validation rules on
|
||||
the name attribute when set. Use with care. Thanks Ian Cook for
|
||||
sponsoring.
|
||||
! Implement %AutoFormat.RemoveEmpty.RemoveNbsp, which removes empty
|
||||
tags that contain non-breaking spaces as well other whitespace. You
|
||||
can also modify which tags should have maintained with
|
||||
%AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.
|
||||
! Implement %Attr.AllowedClasses, which allows administrators to restrict
|
||||
classes users can use to a specified finite set of classes, and
|
||||
%Attr.ForbiddenClasses, which is the logical inverse.
|
||||
! You can now maintain your own configuration schema directories by
|
||||
creating a config-schema.php file or passing an extra argument. Check
|
||||
docs/dev-config-schema.html for more details.
|
||||
! Added HTMLPurifier_Config->serialize() method, which lets you save away
|
||||
your configuration in a compact serial file, which you can unserialize
|
||||
and use directly without having to go through the overhead of setup.
|
||||
- Fix bug where URIDefinition would not get cleared if it's directives got
|
||||
changed.
|
||||
- Fix fatal error in HTMLPurifier_Encoder on certain platforms (probably NetBSD 5.0)
|
||||
- Fix bug in Linkify autoformatter involving <a><span>http://foo</span></a>
|
||||
- Make %URI.Munge not apply to links that have the same host as your host.
|
||||
- Prevent stray </body> tag from truncating output, if a second </body>
|
||||
is present.
|
||||
. Created script maintenance/rename-config.php for renaming a configuration
|
||||
directive while maintaining its alias. This script does not change source code.
|
||||
. Implement namespace locking for definition construction, to prevent
|
||||
bugs where a directive is used for definition construction but is not
|
||||
used to construct the cache hash.
|
||||
|
||||
3.3.0, released 2009-02-16
|
||||
! Implement CSS property 'overflow' when %CSS.AllowTricky is true.
|
||||
! Implement generic property list classess
|
||||
- Fix bug with testEncodingSupportsASCII() algorithm when iconv() implementation
|
||||
does not do the "right thing" with characters not supported in the output
|
||||
set.
|
||||
- Spellcheck UTF-8: The Secret To Character Encoding
|
||||
- Fix improper removal of the contents of elements with only whitespace. Thanks
|
||||
Eric Wald for reporting.
|
||||
- Fix broken test suite in versions of PHP without spl_autoload_register()
|
||||
- Fix degenerate case with YouTube filter involving double hyphens.
|
||||
Thanks Pierre Attar for reporting.
|
||||
- Fix YouTube rendering problem on certain versions of Firefox.
|
||||
- Fix CSSDefinition Printer problems with decorators
|
||||
- Add text parameter to unit tests, forces text output
|
||||
. Add verbose mode to command line test runner, use (--verbose)
|
||||
. Turn on unit tests for UnitConverter
|
||||
. Fix missing version number in configuration %Attr.DefaultImageAlt (added 3.2.0)
|
||||
. Fix newline errors that caused spurious failures when CRLF HTML Purifier was
|
||||
tested on Linux.
|
||||
. Removed trailing whitespace from all text files, see
|
||||
remote-trailing-whitespace.php maintenance script.
|
||||
. Convert configuration to use property list backend.
|
||||
|
||||
3.2.0, released 2008-10-31
|
||||
# Using %Core.CollectErrors forces line number/column tracking on, whereas
|
||||
previously you could theoretically turn it off.
|
||||
# HTMLPurifier_Injector->notifyEnd() is formally deprecated. Please
|
||||
use handleEnd() instead.
|
||||
! %Output.AttrSort for when you need your attributes in alphabetical order to
|
||||
deal with a bug in FCKEditor. Requested by frank farmer.
|
||||
! Enable HTML comments when %HTML.Trusted is on. Requested by Waldo Jaquith.
|
||||
! Proper support for name attribute. It is now allowed and equivalent to the id
|
||||
attribute in a and img tags, and is only converted to id when %HTML.TidyLevel
|
||||
is heavy (for all doctypes).
|
||||
! %AutoFormat.RemoveEmpty to remove some empty tags from documents. Please don't
|
||||
use on hand-written HTML.
|
||||
! Add error-cases for unsupported elements in MakeWellFormed. This enables
|
||||
the strategy to be used, standalone, on untrusted input.
|
||||
! %Core.AggressivelyFixLt is on by default. This causes more sensible
|
||||
processing of left angled brackets in smileys and other whatnot.
|
||||
! Test scripts now have a 'type' parameter, which lets you say 'htmlpurifier',
|
||||
'phpt', 'vtest', etc. in order to only execute those tests. This supercedes
|
||||
the --only-phpt parameter, although for backwards-compatibility the flag
|
||||
will still work.
|
||||
! AutoParagraph auto-formatter will now preserve double-newlines upon output.
|
||||
Users who are not performing inbound filtering, this may seem a little
|
||||
useless, but as a bonus, the test suite and handling of edge cases is also
|
||||
improved.
|
||||
! Experimental implementation of forms for %HTML.Trusted
|
||||
! Track column numbers when maintain line numbers is on
|
||||
! Proprietary 'background' attribute on table-related elements converted into
|
||||
corresponding CSS. Thanks Fusemail for sponsoring this feature!
|
||||
! Add forward(), forwardUntilEndToken(), backward() and current() to Injector
|
||||
supertype.
|
||||
! HTMLPurifier_Injector->handleEnd() permits modification to end tokens. The
|
||||
time of operation varies slightly from notifyEnd() as *all* end tokens are
|
||||
processed by the injector before they are subject to the well-formedness rules.
|
||||
! %Attr.DefaultImageAlt allows overriding default behavior of setting alt to
|
||||
basename of image when not present.
|
||||
! %AutoFormat.DisplayLinkURI neuters <a> tags into plain text URLs.
|
||||
- Fix two bugs in %URI.MakeAbsolute; one involving empty paths in base URLs,
|
||||
the other involving an undefined $is_folder error.
|
||||
- Throw error when %Core.Encoding is set to a spurious value. Previously,
|
||||
this errored silently and returned false.
|
||||
- Redirected stderr to stdout for flush error output.
|
||||
- %URI.DisableExternal will now use the host in %URI.Base if %URI.Host is not
|
||||
available.
|
||||
- Do not re-munge URL if the output URL has the same host as the input URL.
|
||||
Requested by Chris.
|
||||
- Fix error in documentation regarding %Filter.ExtractStyleBlocks
|
||||
- Prevent <![CDATA[<body></body>]]> from triggering %Core.ConvertDocumentToFragment
|
||||
- Fix bug with inline elements in blockquotes conflicting with strict doctype
|
||||
- Detect if HTML support is disabled for DOM by checking for loadHTML() method.
|
||||
- Fix bug where dots and double-dots in absolute URLs without hostname were
|
||||
not collapsed by URIFilter_MakeAbsolute.
|
||||
- Fix bug with anonymous modules operating on SafeEmbed or SafeObject elements
|
||||
by reordering their addition.
|
||||
- Will now throw exception on many error conditions during lexer creation; also
|
||||
throw an exception when MaintainLineNumbers is true, but a non-tracksLineNumbers
|
||||
is being used.
|
||||
- Detect if domxml extension is loaded, and use DirectLEx accordingly.
|
||||
- Improve handling of big numbers with floating point arithmetic in UnitConverter.
|
||||
Reported by David Morton.
|
||||
. Strategy_MakeWellFormed now operates in-place, saving memory and allowing
|
||||
for more interesting filter-backtracking
|
||||
. New HTMLPurifier_Injector->rewind() functionality, allows injectors to rewind
|
||||
index to reprocess tokens.
|
||||
. StringHashParser now allows for multiline sections with "empty" content;
|
||||
previously the section would remain undefined.
|
||||
. Added --quick option to multitest.php, which tests only the most recent
|
||||
release for each series.
|
||||
. Added --distro option to multitest.php, which accepts either 'normal' or
|
||||
'standalone'. This supercedes --exclude-normal and --exclude-standalone
|
||||
|
||||
3.1.1, released 2008-06-19
|
||||
# %URI.Munge now, by default, does not munge resources (for example, <img src="">)
|
||||
In order to enable this again, please set %URI.MungeResources to true.
|
||||
! More robust imagecrash protection with height/width CSS with %CSS.MaxImgLength,
|
||||
and height/width HTML with %HTML.MaxImgLength.
|
||||
! %URI.MungeSecretKey for secure URI munging. Thanks Chris
|
||||
for sponsoring this feature. Check out the corresponding documentation
|
||||
for details. (Att Nightly testers: The API for this feature changed before
|
||||
the general release. Namely, rename your directives %URI.SecureMungeSecretKey =>
|
||||
%URI.MungeSecretKey and and %URI.SecureMunge => %URI.Munge)
|
||||
! Implemented post URI filtering. Set member variable $post to true to set
|
||||
a URIFilter as such.
|
||||
! Allow modules to define injectors via $info_injector. Injectors are
|
||||
automatically disabled if injector's needed elements are not found.
|
||||
! Support for "safe" objects added, use %HTML.SafeObject and %HTML.SafeEmbed.
|
||||
Thanks Chris for sponsoring. If you've been using ad hoc code from the
|
||||
forums, PLEASE use this instead.
|
||||
! Added substitutions for %e, %n, %a and %p in %URI.Munge (in order,
|
||||
embedded, tag name, attribute name, CSS property name). See %URI.Munge
|
||||
for more details. Requested by Jochem Blok.
|
||||
- Disable percent height/width attributes for img.
|
||||
- AttrValidator operations are now atomic; updates to attributes are not
|
||||
manifest in token until end of operations. This prevents naughty internal
|
||||
code from directly modifying CurrentToken when they're not supposed to.
|
||||
This semantics change was requested by frank farmer.
|
||||
- Percent encoding checks enabled for URI query and fragment
|
||||
- Fix stray backslashes in font-family; CSS Unicode character escapes are
|
||||
now properly resolved (although *only* in font-family). Thanks Takeshi Terada
|
||||
for reporting.
|
||||
- Improve parseCDATA algorithm to take into account newline normalization
|
||||
- Account for browser confusion between Yen character and backslash in
|
||||
Shift_JIS encoding. This fix generalizes to any other encoding which is not
|
||||
a strict superset of printable ASCII. Thanks Takeshi Terada for reporting.
|
||||
- Fix missing configuration parameter in Generator calls. Thanks vs for the
|
||||
partial patch.
|
||||
- Improved adherence to Unicode by checking for non-character codepoints.
|
||||
Thanks Geoffrey Sneddon for reporting. This may result in degraded
|
||||
performance for extremely large inputs.
|
||||
- Allow CSS property-value pair ''text-decoration: none''. Thanks Jochem Blok
|
||||
for reporting.
|
||||
. Added HTMLPurifier_UnitConverter and HTMLPurifier_Length for convenient
|
||||
handling of CSS-style lengths. HTMLPurifier_AttrDef_CSS_Length now uses
|
||||
this class.
|
||||
. API of HTMLPurifier_AttrDef_CSS_Length changed from __construct($disable_negative)
|
||||
to __construct($min, $max). __construct(true) is equivalent to
|
||||
__construct('0').
|
||||
. Added HTMLPurifier_AttrDef_Switch class
|
||||
. Rename HTMLPurifier_HTMLModule_Tidy->construct() to setup() and bubble method
|
||||
up inheritance hierarchy to HTMLPurifier_HTMLModule. All HTMLModules
|
||||
get this called with the configuration object. All modules now
|
||||
use this rather than __construct(), although legacy code using constructors
|
||||
will still work--the new format, however, lets modules access the
|
||||
configuration object for HTML namespace dependant tweaks.
|
||||
. AttrDef_HTML_Pixels now takes a single construction parameter, pixels.
|
||||
. ConfigSchema data-structure heavily optimized; on average it uses a third
|
||||
the memory it did previously. The interface has changed accordingly,
|
||||
consult changes to HTMLPurifier_Config for details.
|
||||
. Variable parsing types now are magic integers instead of strings
|
||||
. Added benchmark for ConfigSchema
|
||||
. HTMLPurifier_Generator requires $config and $context parameters. If you
|
||||
don't know what they should be, use HTMLPurifier_Config::createDefault()
|
||||
and new HTMLPurifier_Context().
|
||||
. Printers now properly distinguish between output configuration, and
|
||||
target configuration. This is not applicable to scripts using
|
||||
the Printers for HTML Purifier related tasks.
|
||||
. HTML/CSS Printers must be primed with prepareGenerator($gen_config), otherwise
|
||||
fatal errors will ensue.
|
||||
. URIFilter->prepare can return false in order to abort loading of the filter
|
||||
. Factory for AttrDef_URI implemented, URI#embedded to indicate URI that embeds
|
||||
an external resource.
|
||||
. %URI.Munge functionality factored out into a post-filter class.
|
||||
. Added CurrentCSSProperty context variable during CSS validation
|
||||
|
||||
3.1.0, released 2008-05-18
|
||||
# Unnecessary references to objects (vestiges of PHP4) removed from method
|
||||
signatures. The following methods do not need references when assigning from
|
||||
them and will result in E_STRICT errors if you try:
|
||||
+ HTMLPurifier_Config->get*Definition() [* = HTML, CSS]
|
||||
+ HTMLPurifier_ConfigSchema::instance()
|
||||
+ HTMLPurifier_DefinitionCacheFactory::instance()
|
||||
+ HTMLPurifier_DefinitionCacheFactory->create()
|
||||
+ HTMLPurifier_DoctypeRegistry->register()
|
||||
+ HTMLPurifier_DoctypeRegistry->get()
|
||||
+ HTMLPurifier_HTMLModule->addElement()
|
||||
+ HTMLPurifier_HTMLModule->addBlankElement()
|
||||
+ HTMLPurifier_LanguageFactory::instance()
|
||||
# Printer_ConfigForm's get*() functions were static-ified
|
||||
# %HTML.ForbiddenAttributes requires attribute declarations to be in the
|
||||
form of tag@attr, NOT tag.attr (which will throw an error and won't do
|
||||
anything). This is for forwards compatibility with XML; you'd do best
|
||||
to migrate an %HTML.AllowedAttributes directives to this syntax too.
|
||||
! Allow index to be false for config from form creation
|
||||
! Added HTMLPurifier::VERSION constant
|
||||
! Commas, not dashes, used for serializer IDs. This change is forwards-compatible
|
||||
and allows for version numbers like "3.1.0-dev".
|
||||
! %HTML.Allowed deals gracefully with whitespace anywhere, anytime!
|
||||
! HTML Purifier's URI handling is a lot more robust, with much stricter
|
||||
validation checks and better percent encoding handling. Thanks Gareth Heyes
|
||||
for indicating security vulnerabilities from lax percent encoding.
|
||||
! Bootstrap autoloader deals more robustly with classes that don't exist,
|
||||
preventing class_exists($class, true) from barfing.
|
||||
- InterchangeBuilder now alphabetizes its lists
|
||||
- Validation error in configdoc output fixed
|
||||
- Iconv and other encoding errors muted even with custom error handlers that
|
||||
do not honor error_reporting
|
||||
- Add protection against imagecrash attack with CSS height/width
|
||||
- HTMLPurifier::instance() created for consistency, is equivalent to getInstance()
|
||||
- Fixed and revamped broken ConfigForm smoketest
|
||||
- Bug with bool/null fields in Printer_ConfigForm fixed
|
||||
- Bug with global forbidden attributes fixed
|
||||
- Improved error messages for allowed and forbidden HTML elements and attributes
|
||||
- Missing (or null) in configdoc documentation restored
|
||||
- If DOM throws and exception during parsing with PH5P (occurs in newer versions
|
||||
of DOM), HTML Purifier punts to DirectLex
|
||||
- Fatal error with unserialization of ScriptRequired
|
||||
- Created directories are now chmod'ed properly
|
||||
- Fixed bug with fallback languages in LanguageFactory
|
||||
- Standalone testing setup properly with autoload
|
||||
. Out-of-date documentation revised
|
||||
. UTF-8 encoding check optimization as suggested by Diego
|
||||
. HTMLPurifier_Error removed in favor of exceptions
|
||||
. More copy() function removed; should use clone instead
|
||||
. More extensive unit tests for HTMLDefinition
|
||||
. assertPurification moved to central harness
|
||||
. HTMLPurifier_Generator accepts $config and $context parameters during
|
||||
instantiation, not runtime
|
||||
. Double-quotes outside of attribute values are now unescaped
|
||||
|
||||
3.1.0rc1, released 2008-04-22
|
||||
# Autoload support added. Internal require_once's removed in favor of an
|
||||
explicit require list or autoloading. To use HTML Purifier,
|
||||
you must now either use HTMLPurifier.auto.php
|
||||
or HTMLPurifier.includes.php; setting the include path and including
|
||||
HTMLPurifier.php is insufficient--in such cases include HTMLPurifier.autoload.php
|
||||
as well to register our autoload handler (or modify your autoload function
|
||||
to check HTMLPurifier_Bootstrap::getPath($class)). You can also use
|
||||
HTMLPurifier.safe-includes.php for a less performance friendly but more
|
||||
user-friendly library load.
|
||||
# HTMLPurifier_ConfigSchema static functions are officially deprecated. Schema
|
||||
information is stored in the ConfigSchema directory, and the
|
||||
maintenance/generate-schema-cache.php generates the schema.ser file, which
|
||||
is now instantiated. Support for userland schema changes coming soon!
|
||||
# HTMLPurifier_Config will now throw E_USER_NOTICE when you use a directive
|
||||
alias; to get rid of these errors just modify your configuration to use
|
||||
the new directive name.
|
||||
# HTMLPurifier->addFilter is deprecated; built-in filters can now be
|
||||
enabled using %Filter.$filter_name or by setting your own filters using
|
||||
%Filter.Custom
|
||||
# Directive-level safety properties superceded in favor of module-level
|
||||
safety. Internal method HTMLModule->addElement() has changed, although
|
||||
the externally visible HTMLDefinition->addElement has *not* changed.
|
||||
! Extra utility classes for testing and non-library operations can
|
||||
be found in extras/. Specifically, these are FSTools and ConfigDoc.
|
||||
You may find a use for these in your own project, but right now they
|
||||
are highly experimental and volatile.
|
||||
! Integration with PHPT allows for automated smoketests
|
||||
! Limited support for proprietary HTML elements, namely <marquee>, sponsored
|
||||
by Chris. You can enable them with %HTML.Proprietary if your client
|
||||
demands them.
|
||||
! Support for !important CSS cascade modifier. By default, this will be stripped
|
||||
from CSS, but you can enable it using %CSS.AllowImportant
|
||||
! Support for display and visibility CSS properties added, set %CSS.AllowTricky
|
||||
to true to use them.
|
||||
! HTML Purifier now has its own Exception hierarchy under HTMLPurifier_Exception.
|
||||
Developer error (not enduser error) can cause these to be triggered.
|
||||
! Experimental kses() wrapper introduced with HTMLPurifier.kses.php
|
||||
! Finally %CSS.AllowedProperties for tweaking allowed CSS properties without
|
||||
mucking around with HTMLPurifier_CSSDefinition
|
||||
! ConfigDoc output has been enhanced with version and deprecation info.
|
||||
! %HTML.ForbiddenAttributes and %HTML.ForbiddenElements implemented.
|
||||
- Autoclose now operates iteratively, i.e. <span><span><div> now has
|
||||
both span tags closed.
|
||||
- Various HTMLPurifier_Config convenience functions now accept another parameter
|
||||
$schema which defines what HTMLPurifier_ConfigSchema to use besides the
|
||||
global default.
|
||||
- Fix bug with trusted script handling in libxml versions later than 2.6.28.
|
||||
- Fix bug in ExtractStyleBlocks with comments in style tags
|
||||
- Fix bug in comment parsing for DirectLex
|
||||
- Flush output now displayed when in command line mode for unit tester
|
||||
- Fix bug with rgb(0, 1, 2) color syntax with spaces inside shorthand syntax
|
||||
- HTMLPurifier_HTMLDefinition->addAttribute can now be called multiple times
|
||||
on the same element without emitting errors.
|
||||
- Fixed fatal error in PH5P lexer with invalid tag names
|
||||
. Plugins now get their own changelogs according to project conventions.
|
||||
. Convert tokens to use instanceof, reducing memory footprint and
|
||||
improving comparison speed.
|
||||
. Dry runs now supported in SimpleTest; testing facilities improved
|
||||
. Bootstrap class added for handling autoloading functionality
|
||||
. Implemented recursive glob at FSTools->globr
|
||||
. ConfigSchema now has instance methods for all corresponding define*
|
||||
static methods.
|
||||
. A couple of new historical maintenance scripts were added.
|
||||
. HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php split into two files
|
||||
. tests/index.php can now be run from any directory.
|
||||
. HTMLPurifier_Token subclasses split into seperate files
|
||||
. HTMLPURIFIER_PREFIX now is defined in Bootstrap.php, NOT HTMLPurifier.php
|
||||
. HTMLPURIFIER_PREFIX can now be defined outside of HTML Purifier
|
||||
. New --php=php flag added, allows PHP executable to be specified (command
|
||||
line only!)
|
||||
. htmlpurifier_add_test() preferred method to translate test files in to
|
||||
classes, because it handles PHPT files too.
|
||||
. Debugger class is deprecated and will be removed soon.
|
||||
. Command line argument parsing for testing scripts revamped, now --opt value
|
||||
format is supported.
|
||||
. Smoketests now cleanup after magic quotes
|
||||
. Generator now can output comments (however, comments are still stripped
|
||||
from HTML Purifier output)
|
||||
. HTMLPurifier_ConfigSchema->validate() deprecated in favor of
|
||||
HTMLPurifier_VarParser->parse()
|
||||
. Integers auto-cast into float type by VarParser.
|
||||
. HTMLPURIFIER_STRICT removed; no validation is performed on runtime, only
|
||||
during cache generation
|
||||
. Reordered script calls in maintenance/flush.php
|
||||
. Command line scripts now honor exit codes
|
||||
. When --flush fails in unit testers, abort tests and print message
|
||||
. Improved documentation in docs/dev-flush.html about the maintenance scripts
|
||||
. copy() methods removed in favor of clone keyword
|
||||
|
||||
3.0.0, released 2008-01-06
|
||||
# HTML Purifier is PHP 5 only! The 2.1.x branch will be maintained
|
||||
until PHP 4 is completely deprecated, but no new features will be added
|
||||
to it.
|
||||
+ Visibility declarations added
|
||||
+ Constructor methods renamed to __construct()
|
||||
+ PHP4 reference cruft removed (in progress)
|
||||
! CSS properties are now case-insensitive
|
||||
! DefinitionCacheFactory now can register new implementations
|
||||
! New HTMLPurifier_Filter_ExtractStyleBlocks for extracting <style> from
|
||||
documents and cleaning their contents up. Requires the CSSTidy library
|
||||
<http://csstidy.sourceforge.net/>. You can access the blocks with the
|
||||
'StyleBlocks' Context variable ($purifier->context->get('StyleBlocks')).
|
||||
The output CSS can also be "scoped" for a specific element, use:
|
||||
%Filter.ExtractStyleBlocksScope
|
||||
! Experimental support for some proprietary CSS attributes allowed:
|
||||
opacity (and all of the browser-specific equivalents) and scrollbar colors.
|
||||
Enable by setting %CSS.Proprietary to true.
|
||||
- Colors missing # but in hex form will be corrected
|
||||
- CSS Number algorithm improved
|
||||
- Unit testing and multi-testing now on steroids: command lines,
|
||||
XML output, and other goodies now added.
|
||||
. Unit tests for Injector improved
|
||||
. New classes:
|
||||
+ HTMLPurifier_AttrDef_CSS_AlphaValue
|
||||
+ HTMLPurifier_AttrDef_CSS_Filter
|
||||
. Multitest now has a file docblock
|
||||
|
||||
2.1.3, released 2007-11-05
|
||||
! tests/multitest.php allows you to test multiple versions by running
|
||||
tests/index.php through multiple interpreters using `phpv` shell
|
||||
script (you must provide this script!)
|
||||
- Fixed poor include ordering for Email URI AttrDefs, causes fatal errors
|
||||
on some systems.
|
||||
- Injector algorithm further refined: off-by-one error regarding skip
|
||||
counts for dormant injectors fixed
|
||||
- Corrective blockquote definition now enabled for HTML 4.01 Strict
|
||||
- Fatal error when <img> tag (or any other element with required attributes)
|
||||
has 'id' attribute fixed, thanks NykO18 for reporting
|
||||
- Fix warning emitted when a non-supported URI scheme is passed to the
|
||||
MakeAbsolute URIFilter, thanks NykO18 (again)
|
||||
- Further refine AutoParagraph injector. Behavior inside of elements
|
||||
allowing paragraph tags clarified: only inline content delimeted by
|
||||
double newlines (not block elements) are paragraphed.
|
||||
- Buggy treatment of end tags of elements that have required attributes
|
||||
fixed (does not manifest on default tag-set)
|
||||
- Spurious internal content reorganization error suppressed
|
||||
- HTMLDefinition->addElement now returns a reference to the created
|
||||
element object, as implied by the documentation
|
||||
- Phorum mod's HTML Purifier help message expanded (unreleased elsewhere)
|
||||
- Fix a theoretical class of infinite loops from DirectLex reported
|
||||
by Nate Abele
|
||||
- Work around unnecessary DOMElement type-cast in PH5P that caused errors
|
||||
in PHP 5.1
|
||||
- Work around PHP 4 SimpleTest lack-of-error complaining for one-time-only
|
||||
HTMLDefinition errors, this may indicate problems with error-collecting
|
||||
facilities in PHP 5
|
||||
- Make ErrorCollectorEMock work in both PHP 4 and PHP 5
|
||||
- Make PH5P work with PHP 5.0 by removing unnecessary array parameter typedef
|
||||
. %Core.AcceptFullDocuments renamed to %Core.ConvertDocumentToFragment
|
||||
to better communicate its purpose
|
||||
. Error unit tests can now specify the expectation of no errors. Future
|
||||
iterations of the harness will be extremely strict about what errors
|
||||
are allowed
|
||||
. Extend Injector hooks to allow for more powerful injector routines
|
||||
. HTMLDefinition->addBlankElement created, as according to the HTMLModule
|
||||
method
|
||||
. Doxygen configuration file updated, with minor improvements
|
||||
. Test runner now checks for similarly named files in conf/ directory too.
|
||||
. Minor cosmetic change to flush-definition-cache.php: trailing newline is
|
||||
outputted
|
||||
. Maintenance script for generating PH5P patch added, original PH5P source
|
||||
file also added under version control
|
||||
. Full unit test runner script title made more descriptive with PHP version
|
||||
. Updated INSTALL file to state that 4.3.7 is the earliest version we
|
||||
are actively testing
|
||||
|
||||
2.1.2, released 2007-09-03
|
||||
! Implemented Object module for trusted users
|
||||
! Implemented experimental HTML5 parsing mode using PH5P. To use, add
|
||||
this to your code:
|
||||
require_once 'HTMLPurifier/Lexer/PH5P.php';
|
||||
$config->set('Core', 'LexerImpl', 'PH5P');
|
||||
Note that this Lexer introduces some classes not in the HTMLPurifier
|
||||
namespace. Also, this is PHP5 only.
|
||||
! CSS property border-spacing implemented
|
||||
- Fix non-visible parsing error in DirectLex with empty tags that have
|
||||
slashes inside attribute values.
|
||||
- Fix typo in CSS definition: border-collapse:seperate; was incorrectly
|
||||
accepted as valid CSS. Usually non-visible, because this styling is the
|
||||
default for tables in most browsers. Thanks Brett Zamir for pointing
|
||||
this out.
|
||||
- Fix validation errors in configuration form
|
||||
- Hammer out a bunch of edge-case bugs in the standalone distribution
|
||||
- Inclusion reflection removed from URISchemeRegistry; you must manually
|
||||
include any new schema files you wish to use
|
||||
- Numerous typo fixes in documentation thanks to Brett Zamir
|
||||
. Unit test refactoring for one logical test per test function
|
||||
. Config and context parameters in ComplexHarness deprecated: instead, edit
|
||||
the $config and $context member variables
|
||||
. HTML wrapper in DOMLex now takes DTD identifiers into account; doesn't
|
||||
really make a difference, but is good for completeness sake
|
||||
. merge-library.php script refactored for greater code reusability and
|
||||
PHP4 compatibility
|
||||
|
||||
2.1.1, released 2007-08-04
|
||||
- Fix show-stopper bug in %URI.MakeAbsolute functionality
|
||||
- Fix PHP4 syntax error in standalone version
|
||||
. Add prefix directory to include path for standalone, this prevents
|
||||
other installations from clobbering the standalone's URI schemes
|
||||
. Single test methods can be invoked by prefixing with __only
|
||||
|
||||
2.1.0, released 2007-08-02
|
||||
# flush-htmldefinition-cache.php superseded in favor of a generic
|
||||
flush-definition-cache.php script, you can clear a specific cache
|
||||
by passing its name as a parameter to the script
|
||||
! Phorum mod implemented for HTML Purifier
|
||||
! With %Core.AggressivelyFixLt, <3 and similar emoticons no longer
|
||||
trigger HTML removal in PHP5 (DOMLex). This directive is not necessary
|
||||
for PHP4 (DirectLex).
|
||||
! Standalone file now available, which greatly reduces the amount of
|
||||
includes (although there are still a few files that reside in the
|
||||
standalone folder)
|
||||
! Relative URIs can now be transformed into their absolute equivalents
|
||||
using %URI.Base and %URI.MakeAbsolute
|
||||
! Ruby implemented for XHTML 1.1
|
||||
! You can now define custom URI filtering behavior, see enduser-uri-filter.html
|
||||
for more details
|
||||
! UTF-8 font names now supported in CSS
|
||||
- AutoFormatters emit friendly error messages if tags or attributes they
|
||||
need are not allowed
|
||||
- ConfigForm's compactification of directive names is now configurable
|
||||
- AutoParagraph autoformatter algorithm refined after field-testing
|
||||
- XHTML 1.1 now applies XHTML 1.0 Strict cleanup routines, namely
|
||||
blockquote wrapping
|
||||
- Contents of <style> tags removed by default when tags are removed
|
||||
. HTMLPurifier_Config->getSerial() implemented, this is extremely useful
|
||||
for output cache invalidation
|
||||
. ConfigForm printer now can retrieve CSS and JS files as strings, in
|
||||
case HTML Purifier's directory is not publically accessible
|
||||
. Introduce new text/itext configuration directive values: these represent
|
||||
longer strings that would be more appropriately edited with a textarea
|
||||
. Allow newlines to act as separators for lists, hashes, lookups and
|
||||
%HTML.Allowed
|
||||
. ConfigForm generates textareas instead of text inputs for lists, hashes,
|
||||
lookups, text and itext fields
|
||||
. Hidden element content removal genericized: %Core.HiddenElements can
|
||||
be used to customize this behavior, by default <script> and <style> are
|
||||
hidden
|
||||
. Added HTMLPURIFIER_PREFIX constant, should be used instead of dirname(__FILE__)
|
||||
. Custom ChildDef added to default include list
|
||||
. URIScheme reflection improved: will not attempt to include file if class
|
||||
already exists. May clobber autoload, so I need to keep an eye on it
|
||||
. ConfigSchema heavily optimized, will only collect information and validate
|
||||
definitions when HTMLPURIFIER_SCHEMA_STRICT is true.
|
||||
. AttrDef_URI unit tests and implementation refactored
|
||||
. benchmarks/ directory now protected from public view with .htaccess file;
|
||||
run the tests via command line
|
||||
. URI scheme is munged off if there is no authority and the scheme is the
|
||||
default one
|
||||
. All unit tests inherit from HTMLPurifier_Harness, not UnitTestCase
|
||||
. Interface for URIScheme changed
|
||||
. Generic URI object to hold components of URI added, most systems involved
|
||||
in URI validation have been migrated to use it
|
||||
. Custom filtering for URIs factored out to URIDefinition interface for
|
||||
maximum extensibility
|
||||
|
||||
2.0.1, released 2007-06-27
|
||||
! Tag auto-closing now based on a ChildDef heuristic rather than a
|
||||
manually set auto_close array; some behavior may change
|
||||
! Experimental AutoFormat functionality added: auto-paragraph and
|
||||
linkify your HTML input by setting %AutoFormat.AutoParagraph and
|
||||
%AutoFormat.Linkify to true
|
||||
! Newlines normalized internally, and then converted back to the
|
||||
value of PHP_EOL. If this is not desired, set your newline format
|
||||
using %Output.Newline.
|
||||
! Beta error collection, messages are implemented for the most generic
|
||||
cases involving Lexing or Strategies
|
||||
- Clean up special case code for <script> tags
|
||||
- Reorder includes for DefinitionCache decorators, fixes a possible
|
||||
missing class error
|
||||
- Fixed bug where manually modified definitions were not saved via cache
|
||||
(mostly harmless, except for the fact that it would be a little slower)
|
||||
- Configuration objects with different serials do not clobber each
|
||||
others when revision numbers are unequal
|
||||
- Improve Serializer DefinitionCache directory permissions checks
|
||||
- DefinitionCache no longer throws errors when it encounters old
|
||||
serial files that do not conform to the current style
|
||||
- Stray xmlns attributes removed from configuration documentation
|
||||
- configForm.php smoketest no longer has XSS vulnerability due to
|
||||
unescaped print_r output
|
||||
- Printer adheres to configuration's directives on output format
|
||||
- Fix improperly named form field in ConfigForm printer
|
||||
. Rewire some test-cases to swallow errors rather than expect them
|
||||
. HTMLDefinition printer updated with some of the new attributes
|
||||
. DefinitionCache keys reordered to reflect precedence: version number,
|
||||
hash, then revision number
|
||||
. %Core.DefinitionCache renamed to %Cache.DefinitionImpl
|
||||
. Interlinking in configuration documentation added using
|
||||
Injector_PurifierLinkify
|
||||
. Directives now keep track of aliases to themselves
|
||||
. Error collector now requires a severity to be passed, use PHP's internal
|
||||
error constants for this
|
||||
. HTMLPurifier_Config::getAllowedDirectivesForForm implemented, allows
|
||||
much easier selective embedding of configuration values
|
||||
. Doctype objects now accept public and system DTD identifiers
|
||||
. %HTML.Doctype is now constrained by specific values, to specify a custom
|
||||
doctype use new %HTML.CustomDoctype
|
||||
. ConfigForm truncates long directives to keep the form small, and does
|
||||
not re-output namespaces
|
||||
|
||||
2.0.0, released 2007-06-20
|
||||
# Completely refactored HTMLModuleManager, decentralizing safety
|
||||
information
|
||||
# Transform modules changed to Tidy modules, which offer more flexibility
|
||||
and better modularization
|
||||
# Configuration object now finalizes itself when a read operation is
|
||||
performed on it, ensuring that its internal state stays consistent.
|
||||
To revert this behavior, you can set the $autoFinalize member variable
|
||||
off, but it's not recommended.
|
||||
# New compact syntax for AttrDef objects that can be used to instantiate
|
||||
new objects via make()
|
||||
# Definitions (esp. HTMLDefinition) are now cached for a significant
|
||||
performance boost. You can disable caching by setting %Core.DefinitionCache
|
||||
to null. You CANNOT edit raw definitions without setting the corresponding
|
||||
DefinitionID directive (%HTML.DefinitionID for HTMLDefinition).
|
||||
# Contents between <script> tags are now completely removed if <script>
|
||||
is not allowed
|
||||
# Prototype-declarations for Lexer removed in favor of configuration
|
||||
determination of Lexer implementations.
|
||||
! HTML Purifier now works in PHP 4.3.2.
|
||||
! Configuration form-editing API makes tweaking HTMLPurifier_Config a
|
||||
breeze!
|
||||
! Configuration directives that accept hashes now allow new string
|
||||
format: key1:value1,key2:value2
|
||||
! ConfigDoc now factored into OOP design
|
||||
! All deprecated elements now natively supported
|
||||
! Implement TinyMCE styled whitelist specification format in
|
||||
%HTML.Allowed
|
||||
! Config object gives more friendly error messages when things go wrong
|
||||
! Advanced API implemented: easy functions for creating elements (addElement)
|
||||
and attributes (addAttribute) on HTMLDefinition
|
||||
! Add native support for required attributes
|
||||
- Deprecated and removed EnableRedundantUTF8Cleaning. It didn't even work!
|
||||
- DOMLex will not emit errors when a custom error handler that does not
|
||||
honor error_reporting is used
|
||||
- StrictBlockquote child definition refrains from wrapping whitespace
|
||||
in tags now.
|
||||
- Bug resulting from tag transforms to non-allowed elements fixed
|
||||
- ChildDef_Custom's regex generation has been improved, removing several
|
||||
false positives
|
||||
. Unit test for ElementDef created, ElementDef behavior modified to
|
||||
be more flexible
|
||||
. Added convenience functions for HTMLModule constructors
|
||||
. AttrTypes now has accessor functions that should be used instead
|
||||
of directly manipulating info
|
||||
. TagTransform_Center deprecated in favor of generic TagTransform_Simple
|
||||
. Add extra protection in AttrDef_URI against phantom Schemes
|
||||
. Doctype object added to HTMLDefinition which describes certain aspects
|
||||
of the operational document type
|
||||
. Lexer is now pre-emptively included, with a conditional include for the
|
||||
PHP5 only version.
|
||||
. HTMLDefinition and CSSDefinition have a common parent class: Definition.
|
||||
. DirectLex can now track line-numbers
|
||||
. Preliminary error collector is in place, although no code actually reports
|
||||
errors yet
|
||||
. Factor out most of ValidateAttributes to new AttrValidator class
|
||||
|
||||
1.6.1, released 2007-05-05
|
||||
! Support for more deprecated attributes via transformations:
|
||||
+ hspace and vspace in img
|
||||
+ size and noshade in hr
|
||||
+ nowrap in td
|
||||
+ clear in br
|
||||
+ align in caption, table, img and hr
|
||||
+ type in ul, ol and li
|
||||
! DirectLex now preserves text in which a < bracket is followed by
|
||||
a non-alphanumeric character. This means that certain emoticons
|
||||
are now preserved.
|
||||
! %Core.RemoveInvalidImg is now operational, when set to false invalid
|
||||
images will hang around with an empty src
|
||||
! target attribute in a tag supported, use %Attr.AllowedFrameTargets
|
||||
to enable
|
||||
! CSS property white-space now allows nowrap (supported in all modern
|
||||
browsers) but not others (which have spotty browser implementations)
|
||||
! XHTML 1.1 mode now sort-of works without any fatal errors, and
|
||||
lang is now moved over to xml:lang.
|
||||
! Attribute transformation smoketest available at smoketests/attrTransform.php
|
||||
! Transformation of font's size attribute now handles super-large numbers
|
||||
- Possibly fatal bug with __autoload() fixed in module manager
|
||||
- Invert HTMLModuleManager->addModule() processing order to check
|
||||
prefixes first and then the literal module
|
||||
- Empty strings get converted to empty arrays instead of arrays with
|
||||
an empty string in them.
|
||||
- Merging in attribute lists now works.
|
||||
. Demo script removed: it has been added to the website's repository
|
||||
. Basic.php script modified to work out of the box
|
||||
. Refactor AttrTransform classes to reduce duplication
|
||||
. AttrTransform_TextAlign axed in favor of a more general
|
||||
AttrTransform_EnumToCSS, refer to HTMLModule/TransformToStrict.php to
|
||||
see how the new equivalent is implemented
|
||||
. Unit tests now use exclusively assertIdentical
|
||||
|
||||
1.6.0, released 2007-04-01
|
||||
! Support for most common deprecated attributes via transformations:
|
||||
+ bgcolor in td, th, tr and table
|
||||
+ border in img
|
||||
+ name in a and img
|
||||
+ width in td, th and hr
|
||||
+ height in td, th
|
||||
! Support for CSS attribute 'height' added
|
||||
! Support for rel and rev attributes in a tags added, use %Attr.AllowedRel
|
||||
and %Attr.AllowedRev to activate
|
||||
- You can define ID blacklists using regular expressions via
|
||||
%Attr.IDBlacklistRegexp
|
||||
- Error messages are emitted when you attempt to "allow" elements or
|
||||
attributes that HTML Purifier does not support
|
||||
- Fix segfault in unit test. The problem is not very reproduceable and
|
||||
I don't know what causes it, but a six line patch fixed it.
|
||||
|
||||
1.5.0, released 2007-03-23
|
||||
! Added a rudimentary I18N and L10N system modeled off MediaWiki. It
|
||||
doesn't actually do anything yet, but keep your eyes peeled.
|
||||
! docs/enduser-utf8.html explains how to use UTF-8 and HTML Purifier
|
||||
! Newly structured HTMLDefinition modeled off of XHTML 1.1 modules.
|
||||
I am loathe to release beta quality APIs, but this is exactly that;
|
||||
don't use the internal interfaces if you're not willing to do migration
|
||||
later on.
|
||||
- Allow 'x' subtag in language codes
|
||||
- Fixed buggy chameleon-support for ins and del
|
||||
. Added support for IDREF attributes (i.e. for)
|
||||
. Renamed HTMLPurifier_AttrDef_Class to HTMLPurifier_AttrDef_Nmtokens
|
||||
. Removed context variable ParentType, replaced with IsInline, which
|
||||
is false when you're not inline and an integer of the parent that
|
||||
caused you to become inline when you are (so possibly zero)
|
||||
. Removed ElementDef->type in favor of ElementDef->descendants_are_inline
|
||||
and HTMLDefinition->content_sets
|
||||
. StrictBlockquote now reports what elements its supposed to allow,
|
||||
rather than what it does allow
|
||||
. Removed HTMLDefinition->info_flow_elements in favor of
|
||||
HTMLDefinition->content_sets['Flow']
|
||||
. Removed redundant "exclusionary" definitions from DTD roster
|
||||
. StrictBlockquote now requires a construction parameter as if it
|
||||
were an Required ChildDef, this is the "real" set of allowed elements
|
||||
. AttrDef partitioned into HTML, CSS and URI segments
|
||||
. Modify Youtube filter regexp to be multiline
|
||||
. Require both PHP5 and DOM extension in order to use DOMLex, fixes
|
||||
some edge cases where a DOMDocument class exists in a PHP4 environment
|
||||
due to DOM XML extension.
|
||||
|
||||
1.4.1, released 2007-01-21
|
||||
! docs/enduser-youtube.html updated according to new functionality
|
||||
- YouTube IDs can have underscores and dashes
|
||||
|
||||
1.4.0, released 2007-01-21
|
||||
! Implemented list-style-image, URIs now allowed in list-style
|
||||
! Implemented background-image, background-repeat, background-attachment
|
||||
and background-position CSS properties. Shorthand property background
|
||||
supports all of these properties.
|
||||
! Configuration documentation looks nicer
|
||||
! Added %Core.EscapeNonASCIICharacters to workaround loss of Unicode
|
||||
characters while %Core.Encoding is set to a non-UTF-8 encoding.
|
||||
! Support for configuration directive aliases added
|
||||
! Config object can now be instantiated from ini files
|
||||
! YouTube preservation code added to the core, with two lines of code
|
||||
you can add it as a filter to your code. See smoketests/preserveYouTube.php
|
||||
for sample code.
|
||||
! Moved SLOW to docs/enduser-slow.html and added code examples
|
||||
- Replaced version check with functionality check for DOM (thanks Stephen
|
||||
Khoo)
|
||||
. Added smoketest 'all.php', which loads all other smoketests via frames
|
||||
. Implemented AttrDef_CSSURI for url(http://google.com) style declarations
|
||||
. Added convenient single test selector form on test runner
|
||||
|
||||
1.3.2, released 2006-12-25
|
||||
! HTMLPurifier object now accepts configuration arrays, no need to manually
|
||||
instantiate a configuration object
|
||||
! Context object now accessible to outside
|
||||
! Added enduser-youtube.html, explains how to embed YouTube videos. See
|
||||
also corresponding smoketest preserveYouTube.php.
|
||||
! Added purifyArray(), which takes a list of HTML and purifies it all
|
||||
! Added static member variable $version to HTML Purifier with PHP-compatible
|
||||
version number string.
|
||||
- Fixed fatal error thrown by upper-cased language attributes
|
||||
- printDefinition.php: added labels, added better clarification
|
||||
. HTMLPurifier_Config::create() added, takes mixed variable and converts into
|
||||
a HTMLPurifier_Config object.
|
||||
|
||||
1.3.1, released 2006-12-06
|
||||
! Added HTMLPurifier.func.php stub for a convenient function to call the library
|
||||
- Fixed bug in RemoveInvalidImg code that caused all images to be dropped
|
||||
(thanks to .mario for reporting this)
|
||||
. Standardized all attribute handling variables to attr, made it plural
|
||||
|
||||
1.3.0, released 2006-11-26
|
||||
# Invalid images are now removed, rather than replaced with a dud
|
||||
<img src="" alt="Invalid image" />. Previous behavior can be restored
|
||||
with new directive %Core.RemoveInvalidImg set to false.
|
||||
! (X)HTML Strict now supported
|
||||
+ Transparently handles inline elements in block context (blockquote)
|
||||
! Added GET method to demo for easier validation, added 50kb max input size
|
||||
! New directive %HTML.BlockWrapper, for block-ifying inline elements
|
||||
! New directive %HTML.Parent, allows you to only allow inline content
|
||||
! New directives %HTML.AllowedElements and %HTML.AllowedAttributes to let
|
||||
users narrow the set of allowed tags
|
||||
! <li value="4"> and <ul start="2"> now allowed in loose mode
|
||||
! New directives %URI.DisableExternalResources and %URI.DisableResources
|
||||
! New directive %Attr.DisableURI, which eliminates all hyperlinking
|
||||
! New directive %URI.Munge, munges URI so you can use some sort of redirector
|
||||
service to avoid PageRank leaks or warn users that they are exiting your site.
|
||||
! Added spiffy new smoketest printDefinition.php, which lets you twiddle with
|
||||
the configuration settings and see how the internal rules are affected.
|
||||
! New directive %URI.HostBlacklist for blocking links to bad hosts.
|
||||
xssAttacks.php smoketest updated accordingly.
|
||||
- Added missing type to ChildDef_Chameleon
|
||||
- Remove Tidy option from demo if there is not Tidy available
|
||||
. ChildDef_Required guards against empty tags
|
||||
. Lookup table HTMLDefinition->info_flow_elements added
|
||||
. Added peace-of-mind variable initialization to Strategy_FixNesting
|
||||
. Added HTMLPurifier->info_parent_def, parent child processing made special
|
||||
. Added internal documents briefly summarizing future progression of HTML
|
||||
. HTMLPurifier_Config->getBatch($namespace) added
|
||||
. More lenient casting to bool from string in HTMLPurifier_ConfigSchema
|
||||
. Refactored ChildDef classes into their own files
|
||||
|
||||
1.2.0, released 2006-11-19
|
||||
# ID attributes now disabled by default. New directives:
|
||||
+ %HTML.EnableAttrID - restores old behavior by allowing IDs
|
||||
+ %Attr.IDPrefix - %Attr.IDBlacklist alternative that munges all user IDs
|
||||
so that they don't collide with your IDs
|
||||
+ %Attr.IDPrefixLocal - Same as above, but for when there are multiple
|
||||
instances of user content on the page
|
||||
+ Profuse documentation on how to use these available in docs/enduser-id.txt
|
||||
! Added MODx plugin <http://modxcms.com/forums/index.php/topic,6604.0.html>
|
||||
! Added percent encoding normalization
|
||||
! XSS attacks smoketest given facelift
|
||||
! Configuration documentation now has table of contents
|
||||
! Added %URI.DisableExternal, which prevents links to external websites. You
|
||||
can also use %URI.Host to permit absolute linking to subdomains
|
||||
! Non-accessible resources (ex. mailto) blocked from embedded URIs (img src)
|
||||
- Type variable in HTMLDefinition was not being set properly, fixed
|
||||
- Documentation updated
|
||||
+ TODO added request Phalanger
|
||||
+ TODO added request Native compression
|
||||
+ TODO added request Remove redundant tags
|
||||
+ TODO added possible plaintext formatter for HTML Purifier documentation
|
||||
+ Updated ConfigDoc TODO
|
||||
+ Improved inline comments in AttrDef/Class.php, AttrDef/CSS.php
|
||||
and AttrDef/Host.php
|
||||
+ Revamped documentation into HTML, along with misc updates
|
||||
- HTMLPurifier_Context doesn't throw a variable reference error if you attempt
|
||||
to retrieve a non-existent variable
|
||||
. Switched to purify()-wide Context object registry
|
||||
. Refactored unit tests to minimize duplication
|
||||
. XSS attack sheet updated
|
||||
. configdoc.xml now has xml:space attached to default value nodes
|
||||
. Allow configuration directives to permit null values
|
||||
. Cleaned up test-cases to remove unnecessary swallowErrors()
|
||||
|
||||
1.1.2, released 2006-09-30
|
||||
! Add HTMLPurifier.auto.php stub file that configures include_path
|
||||
- Documentation updated
|
||||
+ INSTALL document rewritten
|
||||
+ TODO added semi-lossy conversion
|
||||
+ API Doxygen docs' file exclusions updated
|
||||
+ Added notes on HTML versus XML attribute whitespace handling
|
||||
+ Noted that HTMLPurifier_ChildDef_Custom isn't being used
|
||||
+ Noted that config object's definitions are cached versions
|
||||
- Fixed lack of attribute parsing in HTMLPurifier_Lexer_PEARSax3
|
||||
- ftp:// URIs now have their typecodes checked
|
||||
- Hooked up HTMLPurifier_ChildDef_Custom's unit tests (they weren't being run)
|
||||
. Line endings standardized throughout project (svn:eol-style standardized)
|
||||
. Refactored parseData() to general Lexer class
|
||||
. Tester named "HTML Purifier" not "HTMLPurifier"
|
||||
|
||||
1.1.1, released 2006-09-24
|
||||
! Configuration option to optionally Tidy up output for indentation to make up
|
||||
for dropped whitespace by DOMLex (pretty-printing for the entire application
|
||||
should be done by a page-wide Tidy)
|
||||
- Various documentation updates
|
||||
- Fixed parse error in configuration documentation script
|
||||
- Fixed fatal error in benchmark scripts, slightly augmented
|
||||
- As far as possible, whitespace is preserved in-between table children
|
||||
- Sample test-settings.php file included
|
||||
|
||||
1.1.0, released 2006-09-16
|
||||
! Directive documentation generation using XSLT
|
||||
! XHTML can now be turned off, output becomes <br>
|
||||
- Made URI validator more forgiving: will ignore leading and trailing
|
||||
quotes, apostrophes and less than or greater than signs.
|
||||
- Enforce alphanumeric namespace and directive names for configuration.
|
||||
- Table child definition made more flexible, will fix up poorly ordered elements
|
||||
. Renamed ConfigDef to ConfigSchema
|
||||
|
||||
1.0.1, released 2006-09-04
|
||||
- Fixed slight bug in DOMLex attribute parsing
|
||||
- Fixed rejection of case-insensitive configuration values when there is a
|
||||
set of allowed values. This manifested in %Core.Encoding.
|
||||
- Fixed rejection of inline style declarations that had lots of extra
|
||||
space in them. This manifested in TinyMCE.
|
||||
|
||||
1.0.0, released 2006-09-01
|
||||
! Shorthand CSS properties implemented: font, border, background, list-style
|
||||
! Basic color keywords translated into hexadecimal values
|
||||
! Table CSS properties implemented
|
||||
! Support for charsets other than UTF-8 (defined by iconv)
|
||||
! Malformed UTF-8 and non-SGML character detection and cleaning implemented
|
||||
- Fixed broken numeric entity conversion
|
||||
- API documentation completed
|
||||
. (HTML|CSS)Definition de-singleton-ized
|
||||
|
||||
1.0.0beta, released 2006-08-16
|
||||
! First public release, most functionality implemented. Notable omissions are:
|
||||
+ Shorthand CSS properties
|
||||
+ Table CSS properties
|
||||
+ Deprecated attribute transformations
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
24
library/ezyang/htmlpurifier/README
Normal file
24
library/ezyang/htmlpurifier/README
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
README
|
||||
All about HTML Purifier
|
||||
|
||||
HTML Purifier is an HTML filtering solution that uses a unique combination
|
||||
of robust whitelists and agressive parsing to ensure that not only are
|
||||
XSS attacks thwarted, but the resulting HTML is standards compliant.
|
||||
|
||||
HTML Purifier is oriented towards richly formatted documents from
|
||||
untrusted sources that require CSS and a full tag-set. This library can
|
||||
be configured to accept a more restrictive set of tags, but it won't be
|
||||
as efficient as more bare-bones parsers. It will, however, do the job
|
||||
right, which may be more important.
|
||||
|
||||
Places to go:
|
||||
|
||||
* See INSTALL for a quick installation guide
|
||||
* See docs/ for developer-oriented documentation, code examples and
|
||||
an in-depth installation guide.
|
||||
* See WYSIWYG for information on editors like TinyMCE and FCKeditor
|
||||
|
||||
HTML Purifier can be found on the web at: http://htmlpurifier.org/
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
150
library/ezyang/htmlpurifier/TODO
Normal file
150
library/ezyang/htmlpurifier/TODO
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
TODO List
|
||||
|
||||
= KEY ====================
|
||||
# Flagship
|
||||
- Regular
|
||||
? Maybe I'll Do It
|
||||
==========================
|
||||
|
||||
If no interest is expressed for a feature that may require a considerable
|
||||
amount of effort to implement, it may get endlessly delayed. Do not be
|
||||
afraid to cast your vote for the next feature to be implemented!
|
||||
|
||||
Things to do as soon as possible:
|
||||
|
||||
- http://htmlpurifier.org/phorum/read.php?3,5560,6307#msg-6307
|
||||
- Think about allowing explicit order of operations hooks for transforms
|
||||
- Fix "<.<" bug (trailing < is removed if not EOD)
|
||||
- Build in better internal state dumps and debugging tools for remote
|
||||
debugging
|
||||
- Allowed/Allowed* have strange interactions when both set
|
||||
? Transform lone embeds into object tags
|
||||
- Deprecated config options that emit warnings when you set them (with'
|
||||
a way of muting the warning if you really want to)
|
||||
- Make HTML.Trusted work with Output.FlashCompat
|
||||
- HTML.Trusted and HTML.SafeObject have funny interaction; general
|
||||
problem is what to do when a module "supersedes" another
|
||||
(see also tables and basic tables.) This is a little dicier
|
||||
because HTML.SafeObject has some extra functionality that
|
||||
trusted might find useful. See http://htmlpurifier.org/phorum/read.php?3,5762,6100
|
||||
|
||||
FUTURE VERSIONS
|
||||
---------------
|
||||
|
||||
4.8 release [OMG CONFIG PONIES]
|
||||
! Fix Printer. It's from the old days when we didn't have decent XML classes
|
||||
! Factor demo.php into a set of Printer classes, and then create a stub
|
||||
file for users here (inside the actual HTML Purifier library)
|
||||
- Fix error handling with form construction
|
||||
- Do encoding validation in Printers, or at least, where user data comes in
|
||||
- Config: Add examples to everything (make built-in which also automatically
|
||||
gives output)
|
||||
- Add "register" field to config schemas to eliminate dependence on
|
||||
naming conventions (try to remember why we ultimately decided on tihs)
|
||||
|
||||
5.0 release [HTML 5]
|
||||
# Swap out code to use html5lib tokenizer and tree-builder
|
||||
! Allow turning off of FixNesting and required attribute insertion
|
||||
|
||||
5.1 release [It's All About Trust] (floating)
|
||||
# Implement untrusted, dangerous elements/attributes
|
||||
# Implement IDREF support (harder than it seems, since you cannot have
|
||||
IDREFs to non-existent IDs)
|
||||
- Implement <area> (client and server side image maps are blocking
|
||||
on IDREF support)
|
||||
# Frameset XHTML 1.0 and HTML 4.01 doctypes
|
||||
- Figure out how to simultaneously set %CSS.Trusted and %HTML.Trusted (?)
|
||||
|
||||
5.2 release [Error'ed]
|
||||
# Error logging for filtering/cleanup procedures
|
||||
# Additional support for poorly written HTML
|
||||
- Microsoft Word HTML cleaning (i.e. MsoNormal, but research essential!)
|
||||
- Friendly strict handling of <address> (block -> <br>)
|
||||
- XSS-attempt detection--certain errors are flagged XSS-like
|
||||
- Append something to duplicate IDs so they're still usable (impl. note: the
|
||||
dupe detector would also need to detect the suffix as well)
|
||||
|
||||
6.0 release [Beyond HTML]
|
||||
# Legit token based CSS parsing (will require revamping almost every
|
||||
AttrDef class). Probably will use CSSTidy
|
||||
# More control over allowed CSS properties using a modularization
|
||||
# IRI support (this includes IDN)
|
||||
- Standardize token armor for all areas of processing
|
||||
|
||||
7.0 release [To XML and Beyond]
|
||||
- Extended HTML capabilities based on namespacing and tag transforms (COMPLEX)
|
||||
- Hooks for adding custom processors to custom namespaced tags and
|
||||
attributes, offer default implementation
|
||||
- Lots of documentation and samples
|
||||
|
||||
Ongoing
|
||||
- More refactoring to take advantage of PHP5's facilities
|
||||
- Refactor unit tests into lots of test methods
|
||||
- Plugins for major CMSes (COMPLEX)
|
||||
- phpBB
|
||||
- Also, a FAQ for extension writers with HTML Purifier
|
||||
|
||||
AutoFormat
|
||||
- Smileys
|
||||
- Syntax highlighting (with GeSHi) with <pre> and possibly <?php
|
||||
- Look at http://drupal.org/project/Modules/category/63 for ideas
|
||||
|
||||
Neat feature related
|
||||
! Support exporting configuration, so users can easily tweak settings
|
||||
in the demo, and then copy-paste into their own setup
|
||||
- Advanced URI filtering schemes (see docs/proposal-new-directives.txt)
|
||||
- Allow scoped="scoped" attribute in <style> tags; may be troublesome
|
||||
because regular CSS has no way of uniquely identifying nodes, so we'd
|
||||
have to generate IDs
|
||||
- Explain how to use HTML Purifier in non-PHP languages / create
|
||||
a simple command line stub (or complicated?)
|
||||
- Fixes for Firefox's inability to handle COL alignment props (Bug 915)
|
||||
- Automatically add non-breaking spaces to empty table cells when
|
||||
empty-cells:show is applied to have compatibility with Internet Explorer
|
||||
- Table of Contents generation (XHTML Compiler might be reusable). May also
|
||||
be out-of-band information.
|
||||
- Full set of color keywords. Also, a way to add onto them without
|
||||
finalizing the configuration object.
|
||||
- Write a var_export and memcached DefinitionCache - Denis
|
||||
- Built-in support for target="_blank" on all external links
|
||||
- Convert RTL/LTR override characters to <bdo> tags, or vice versa on demand.
|
||||
Also, enable disabling of directionality
|
||||
? Externalize inline CSS to promote clean HTML, proposed by Sander Tekelenburg
|
||||
? Remove redundant tags, ex. <u><u>Underlined</u></u>. Implementation notes:
|
||||
1. Analyzing which tags to remove duplicants
|
||||
2. Ensure attributes are merged into the parent tag
|
||||
3. Extend the tag exclusion system to specify whether or not the
|
||||
contents should be dropped or not (currently, there's code that could do
|
||||
something like this if it didn't drop the inner text too.)
|
||||
? Make AutoParagraph also support paragraph-izing double <br> tags, and not
|
||||
just double newlines. This is kind of tough to do in the current framework,
|
||||
though, and might be reasonably approximated by search replacing double <br>s
|
||||
with newlines before running it through HTML Purifier.
|
||||
|
||||
Maintenance related (slightly boring)
|
||||
# CHMOD install script for PEAR installs
|
||||
! Factor out command line parser into its own class, and unit test it
|
||||
- Reduce size of internal data-structures (esp. HTMLDefinition)
|
||||
- Allow merging configurations. Thus,
|
||||
a -> b -> default
|
||||
c -> d -> default
|
||||
becomes
|
||||
a -> b -> c -> d -> default
|
||||
Maybe allow more fine-grained tuning of this behavior. Alternatively,
|
||||
encourage people to use short plist depths before building them up.
|
||||
- Time PHPT tests
|
||||
|
||||
ChildDef related (very boring)
|
||||
- Abstract ChildDef_BlockQuote to work with all elements that only
|
||||
allow blocks in them, required or optional
|
||||
- Implement lenient <ruby> child validation
|
||||
|
||||
Wontfix
|
||||
- Non-lossy smart alternate character encoding transformations (unless
|
||||
patch provided)
|
||||
- Pretty-printing HTML: users can use Tidy on the output on entire page
|
||||
- Native content compression, whitespace stripping: use gzip if this is
|
||||
really important
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
1
library/ezyang/htmlpurifier/VERSION
Normal file
1
library/ezyang/htmlpurifier/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
4.7.0
|
||||
4
library/ezyang/htmlpurifier/WHATSNEW
Normal file
4
library/ezyang/htmlpurifier/WHATSNEW
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
HTML Purifier 4.7.0 is a bugfix release, collecting two years
|
||||
worth of accumulated bug fixes. Highlighted bugfixes are updated
|
||||
YouTube filter code, corrected rgb() CSS parsing, and one new
|
||||
configuration option, %AutoFormat.RemoveEmpty.Predicate.
|
||||
20
library/ezyang/htmlpurifier/WYSIWYG
Normal file
20
library/ezyang/htmlpurifier/WYSIWYG
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
WYSIWYG - What You See Is What You Get
|
||||
HTML Purifier: A Pretty Good Fit for TinyMCE and FCKeditor
|
||||
|
||||
Javascript-based WYSIWYG editors, simply stated, are quite amazing. But I've
|
||||
always been wary about using them due to security issues: they handle the
|
||||
client-side magic, but once you've been served a piping hot load of unfiltered
|
||||
HTML, what should be done then? In some situations, you can serve it uncleaned,
|
||||
since you only offer these facilities to trusted(?) authors.
|
||||
|
||||
Unfortunantely, for blog comments and anonymous input, BBCode, Textile and
|
||||
other markup languages still reign supreme. Put simply: filtering HTML is
|
||||
hard work, and these WYSIWYG authors don't offer anything to alleviate that
|
||||
trouble. Therein lies the solution:
|
||||
|
||||
HTML Purifier is perfect for filtering pure-HTML input from WYSIWYG editors.
|
||||
|
||||
Enough said.
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
22
library/ezyang/htmlpurifier/composer.json
Normal file
22
library/ezyang/htmlpurifier/composer.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "ezyang/htmlpurifier",
|
||||
"description": "Standards compliant HTML filter written in PHP",
|
||||
"type": "library",
|
||||
"keywords": ["html"],
|
||||
"homepage": "http://htmlpurifier.org/",
|
||||
"license": "LGPL",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Edward Z. Yang",
|
||||
"email": "admin@htmlpurifier.org",
|
||||
"homepage": "http://ezyang.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "HTMLPurifier": "library/" },
|
||||
"files": ["library/HTMLPurifier.composer.php"]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Decorator/extender XSLT processor specifically for HTML documents.
|
||||
*/
|
||||
class ConfigDoc_HTMLXSLTProcessor
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of XSLTProcessor
|
||||
*/
|
||||
protected $xsltProcessor;
|
||||
|
||||
public function __construct($proc = false)
|
||||
{
|
||||
if ($proc === false) $proc = new XSLTProcessor();
|
||||
$this->xsltProcessor = $proc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @note Allows a string $xsl filename to be passed
|
||||
*/
|
||||
public function importStylesheet($xsl)
|
||||
{
|
||||
if (is_string($xsl)) {
|
||||
$xsl_file = $xsl;
|
||||
$xsl = new DOMDocument();
|
||||
$xsl->load($xsl_file);
|
||||
}
|
||||
return $this->xsltProcessor->importStylesheet($xsl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an XML file into compatible XHTML based on the stylesheet
|
||||
* @param $xml XML DOM tree, or string filename
|
||||
* @return string HTML output
|
||||
* @todo Rename to transformToXHTML, as transformToHTML is misleading
|
||||
*/
|
||||
public function transformToHTML($xml)
|
||||
{
|
||||
if (is_string($xml)) {
|
||||
$dom = new DOMDocument();
|
||||
$dom->load($xml);
|
||||
} else {
|
||||
$dom = $xml;
|
||||
}
|
||||
$out = $this->xsltProcessor->transformToXML($dom);
|
||||
|
||||
// fudges for HTML backwards compatibility
|
||||
// assumes that document is XHTML
|
||||
$out = str_replace('/>', ' />', $out); // <br /> not <br/>
|
||||
$out = str_replace(' xmlns=""', '', $out); // rm unnecessary xmlns
|
||||
|
||||
if (class_exists('Tidy')) {
|
||||
// cleanup output
|
||||
$config = array(
|
||||
'indent' => true,
|
||||
'output-xhtml' => true,
|
||||
'wrap' => 80
|
||||
);
|
||||
$tidy = new Tidy;
|
||||
$tidy->parseString($out, $config, 'utf8');
|
||||
$tidy->cleanRepair();
|
||||
$out = (string) $tidy;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk sets parameters for the XSL stylesheet
|
||||
* @param array $options Associative array of options to set
|
||||
*/
|
||||
public function setParameters($options)
|
||||
{
|
||||
foreach ($options as $name => $value) {
|
||||
$this->xsltProcessor->setParameter('', $name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward any other calls to the XSLT processor
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
call_user_func_array(array($this->xsltProcessor, $name), $arguments);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
164
library/ezyang/htmlpurifier/extras/FSTools.php
Normal file
164
library/ezyang/htmlpurifier/extras/FSTools.php
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Filesystem tools not provided by default; can recursively create, copy
|
||||
* and delete folders. Some template methods are provided for extensibility.
|
||||
*
|
||||
* @note This class must be instantiated to be used, although it does
|
||||
* not maintain state.
|
||||
*/
|
||||
class FSTools
|
||||
{
|
||||
|
||||
private static $singleton;
|
||||
|
||||
/**
|
||||
* Returns a global instance of FSTools
|
||||
*/
|
||||
public static function singleton()
|
||||
{
|
||||
if (empty(FSTools::$singleton)) FSTools::$singleton = new FSTools();
|
||||
return FSTools::$singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets our global singleton to something else; useful for overloading
|
||||
* functions.
|
||||
*/
|
||||
public static function setSingleton($singleton)
|
||||
{
|
||||
FSTools::$singleton = $singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively creates a directory
|
||||
* @param string $folder Name of folder to create
|
||||
* @note Adapted from the PHP manual comment 76612
|
||||
*/
|
||||
public function mkdirr($folder)
|
||||
{
|
||||
$folders = preg_split("#[\\\\/]#", $folder);
|
||||
$base = '';
|
||||
for($i = 0, $c = count($folders); $i < $c; $i++) {
|
||||
if(empty($folders[$i])) {
|
||||
if (!$i) {
|
||||
// special case for root level
|
||||
$base .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$base .= $folders[$i];
|
||||
if(!is_dir($base)){
|
||||
$this->mkdir($base);
|
||||
}
|
||||
$base .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file, or recursively copy a folder and its contents; modified
|
||||
* so that copied files, if PHP, have includes removed
|
||||
* @note Adapted from http://aidanlister.com/repos/v/function.copyr.php
|
||||
*/
|
||||
public function copyr($source, $dest)
|
||||
{
|
||||
// Simple copy for a file
|
||||
if (is_file($source)) {
|
||||
return $this->copy($source, $dest);
|
||||
}
|
||||
// Make destination directory
|
||||
if (!is_dir($dest)) {
|
||||
$this->mkdir($dest);
|
||||
}
|
||||
// Loop through the folder
|
||||
$dir = $this->dir($source);
|
||||
while ( false !== ($entry = $dir->read()) ) {
|
||||
// Skip pointers
|
||||
if ($entry == '.' || $entry == '..') {
|
||||
continue;
|
||||
}
|
||||
if (!$this->copyable($entry)) {
|
||||
continue;
|
||||
}
|
||||
// Deep copy directories
|
||||
if ($dest !== "$source/$entry") {
|
||||
$this->copyr("$source/$entry", "$dest/$entry");
|
||||
}
|
||||
}
|
||||
// Clean up
|
||||
$dir->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloadable function that tests a filename for copyability. By
|
||||
* default, everything should be copied; you can restrict things to
|
||||
* ignore hidden files, unreadable files, etc. This function
|
||||
* applies to copyr().
|
||||
*/
|
||||
public function copyable($file)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file, or a folder and its contents
|
||||
* @note Adapted from http://aidanlister.com/repos/v/function.rmdirr.php
|
||||
*/
|
||||
public function rmdirr($dirname)
|
||||
{
|
||||
// Sanity check
|
||||
if (!$this->file_exists($dirname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simple delete for a file
|
||||
if ($this->is_file($dirname) || $this->is_link($dirname)) {
|
||||
return $this->unlink($dirname);
|
||||
}
|
||||
|
||||
// Loop through the folder
|
||||
$dir = $this->dir($dirname);
|
||||
while (false !== $entry = $dir->read()) {
|
||||
// Skip pointers
|
||||
if ($entry == '.' || $entry == '..') {
|
||||
continue;
|
||||
}
|
||||
// Recurse
|
||||
$this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
$dir->close();
|
||||
return $this->rmdir($dirname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively globs a directory.
|
||||
*/
|
||||
public function globr($dir, $pattern, $flags = NULL)
|
||||
{
|
||||
$files = $this->glob("$dir/$pattern", $flags);
|
||||
if ($files === false) $files = array();
|
||||
$sub_dirs = $this->glob("$dir/*", GLOB_ONLYDIR);
|
||||
if ($sub_dirs === false) $sub_dirs = array();
|
||||
foreach ($sub_dirs as $sub_dir) {
|
||||
$sub_files = $this->globr($sub_dir, $pattern, $flags);
|
||||
$files = array_merge($files, $sub_files);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for PHP functions to be called and be stubbed.
|
||||
* @warning This function will not work for functions that need
|
||||
* to pass references; manually define a stub function for those.
|
||||
*/
|
||||
public function __call($name, $args)
|
||||
{
|
||||
return call_user_func_array($name, $args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
141
library/ezyang/htmlpurifier/extras/FSTools/File.php
Normal file
141
library/ezyang/htmlpurifier/extras/FSTools/File.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Represents a file in the filesystem
|
||||
*
|
||||
* @warning Be sure to distinguish between get() and write() versus
|
||||
* read() and put(), the former operates on the entire file, while
|
||||
* the latter operates on a handle.
|
||||
*/
|
||||
class FSTools_File
|
||||
{
|
||||
|
||||
/** Filename of file this object represents */
|
||||
protected $name;
|
||||
|
||||
/** Handle for the file */
|
||||
protected $handle = false;
|
||||
|
||||
/** Instance of FSTools for interfacing with filesystem */
|
||||
protected $fs;
|
||||
|
||||
/**
|
||||
* Filename of file you wish to instantiate.
|
||||
* @note This file need not exist
|
||||
*/
|
||||
public function __construct($name, $fs = false)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->fs = $fs ? $fs : FSTools::singleton();
|
||||
}
|
||||
|
||||
/** Returns the filename of the file. */
|
||||
public function getName() {return $this->name;}
|
||||
|
||||
/** Returns directory of the file without trailing slash */
|
||||
public function getDirectory() {return $this->fs->dirname($this->name);}
|
||||
|
||||
/**
|
||||
* Retrieves the contents of a file
|
||||
* @todo Throw an exception if file doesn't exist
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->fs->file_get_contents($this->name);
|
||||
}
|
||||
|
||||
/** Writes contents to a file, creates new file if necessary */
|
||||
public function write($contents)
|
||||
{
|
||||
return $this->fs->file_put_contents($this->name, $contents);
|
||||
}
|
||||
|
||||
/** Deletes the file */
|
||||
public function delete()
|
||||
{
|
||||
return $this->fs->unlink($this->name);
|
||||
}
|
||||
|
||||
/** Returns true if file exists and is a file. */
|
||||
public function exists()
|
||||
{
|
||||
return $this->fs->is_file($this->name);
|
||||
}
|
||||
|
||||
/** Returns last file modification time */
|
||||
public function getMTime()
|
||||
{
|
||||
return $this->fs->filemtime($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chmod a file
|
||||
* @note We ignore errors because of some weird owner trickery due
|
||||
* to SVN duality
|
||||
*/
|
||||
public function chmod($octal_code)
|
||||
{
|
||||
return @$this->fs->chmod($this->name, $octal_code);
|
||||
}
|
||||
|
||||
/** Opens file's handle */
|
||||
public function open($mode)
|
||||
{
|
||||
if ($this->handle) $this->close();
|
||||
$this->handle = $this->fs->fopen($this->name, $mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Closes file's handle */
|
||||
public function close()
|
||||
{
|
||||
if (!$this->handle) return false;
|
||||
$status = $this->fs->fclose($this->handle);
|
||||
$this->handle = false;
|
||||
return $status;
|
||||
}
|
||||
|
||||
/** Retrieves a line from an open file, with optional max length $length */
|
||||
public function getLine($length = null)
|
||||
{
|
||||
if (!$this->handle) $this->open('r');
|
||||
if ($length === null) return $this->fs->fgets($this->handle);
|
||||
else return $this->fs->fgets($this->handle, $length);
|
||||
}
|
||||
|
||||
/** Retrieves a character from an open file */
|
||||
public function getChar()
|
||||
{
|
||||
if (!$this->handle) $this->open('r');
|
||||
return $this->fs->fgetc($this->handle);
|
||||
}
|
||||
|
||||
/** Retrieves an $length bytes of data from an open data */
|
||||
public function read($length)
|
||||
{
|
||||
if (!$this->handle) $this->open('r');
|
||||
return $this->fs->fread($this->handle, $length);
|
||||
}
|
||||
|
||||
/** Writes to an open file */
|
||||
public function put($string)
|
||||
{
|
||||
if (!$this->handle) $this->open('a');
|
||||
return $this->fs->fwrite($this->handle, $string);
|
||||
}
|
||||
|
||||
/** Returns TRUE if the end of the file has been reached */
|
||||
public function eof()
|
||||
{
|
||||
if (!$this->handle) return true;
|
||||
return $this->fs->feof($this->handle);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->handle) $this->close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This is a stub include that automatically configures the include path.
|
||||
*/
|
||||
|
||||
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
|
||||
require_once 'HTMLPurifierExtras.php';
|
||||
require_once 'HTMLPurifierExtras.autoload.php';
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Convenience file that registers autoload handler for HTML Purifier.
|
||||
*
|
||||
* @warning
|
||||
* This autoloader does not contain the compatibility code seen in
|
||||
* HTMLPurifier_Bootstrap; the user is expected to make any necessary
|
||||
* changes to use this library.
|
||||
*/
|
||||
|
||||
if (function_exists('spl_autoload_register')) {
|
||||
spl_autoload_register(array('HTMLPurifierExtras', 'autoload'));
|
||||
if (function_exists('__autoload')) {
|
||||
// Be polite and ensure that userland autoload gets retained
|
||||
spl_autoload_register('__autoload');
|
||||
}
|
||||
} elseif (!function_exists('__autoload')) {
|
||||
function __autoload($class)
|
||||
{
|
||||
return HTMLPurifierExtras::autoload($class);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
31
library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php
Normal file
31
library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Meta-class for HTML Purifier's extra class hierarchies, similar to
|
||||
* HTMLPurifier_Bootstrap.
|
||||
*/
|
||||
class HTMLPurifierExtras
|
||||
{
|
||||
|
||||
public static function autoload($class)
|
||||
{
|
||||
$path = HTMLPurifierExtras::getPath($class);
|
||||
if (!$path) return false;
|
||||
require $path;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getPath($class)
|
||||
{
|
||||
if (
|
||||
strncmp('FSTools', $class, 7) !== 0 &&
|
||||
strncmp('ConfigDoc', $class, 9) !== 0
|
||||
) return false;
|
||||
// Custom implementations can go here
|
||||
// Standard implementation:
|
||||
return str_replace('_', '/', $class) . '.php';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
32
library/ezyang/htmlpurifier/extras/README
Normal file
32
library/ezyang/htmlpurifier/extras/README
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
HTML Purifier Extras
|
||||
The Method Behind The Madness!
|
||||
|
||||
The extras/ folder in HTML Purifier contains--you guessed it--extra things
|
||||
for HTML Purifier. Specifically, these are two extra libraries called
|
||||
FSTools and ConfigSchema. They're extra for a reason: you don't need them
|
||||
if you're using HTML Purifier for normal usage: filtering HTML. However,
|
||||
if you're a developer, and would like to test HTML Purifier, or need to
|
||||
use one of HTML Purifier's maintenance scripts, chances are they'll need
|
||||
these libraries. Who knows: maybe you'll find them useful too!
|
||||
|
||||
Here are the libraries:
|
||||
|
||||
|
||||
FSTools
|
||||
-------
|
||||
|
||||
Short for File System Tools, this is a poor-man's object-oriented wrapper for
|
||||
the filesystem. It currently consists of two classes:
|
||||
|
||||
- FSTools: This is a singleton that contains a manner of useful functions
|
||||
such as recursive glob, directory removal, etc, as well as the ability
|
||||
to call arbitrary native PHP functions through it like $FS->fopen(...).
|
||||
This makes it a lot simpler to mock these filesystem calls for unit testing.
|
||||
|
||||
- FSTools_File: This object represents a single file, and has almost any
|
||||
method imaginable one would need.
|
||||
|
||||
Check the files themselves for more information.
|
||||
|
||||
vim: et sw=4 sts=4
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
/**
|
||||
* @file
|
||||
* Convenience file that registers autoload handler for HTML Purifier.
|
||||
* It also does some sanity checks.
|
||||
*/
|
||||
|
||||
if (function_exists('spl_autoload_register') && function_exists('spl_autoload_unregister')) {
|
||||
|
|
@ -13,9 +14,14 @@ if (function_exists('spl_autoload_register') && function_exists('spl_autoload_un
|
|||
spl_autoload_register('__autoload');
|
||||
}
|
||||
} elseif (!function_exists('__autoload')) {
|
||||
function __autoload($class) {
|
||||
function __autoload($class)
|
||||
{
|
||||
return HTMLPurifier_Bootstrap::autoload($class);
|
||||
}
|
||||
}
|
||||
|
||||
if (ini_get('zend.ze1_compatibility_mode')) {
|
||||
trigger_error("HTML Purifier is not compatible with zend.ze1_compatibility_mode; please turn it off", E_USER_ERROR);
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
if (!defined('HTMLPURIFIER_PREFIX')) {
|
||||
define('HTMLPURIFIER_PREFIX', dirname(__FILE__));
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@
|
|||
|
||||
/**
|
||||
* Purify HTML.
|
||||
* @param $html String HTML to purify
|
||||
* @param $config Configuration to use, can be any value accepted by
|
||||
* @param string $html String HTML to purify
|
||||
* @param mixed $config Configuration to use, can be any value accepted by
|
||||
* HTMLPurifier_Config::create()
|
||||
* @return string
|
||||
*/
|
||||
function HTMLPurifier($html, $config = null) {
|
||||
function HTMLPurifier($html, $config = null)
|
||||
{
|
||||
static $purifier = false;
|
||||
if (!$purifier) {
|
||||
$purifier = new HTMLPurifier();
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
|
||||
* FILE, changes will be overwritten the next time the script is run.
|
||||
*
|
||||
* @version 4.1.1
|
||||
* @version 4.7.0
|
||||
*
|
||||
* @warning
|
||||
* You must *not* include any other HTML Purifier files before this file,
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
require 'HTMLPurifier.php';
|
||||
require 'HTMLPurifier/Arborize.php';
|
||||
require 'HTMLPurifier/AttrCollections.php';
|
||||
require 'HTMLPurifier/AttrDef.php';
|
||||
require 'HTMLPurifier/AttrTransform.php';
|
||||
|
|
@ -54,9 +55,11 @@ require 'HTMLPurifier/Language.php';
|
|||
require 'HTMLPurifier/LanguageFactory.php';
|
||||
require 'HTMLPurifier/Length.php';
|
||||
require 'HTMLPurifier/Lexer.php';
|
||||
require 'HTMLPurifier/Node.php';
|
||||
require 'HTMLPurifier/PercentEncoder.php';
|
||||
require 'HTMLPurifier/PropertyList.php';
|
||||
require 'HTMLPurifier/PropertyListIterator.php';
|
||||
require 'HTMLPurifier/Queue.php';
|
||||
require 'HTMLPurifier/Strategy.php';
|
||||
require 'HTMLPurifier/StringHash.php';
|
||||
require 'HTMLPurifier/StringHashParser.php';
|
||||
|
|
@ -72,7 +75,9 @@ require 'HTMLPurifier/URISchemeRegistry.php';
|
|||
require 'HTMLPurifier/UnitConverter.php';
|
||||
require 'HTMLPurifier/VarParser.php';
|
||||
require 'HTMLPurifier/VarParserException.php';
|
||||
require 'HTMLPurifier/Zipper.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS.php';
|
||||
require 'HTMLPurifier/AttrDef/Clone.php';
|
||||
require 'HTMLPurifier/AttrDef/Enum.php';
|
||||
require 'HTMLPurifier/AttrDef/Integer.php';
|
||||
require 'HTMLPurifier/AttrDef/Lang.php';
|
||||
|
|
@ -90,6 +95,7 @@ require 'HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
|
|||
require 'HTMLPurifier/AttrDef/CSS/Filter.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS/Font.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS/FontFamily.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS/Ident.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS/Length.php';
|
||||
require 'HTMLPurifier/AttrDef/CSS/ListStyle.php';
|
||||
|
|
@ -125,14 +131,17 @@ require 'HTMLPurifier/AttrTransform/Lang.php';
|
|||
require 'HTMLPurifier/AttrTransform/Length.php';
|
||||
require 'HTMLPurifier/AttrTransform/Name.php';
|
||||
require 'HTMLPurifier/AttrTransform/NameSync.php';
|
||||
require 'HTMLPurifier/AttrTransform/Nofollow.php';
|
||||
require 'HTMLPurifier/AttrTransform/SafeEmbed.php';
|
||||
require 'HTMLPurifier/AttrTransform/SafeObject.php';
|
||||
require 'HTMLPurifier/AttrTransform/SafeParam.php';
|
||||
require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
|
||||
require 'HTMLPurifier/AttrTransform/TargetBlank.php';
|
||||
require 'HTMLPurifier/AttrTransform/Textarea.php';
|
||||
require 'HTMLPurifier/ChildDef/Chameleon.php';
|
||||
require 'HTMLPurifier/ChildDef/Custom.php';
|
||||
require 'HTMLPurifier/ChildDef/Empty.php';
|
||||
require 'HTMLPurifier/ChildDef/List.php';
|
||||
require 'HTMLPurifier/ChildDef/Required.php';
|
||||
require 'HTMLPurifier/ChildDef/Optional.php';
|
||||
require 'HTMLPurifier/ChildDef/StrictBlockquote.php';
|
||||
|
|
@ -147,10 +156,12 @@ require 'HTMLPurifier/HTMLModule/CommonAttributes.php';
|
|||
require 'HTMLPurifier/HTMLModule/Edit.php';
|
||||
require 'HTMLPurifier/HTMLModule/Forms.php';
|
||||
require 'HTMLPurifier/HTMLModule/Hypertext.php';
|
||||
require 'HTMLPurifier/HTMLModule/Iframe.php';
|
||||
require 'HTMLPurifier/HTMLModule/Image.php';
|
||||
require 'HTMLPurifier/HTMLModule/Legacy.php';
|
||||
require 'HTMLPurifier/HTMLModule/List.php';
|
||||
require 'HTMLPurifier/HTMLModule/Name.php';
|
||||
require 'HTMLPurifier/HTMLModule/Nofollow.php';
|
||||
require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
|
||||
require 'HTMLPurifier/HTMLModule/Object.php';
|
||||
require 'HTMLPurifier/HTMLModule/Presentation.php';
|
||||
|
|
@ -158,10 +169,12 @@ require 'HTMLPurifier/HTMLModule/Proprietary.php';
|
|||
require 'HTMLPurifier/HTMLModule/Ruby.php';
|
||||
require 'HTMLPurifier/HTMLModule/SafeEmbed.php';
|
||||
require 'HTMLPurifier/HTMLModule/SafeObject.php';
|
||||
require 'HTMLPurifier/HTMLModule/SafeScripting.php';
|
||||
require 'HTMLPurifier/HTMLModule/Scripting.php';
|
||||
require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
|
||||
require 'HTMLPurifier/HTMLModule/Tables.php';
|
||||
require 'HTMLPurifier/HTMLModule/Target.php';
|
||||
require 'HTMLPurifier/HTMLModule/TargetBlank.php';
|
||||
require 'HTMLPurifier/HTMLModule/Text.php';
|
||||
require 'HTMLPurifier/HTMLModule/Tidy.php';
|
||||
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
|
||||
|
|
@ -180,6 +193,9 @@ require 'HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
|
|||
require 'HTMLPurifier/Injector/SafeObject.php';
|
||||
require 'HTMLPurifier/Lexer/DOMLex.php';
|
||||
require 'HTMLPurifier/Lexer/DirectLex.php';
|
||||
require 'HTMLPurifier/Node/Comment.php';
|
||||
require 'HTMLPurifier/Node/Element.php';
|
||||
require 'HTMLPurifier/Node/Text.php';
|
||||
require 'HTMLPurifier/Strategy/Composite.php';
|
||||
require 'HTMLPurifier/Strategy/Core.php';
|
||||
require 'HTMLPurifier/Strategy/FixNesting.php';
|
||||
|
|
@ -196,10 +212,13 @@ require 'HTMLPurifier/Token/Start.php';
|
|||
require 'HTMLPurifier/Token/Text.php';
|
||||
require 'HTMLPurifier/URIFilter/DisableExternal.php';
|
||||
require 'HTMLPurifier/URIFilter/DisableExternalResources.php';
|
||||
require 'HTMLPurifier/URIFilter/DisableResources.php';
|
||||
require 'HTMLPurifier/URIFilter/HostBlacklist.php';
|
||||
require 'HTMLPurifier/URIFilter/MakeAbsolute.php';
|
||||
require 'HTMLPurifier/URIFilter/Munge.php';
|
||||
require 'HTMLPurifier/URIFilter/SafeIframe.php';
|
||||
require 'HTMLPurifier/URIScheme/data.php';
|
||||
require 'HTMLPurifier/URIScheme/file.php';
|
||||
require 'HTMLPurifier/URIScheme/ftp.php';
|
||||
require 'HTMLPurifier/URIScheme/http.php';
|
||||
require 'HTMLPurifier/URIScheme/https.php';
|
||||
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
require_once dirname(__FILE__) . '/HTMLPurifier.auto.php';
|
||||
|
||||
function kses($string, $allowed_html, $allowed_protocols = null) {
|
||||
function kses($string, $allowed_html, $allowed_protocols = null)
|
||||
{
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$allowed_elements = array();
|
||||
$allowed_attributes = array();
|
||||
|
|
@ -19,7 +20,6 @@ function kses($string, $allowed_html, $allowed_protocols = null) {
|
|||
}
|
||||
$config->set('HTML.AllowedElements', $allowed_elements);
|
||||
$config->set('HTML.AllowedAttributes', $allowed_attributes);
|
||||
$allowed_schemes = array();
|
||||
if ($allowed_protocols !== null) {
|
||||
$config->set('URI.AllowedSchemes', $allowed_protocols);
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
HTML Purifier 4.1.1 - Standards Compliant HTML Filtering
|
||||
HTML Purifier 4.7.0 - Standards Compliant HTML Filtering
|
||||
Copyright (C) 2006-2008 Edward Z. Yang
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
|
|
@ -54,66 +54,97 @@
|
|||
class HTMLPurifier
|
||||
{
|
||||
|
||||
/** Version of HTML Purifier */
|
||||
public $version = '4.1.1';
|
||||
|
||||
/** Constant with version of HTML Purifier */
|
||||
const VERSION = '4.1.1';
|
||||
|
||||
/** Global configuration object */
|
||||
public $config;
|
||||
|
||||
/** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */
|
||||
private $filters = array();
|
||||
|
||||
/** Single instance of HTML Purifier */
|
||||
private static $instance;
|
||||
|
||||
protected $strategy, $generator;
|
||||
/**
|
||||
* Version of HTML Purifier.
|
||||
* @type string
|
||||
*/
|
||||
public $version = '4.7.0';
|
||||
|
||||
/**
|
||||
* Resultant HTMLPurifier_Context of last run purification. Is an array
|
||||
* of contexts if the last called method was purifyArray().
|
||||
* Constant with version of HTML Purifier.
|
||||
*/
|
||||
const VERSION = '4.7.0';
|
||||
|
||||
/**
|
||||
* Global configuration object.
|
||||
* @type HTMLPurifier_Config
|
||||
*/
|
||||
public $config;
|
||||
|
||||
/**
|
||||
* Array of extra filter objects to run on HTML,
|
||||
* for backwards compatibility.
|
||||
* @type HTMLPurifier_Filter[]
|
||||
*/
|
||||
private $filters = array();
|
||||
|
||||
/**
|
||||
* Single instance of HTML Purifier.
|
||||
* @type HTMLPurifier
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_Strategy_Core
|
||||
*/
|
||||
protected $strategy;
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_Generator
|
||||
*/
|
||||
protected $generator;
|
||||
|
||||
/**
|
||||
* Resultant context of last run purification.
|
||||
* Is an array of contexts if the last called method was purifyArray().
|
||||
* @type HTMLPurifier_Context
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* Initializes the purifier.
|
||||
* @param $config Optional HTMLPurifier_Config object for all instances of
|
||||
* the purifier, if omitted, a default configuration is
|
||||
* supplied (which can be overridden on a per-use basis).
|
||||
*
|
||||
* @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object
|
||||
* for all instances of the purifier, if omitted, a default
|
||||
* configuration is supplied (which can be overridden on a
|
||||
* per-use basis).
|
||||
* The parameter can also be any type that
|
||||
* HTMLPurifier_Config::create() supports.
|
||||
*/
|
||||
public function __construct($config = null) {
|
||||
|
||||
public function __construct($config = null)
|
||||
{
|
||||
$this->config = HTMLPurifier_Config::create($config);
|
||||
|
||||
$this->strategy = new HTMLPurifier_Strategy_Core();
|
||||
|
||||
$this->strategy = new HTMLPurifier_Strategy_Core();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a filter to process the output. First come first serve
|
||||
* @param $filter HTMLPurifier_Filter object
|
||||
*
|
||||
* @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object
|
||||
*/
|
||||
public function addFilter($filter) {
|
||||
trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING);
|
||||
public function addFilter($filter)
|
||||
{
|
||||
trigger_error(
|
||||
'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
|
||||
' in the Filter namespace or Filter.Custom',
|
||||
E_USER_WARNING
|
||||
);
|
||||
$this->filters[] = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters an HTML snippet/document to be XSS-free and standards-compliant.
|
||||
*
|
||||
* @param $html String of HTML to purify
|
||||
* @param $config HTMLPurifier_Config object for this operation, if omitted,
|
||||
* defaults to the config object specified during this
|
||||
* @param string $html String of HTML to purify
|
||||
* @param HTMLPurifier_Config $config Config object for this operation,
|
||||
* if omitted, defaults to the config object specified during this
|
||||
* object's construction. The parameter can also be any type
|
||||
* that HTMLPurifier_Config::create() supports.
|
||||
* @return Purified HTML
|
||||
*
|
||||
* @return string Purified HTML
|
||||
*/
|
||||
public function purify($html, $config = null) {
|
||||
|
||||
public function purify($html, $config = null)
|
||||
{
|
||||
// :TODO: make the config merge in, instead of replace
|
||||
$config = $config ? HTMLPurifier_Config::create($config) : $this->config;
|
||||
|
||||
|
|
@ -151,8 +182,12 @@ class HTMLPurifier
|
|||
unset($filter_flags['Custom']);
|
||||
$filters = array();
|
||||
foreach ($filter_flags as $filter => $flag) {
|
||||
if (!$flag) continue;
|
||||
if (strpos($filter, '.') !== false) continue;
|
||||
if (!$flag) {
|
||||
continue;
|
||||
}
|
||||
if (strpos($filter, '.') !== false) {
|
||||
continue;
|
||||
}
|
||||
$class = "HTMLPurifier_Filter_$filter";
|
||||
$filters[] = new $class;
|
||||
}
|
||||
|
|
@ -175,9 +210,12 @@ class HTMLPurifier
|
|||
// list of un-purified tokens
|
||||
$lexer->tokenizeHTML(
|
||||
// un-purified HTML
|
||||
$html, $config, $context
|
||||
$html,
|
||||
$config,
|
||||
$context
|
||||
),
|
||||
$config, $context
|
||||
$config,
|
||||
$context
|
||||
)
|
||||
);
|
||||
|
||||
|
|
@ -192,11 +230,15 @@ class HTMLPurifier
|
|||
|
||||
/**
|
||||
* Filters an array of HTML snippets
|
||||
* @param $config Optional HTMLPurifier_Config object for this operation.
|
||||
*
|
||||
* @param string[] $array_of_html Array of html snippets
|
||||
* @param HTMLPurifier_Config $config Optional config object for this operation.
|
||||
* See HTMLPurifier::purify() for more details.
|
||||
* @return Array of purified HTML
|
||||
*
|
||||
* @return string[] Array of purified HTML
|
||||
*/
|
||||
public function purifyArray($array_of_html, $config = null) {
|
||||
public function purifyArray($array_of_html, $config = null)
|
||||
{
|
||||
$context_array = array();
|
||||
foreach ($array_of_html as $key => $html) {
|
||||
$array_of_html[$key] = $this->purify($html, $config);
|
||||
|
|
@ -208,11 +250,16 @@ class HTMLPurifier
|
|||
|
||||
/**
|
||||
* Singleton for enforcing just one HTML Purifier in your system
|
||||
* @param $prototype Optional prototype HTMLPurifier instance to
|
||||
* overload singleton with, or HTMLPurifier_Config
|
||||
* instance to configure the generated version with.
|
||||
*
|
||||
* @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
|
||||
* HTMLPurifier instance to overload singleton with,
|
||||
* or HTMLPurifier_Config instance to configure the
|
||||
* generated version with.
|
||||
*
|
||||
* @return HTMLPurifier
|
||||
*/
|
||||
public static function instance($prototype = null) {
|
||||
public static function instance($prototype = null)
|
||||
{
|
||||
if (!self::$instance || $prototype) {
|
||||
if ($prototype instanceof HTMLPurifier) {
|
||||
self::$instance = $prototype;
|
||||
|
|
@ -226,12 +273,20 @@ class HTMLPurifier
|
|||
}
|
||||
|
||||
/**
|
||||
* Singleton for enforcing just one HTML Purifier in your system
|
||||
*
|
||||
* @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
|
||||
* HTMLPurifier instance to overload singleton with,
|
||||
* or HTMLPurifier_Config instance to configure the
|
||||
* generated version with.
|
||||
*
|
||||
* @return HTMLPurifier
|
||||
* @note Backwards compatibility, see instance()
|
||||
*/
|
||||
public static function getInstance($prototype = null) {
|
||||
public static function getInstance($prototype = null)
|
||||
{
|
||||
return HTMLPurifier::instance($prototype);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
$__dir = dirname(__FILE__);
|
||||
|
||||
require_once $__dir . '/HTMLPurifier.php';
|
||||
require_once $__dir . '/HTMLPurifier/Arborize.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrCollections.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform.php';
|
||||
|
|
@ -48,9 +49,11 @@ require_once $__dir . '/HTMLPurifier/Language.php';
|
|||
require_once $__dir . '/HTMLPurifier/LanguageFactory.php';
|
||||
require_once $__dir . '/HTMLPurifier/Length.php';
|
||||
require_once $__dir . '/HTMLPurifier/Lexer.php';
|
||||
require_once $__dir . '/HTMLPurifier/Node.php';
|
||||
require_once $__dir . '/HTMLPurifier/PercentEncoder.php';
|
||||
require_once $__dir . '/HTMLPurifier/PropertyList.php';
|
||||
require_once $__dir . '/HTMLPurifier/PropertyListIterator.php';
|
||||
require_once $__dir . '/HTMLPurifier/Queue.php';
|
||||
require_once $__dir . '/HTMLPurifier/Strategy.php';
|
||||
require_once $__dir . '/HTMLPurifier/StringHash.php';
|
||||
require_once $__dir . '/HTMLPurifier/StringHashParser.php';
|
||||
|
|
@ -66,7 +69,9 @@ require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php';
|
|||
require_once $__dir . '/HTMLPurifier/UnitConverter.php';
|
||||
require_once $__dir . '/HTMLPurifier/VarParser.php';
|
||||
require_once $__dir . '/HTMLPurifier/VarParserException.php';
|
||||
require_once $__dir . '/HTMLPurifier/Zipper.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/Clone.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php';
|
||||
|
|
@ -84,6 +89,7 @@ require_once $__dir . '/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
|
|||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/FontFamily.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Ident.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Length.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php';
|
||||
|
|
@ -119,14 +125,17 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php';
|
|||
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php';
|
||||
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
|
||||
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
|
||||
require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php';
|
||||
require_once $__dir . '/HTMLPurifier/ChildDef/List.php';
|
||||
require_once $__dir . '/HTMLPurifier/ChildDef/Required.php';
|
||||
require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php';
|
||||
require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php';
|
||||
|
|
@ -141,10 +150,12 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php';
|
|||
require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Iframe.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/List.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Nofollow.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
|
||||
|
|
@ -152,10 +163,12 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php';
|
|||
require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeScripting.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
|
||||
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
|
||||
|
|
@ -174,6 +187,9 @@ require_once $__dir . '/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
|
|||
require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php';
|
||||
require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php';
|
||||
require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php';
|
||||
require_once $__dir . '/HTMLPurifier/Node/Comment.php';
|
||||
require_once $__dir . '/HTMLPurifier/Node/Element.php';
|
||||
require_once $__dir . '/HTMLPurifier/Node/Text.php';
|
||||
require_once $__dir . '/HTMLPurifier/Strategy/Composite.php';
|
||||
require_once $__dir . '/HTMLPurifier/Strategy/Core.php';
|
||||
require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php';
|
||||
|
|
@ -190,10 +206,13 @@ require_once $__dir . '/HTMLPurifier/Token/Start.php';
|
|||
require_once $__dir . '/HTMLPurifier/Token/Text.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIFilter/DisableResources.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIFilter/SafeIframe.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIScheme/data.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIScheme/file.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIScheme/http.php';
|
||||
require_once $__dir . '/HTMLPurifier/URIScheme/https.php';
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
|
||||
* and back again.
|
||||
*
|
||||
* @note This transformation is not an equivalence. We mutate the input
|
||||
* token stream to make it so; see all [MUT] markers in code.
|
||||
*/
|
||||
class HTMLPurifier_Arborize
|
||||
{
|
||||
public static function arborize($tokens, $config, $context) {
|
||||
$definition = $config->getHTMLDefinition();
|
||||
$parent = new HTMLPurifier_Token_Start($definition->info_parent);
|
||||
$stack = array($parent->toNode());
|
||||
foreach ($tokens as $token) {
|
||||
$token->skip = null; // [MUT]
|
||||
$token->carryover = null; // [MUT]
|
||||
if ($token instanceof HTMLPurifier_Token_End) {
|
||||
$token->start = null; // [MUT]
|
||||
$r = array_pop($stack);
|
||||
assert($r->name === $token->name);
|
||||
assert(empty($token->attr));
|
||||
$r->endCol = $token->col;
|
||||
$r->endLine = $token->line;
|
||||
$r->endArmor = $token->armor;
|
||||
continue;
|
||||
}
|
||||
$node = $token->toNode();
|
||||
$stack[count($stack)-1]->children[] = $node;
|
||||
if ($token instanceof HTMLPurifier_Token_Start) {
|
||||
$stack[] = $node;
|
||||
}
|
||||
}
|
||||
assert(count($stack) == 1);
|
||||
return $stack[0];
|
||||
}
|
||||
|
||||
public static function flatten($node, $config, $context) {
|
||||
$level = 0;
|
||||
$nodes = array($level => new HTMLPurifier_Queue(array($node)));
|
||||
$closingTokens = array();
|
||||
$tokens = array();
|
||||
do {
|
||||
while (!$nodes[$level]->isEmpty()) {
|
||||
$node = $nodes[$level]->shift(); // FIFO
|
||||
list($start, $end) = $node->toTokenPair();
|
||||
if ($level > 0) {
|
||||
$tokens[] = $start;
|
||||
}
|
||||
if ($end !== NULL) {
|
||||
$closingTokens[$level][] = $end;
|
||||
}
|
||||
if ($node instanceof HTMLPurifier_Node_Element) {
|
||||
$level++;
|
||||
$nodes[$level] = new HTMLPurifier_Queue();
|
||||
foreach ($node->children as $childNode) {
|
||||
$nodes[$level]->push($childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
$level--;
|
||||
if ($level && isset($closingTokens[$level])) {
|
||||
while ($token = array_pop($closingTokens[$level])) {
|
||||
$tokens[] = $token;
|
||||
}
|
||||
}
|
||||
} while ($level > 0);
|
||||
return $tokens;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,8 @@ class HTMLPurifier_AttrCollections
|
|||
{
|
||||
|
||||
/**
|
||||
* Associative array of attribute collections, indexed by name
|
||||
* Associative array of attribute collections, indexed by name.
|
||||
* @type array
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
|
|
@ -16,10 +17,11 @@ class HTMLPurifier_AttrCollections
|
|||
* Performs all expansions on internal data for use by other inclusions
|
||||
* It also collects all attribute collection extensions from
|
||||
* modules
|
||||
* @param $attr_types HTMLPurifier_AttrTypes instance
|
||||
* @param $modules Hash array of HTMLPurifier_HTMLModule members
|
||||
* @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
|
||||
* @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
|
||||
*/
|
||||
public function __construct($attr_types, $modules) {
|
||||
public function __construct($attr_types, $modules)
|
||||
{
|
||||
// load extensions from the modules
|
||||
foreach ($modules as $module) {
|
||||
foreach ($module->attr_collections as $coll_i => $coll) {
|
||||
|
|
@ -30,7 +32,9 @@ class HTMLPurifier_AttrCollections
|
|||
if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
|
||||
// merge in includes
|
||||
$this->info[$coll_i][$attr_i] = array_merge(
|
||||
$this->info[$coll_i][$attr_i], $attr);
|
||||
$this->info[$coll_i][$attr_i],
|
||||
$attr
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$this->info[$coll_i][$attr_i] = $attr;
|
||||
|
|
@ -49,20 +53,29 @@ class HTMLPurifier_AttrCollections
|
|||
/**
|
||||
* Takes a reference to an attribute associative array and performs
|
||||
* all inclusions specified by the zero index.
|
||||
* @param &$attr Reference to attribute array
|
||||
* @param array &$attr Reference to attribute array
|
||||
*/
|
||||
public function performInclusions(&$attr) {
|
||||
if (!isset($attr[0])) return;
|
||||
public function performInclusions(&$attr)
|
||||
{
|
||||
if (!isset($attr[0])) {
|
||||
return;
|
||||
}
|
||||
$merge = $attr[0];
|
||||
$seen = array(); // recursion guard
|
||||
// loop through all the inclusions
|
||||
for ($i = 0; isset($merge[$i]); $i++) {
|
||||
if (isset($seen[$merge[$i]])) continue;
|
||||
if (isset($seen[$merge[$i]])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$merge[$i]] = true;
|
||||
// foreach attribute of the inclusion, copy it over
|
||||
if (!isset($this->info[$merge[$i]])) continue;
|
||||
if (!isset($this->info[$merge[$i]])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->info[$merge[$i]] as $key => $value) {
|
||||
if (isset($attr[$key])) continue; // also catches more inclusions
|
||||
if (isset($attr[$key])) {
|
||||
continue;
|
||||
} // also catches more inclusions
|
||||
$attr[$key] = $value;
|
||||
}
|
||||
if (isset($this->info[$merge[$i]][0])) {
|
||||
|
|
@ -76,20 +89,24 @@ class HTMLPurifier_AttrCollections
|
|||
/**
|
||||
* Expands all string identifiers in an attribute array by replacing
|
||||
* them with the appropriate values inside HTMLPurifier_AttrTypes
|
||||
* @param &$attr Reference to attribute array
|
||||
* @param $attr_types HTMLPurifier_AttrTypes instance
|
||||
* @param array &$attr Reference to attribute array
|
||||
* @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
|
||||
*/
|
||||
public function expandIdentifiers(&$attr, $attr_types) {
|
||||
|
||||
public function expandIdentifiers(&$attr, $attr_types)
|
||||
{
|
||||
// because foreach will process new elements we add, make sure we
|
||||
// skip duplicates
|
||||
$processed = array();
|
||||
|
||||
foreach ($attr as $def_i => $def) {
|
||||
// skip inclusions
|
||||
if ($def_i === 0) continue;
|
||||
if ($def_i === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($processed[$def_i])) continue;
|
||||
if (isset($processed[$def_i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// determine whether or not attribute is required
|
||||
if ($required = (strpos($def_i, '*') !== false)) {
|
||||
|
|
@ -120,9 +137,7 @@ class HTMLPurifier_AttrCollections
|
|||
unset($attr[$def_i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -14,23 +14,25 @@ abstract class HTMLPurifier_AttrDef
|
|||
{
|
||||
|
||||
/**
|
||||
* Tells us whether or not an HTML attribute is minimized. Has no
|
||||
* meaning in other contexts.
|
||||
* Tells us whether or not an HTML attribute is minimized.
|
||||
* Has no meaning in other contexts.
|
||||
* @type bool
|
||||
*/
|
||||
public $minimized = false;
|
||||
|
||||
/**
|
||||
* Tells us whether or not an HTML attribute is required. Has no
|
||||
* meaning in other contexts
|
||||
* Tells us whether or not an HTML attribute is required.
|
||||
* Has no meaning in other contexts
|
||||
* @type bool
|
||||
*/
|
||||
public $required = false;
|
||||
|
||||
/**
|
||||
* Validates and cleans passed string according to a definition.
|
||||
*
|
||||
* @param $string String to be validated and cleaned.
|
||||
* @param $config Mandatory HTMLPurifier_Config object.
|
||||
* @param $context Mandatory HTMLPurifier_AttrContext object.
|
||||
* @param string $string String to be validated and cleaned.
|
||||
* @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
|
||||
* @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
|
||||
*/
|
||||
abstract public function validate($string, $config, $context);
|
||||
|
||||
|
|
@ -55,7 +57,8 @@ abstract class HTMLPurifier_AttrDef
|
|||
* parsing XML, thus, this behavior may still be correct. We
|
||||
* assume that newlines have been normalized.
|
||||
*/
|
||||
public function parseCDATA($string) {
|
||||
public function parseCDATA($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
$string = str_replace(array("\n", "\t", "\r"), ' ', $string);
|
||||
return $string;
|
||||
|
|
@ -63,10 +66,11 @@ abstract class HTMLPurifier_AttrDef
|
|||
|
||||
/**
|
||||
* Factory method for creating this class from a string.
|
||||
* @param $string String construction info
|
||||
* @return Created AttrDef object corresponding to $string
|
||||
* @param string $string String construction info
|
||||
* @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
|
||||
*/
|
||||
public function make($string) {
|
||||
public function make($string)
|
||||
{
|
||||
// default implementation, return a flyweight of this object.
|
||||
// If $string has an effect on the returned object (i.e. you
|
||||
// need to overload this method), it is best
|
||||
|
|
@ -77,16 +81,20 @@ abstract class HTMLPurifier_AttrDef
|
|||
/**
|
||||
* Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
|
||||
* properly. THIS IS A HACK!
|
||||
* @param string $string a CSS colour definition
|
||||
* @return string
|
||||
*/
|
||||
protected function mungeRgb($string) {
|
||||
protected function mungeRgb($string)
|
||||
{
|
||||
return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a possibly escaped CSS string and returns the "pure"
|
||||
* Parses a possibly escaped CSS string and returns the "pure"
|
||||
* version of it.
|
||||
*/
|
||||
protected function expandCSSEscape($string) {
|
||||
protected function expandCSSEscape($string)
|
||||
{
|
||||
// flexibly parse it
|
||||
$ret = '';
|
||||
for ($i = 0, $c = strlen($string); $i < $c; $i++) {
|
||||
|
|
@ -99,25 +107,32 @@ abstract class HTMLPurifier_AttrDef
|
|||
if (ctype_xdigit($string[$i])) {
|
||||
$code = $string[$i];
|
||||
for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
|
||||
if (!ctype_xdigit($string[$i])) break;
|
||||
if (!ctype_xdigit($string[$i])) {
|
||||
break;
|
||||
}
|
||||
$code .= $string[$i];
|
||||
}
|
||||
// We have to be extremely careful when adding
|
||||
// new characters, to make sure we're not breaking
|
||||
// the encoding.
|
||||
$char = HTMLPurifier_Encoder::unichr(hexdec($code));
|
||||
if (HTMLPurifier_Encoder::cleanUTF8($char) === '') continue;
|
||||
if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
|
||||
continue;
|
||||
}
|
||||
$ret .= $char;
|
||||
if ($i < $c && trim($string[$i]) !== '') $i--;
|
||||
if ($i < $c && trim($string[$i]) !== '') {
|
||||
$i--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($string[$i] === "\n") {
|
||||
continue;
|
||||
}
|
||||
if ($string[$i] === "\n") continue;
|
||||
}
|
||||
$ret .= $string[$i];
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -14,8 +14,14 @@
|
|||
class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($css, $config, $context) {
|
||||
|
||||
/**
|
||||
* @param string $css
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($css, $config, $context)
|
||||
{
|
||||
$css = $this->parseCDATA($css);
|
||||
|
||||
$definition = $config->getCSSDefinition();
|
||||
|
|
@ -36,34 +42,47 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
|
|||
$context->register('CurrentCSSProperty', $property);
|
||||
|
||||
foreach ($declarations as $declaration) {
|
||||
if (!$declaration) continue;
|
||||
if (!strpos($declaration, ':')) continue;
|
||||
if (!$declaration) {
|
||||
continue;
|
||||
}
|
||||
if (!strpos($declaration, ':')) {
|
||||
continue;
|
||||
}
|
||||
list($property, $value) = explode(':', $declaration, 2);
|
||||
$property = trim($property);
|
||||
$value = trim($value);
|
||||
$value = trim($value);
|
||||
$ok = false;
|
||||
do {
|
||||
if (isset($definition->info[$property])) {
|
||||
$ok = true;
|
||||
break;
|
||||
}
|
||||
if (ctype_lower($property)) break;
|
||||
if (ctype_lower($property)) {
|
||||
break;
|
||||
}
|
||||
$property = strtolower($property);
|
||||
if (isset($definition->info[$property])) {
|
||||
$ok = true;
|
||||
break;
|
||||
}
|
||||
} while(0);
|
||||
if (!$ok) continue;
|
||||
} while (0);
|
||||
if (!$ok) {
|
||||
continue;
|
||||
}
|
||||
// inefficient call, since the validator will do this again
|
||||
if (strtolower(trim($value)) !== 'inherit') {
|
||||
// inherit works for everything (but only on the base property)
|
||||
$result = $definition->info[$property]->validate(
|
||||
$value, $config, $context );
|
||||
$value,
|
||||
$config,
|
||||
$context
|
||||
);
|
||||
} else {
|
||||
$result = 'inherit';
|
||||
}
|
||||
if ($result === false) continue;
|
||||
if ($result === false) {
|
||||
continue;
|
||||
}
|
||||
$propvalues[$property] = $result;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(false); // opacity is non-negative, but we will clamp it
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return string
|
||||
*/
|
||||
public function validate($number, $config, $context)
|
||||
{
|
||||
$result = parent::validate($number, $config, $context);
|
||||
if ($result === false) {
|
||||
return $result;
|
||||
}
|
||||
$float = (float)$result;
|
||||
if ($float < 0.0) {
|
||||
$result = '0';
|
||||
}
|
||||
if ($float > 1.0) {
|
||||
$result = '1';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -9,11 +9,16 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
|
|||
|
||||
/**
|
||||
* Local copy of component validators.
|
||||
* @type HTMLPurifier_AttrDef[]
|
||||
* @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
|
||||
*/
|
||||
protected $info;
|
||||
|
||||
public function __construct($config) {
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['background-color'] = $def->info['background-color'];
|
||||
$this->info['background-image'] = $def->info['background-image'];
|
||||
|
|
@ -22,40 +27,55 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
|
|||
$this->info['background-position'] = $def->info['background-position'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
// regular pre-processing
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
if ($string === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// munge rgb() decl if necessary
|
||||
$string = $this->mungeRgb($string);
|
||||
|
||||
// assumes URI doesn't have spaces in it
|
||||
$bits = explode(' ', strtolower($string)); // bits to process
|
||||
$bits = explode(' ', $string); // bits to process
|
||||
|
||||
$caught = array();
|
||||
$caught['color'] = false;
|
||||
$caught['image'] = false;
|
||||
$caught['repeat'] = false;
|
||||
$caught['color'] = false;
|
||||
$caught['image'] = false;
|
||||
$caught['repeat'] = false;
|
||||
$caught['attachment'] = false;
|
||||
$caught['position'] = false;
|
||||
|
||||
$i = 0; // number of catches
|
||||
$none = false;
|
||||
|
||||
foreach ($bits as $bit) {
|
||||
if ($bit === '') continue;
|
||||
if ($bit === '') {
|
||||
continue;
|
||||
}
|
||||
foreach ($caught as $key => $status) {
|
||||
if ($key != 'position') {
|
||||
if ($status !== false) continue;
|
||||
if ($status !== false) {
|
||||
continue;
|
||||
}
|
||||
$r = $this->info['background-' . $key]->validate($bit, $config, $context);
|
||||
} else {
|
||||
$r = $bit;
|
||||
}
|
||||
if ($r === false) continue;
|
||||
if ($r === false) {
|
||||
continue;
|
||||
}
|
||||
if ($key == 'position') {
|
||||
if ($caught[$key] === false) $caught[$key] = '';
|
||||
if ($caught[$key] === false) {
|
||||
$caught[$key] = '';
|
||||
}
|
||||
$caught[$key] .= $r . ' ';
|
||||
} else {
|
||||
$caught[$key] = $r;
|
||||
|
|
@ -65,7 +85,9 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
|
|||
}
|
||||
}
|
||||
|
||||
if (!$i) return false;
|
||||
if (!$i) {
|
||||
return false;
|
||||
}
|
||||
if ($caught['position'] !== false) {
|
||||
$caught['position'] = $this->info['background-position']->
|
||||
validate($caught['position'], $config, $context);
|
||||
|
|
@ -73,15 +95,17 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
|
|||
|
||||
$ret = array();
|
||||
foreach ($caught as $value) {
|
||||
if ($value === false) continue;
|
||||
if ($value === false) {
|
||||
continue;
|
||||
}
|
||||
$ret[] = $value;
|
||||
}
|
||||
|
||||
if (empty($ret)) return false;
|
||||
if (empty($ret)) {
|
||||
return false;
|
||||
}
|
||||
return implode(' ', $ret);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -44,15 +44,30 @@
|
|||
class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_AttrDef_CSS_Length
|
||||
*/
|
||||
protected $length;
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_AttrDef_CSS_Percentage
|
||||
*/
|
||||
protected $percentage;
|
||||
|
||||
public function __construct() {
|
||||
$this->length = new HTMLPurifier_AttrDef_CSS_Length();
|
||||
public function __construct()
|
||||
{
|
||||
$this->length = new HTMLPurifier_AttrDef_CSS_Length();
|
||||
$this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
$bits = explode(' ', $string);
|
||||
|
||||
|
|
@ -74,7 +89,9 @@ class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
|
|||
);
|
||||
|
||||
foreach ($bits as $bit) {
|
||||
if ($bit === '') continue;
|
||||
if ($bit === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// test for keyword
|
||||
$lbit = ctype_lower($bit) ? $bit : strtolower($bit);
|
||||
|
|
@ -104,30 +121,37 @@ class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
|
|||
$measures[] = $r;
|
||||
$i++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!$i) return false; // no valid values were caught
|
||||
if (!$i) {
|
||||
return false;
|
||||
} // no valid values were caught
|
||||
|
||||
$ret = array();
|
||||
|
||||
// first keyword
|
||||
if ($keywords['h']) $ret[] = $keywords['h'];
|
||||
elseif ($keywords['ch']) {
|
||||
if ($keywords['h']) {
|
||||
$ret[] = $keywords['h'];
|
||||
} elseif ($keywords['ch']) {
|
||||
$ret[] = $keywords['ch'];
|
||||
$keywords['cv'] = false; // prevent re-use: center = center center
|
||||
} elseif (count($measures)) {
|
||||
$ret[] = array_shift($measures);
|
||||
}
|
||||
elseif (count($measures)) $ret[] = array_shift($measures);
|
||||
|
||||
if ($keywords['v']) $ret[] = $keywords['v'];
|
||||
elseif ($keywords['cv']) $ret[] = $keywords['cv'];
|
||||
elseif (count($measures)) $ret[] = array_shift($measures);
|
||||
if ($keywords['v']) {
|
||||
$ret[] = $keywords['v'];
|
||||
} elseif ($keywords['cv']) {
|
||||
$ret[] = $keywords['cv'];
|
||||
} elseif (count($measures)) {
|
||||
$ret[] = array_shift($measures);
|
||||
}
|
||||
|
||||
if (empty($ret)) return false;
|
||||
if (empty($ret)) {
|
||||
return false;
|
||||
}
|
||||
return implode(' ', $ret);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -8,17 +8,29 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
|
|||
|
||||
/**
|
||||
* Local copy of properties this property is shorthand for.
|
||||
* @type HTMLPurifier_AttrDef[]
|
||||
*/
|
||||
protected $info = array();
|
||||
|
||||
public function __construct($config) {
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['border-width'] = $def->info['border-width'];
|
||||
$this->info['border-style'] = $def->info['border-style'];
|
||||
$this->info['border-top-color'] = $def->info['border-top-color'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
$string = $this->mungeRgb($string);
|
||||
$bits = explode(' ', $string);
|
||||
|
|
@ -26,7 +38,9 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
|
|||
$ret = ''; // return value
|
||||
foreach ($bits as $bit) {
|
||||
foreach ($this->info as $propname => $validator) {
|
||||
if (isset($done[$propname])) continue;
|
||||
if (isset($done[$propname])) {
|
||||
continue;
|
||||
}
|
||||
$r = $validator->validate($bit, $config, $context);
|
||||
if ($r !== false) {
|
||||
$ret .= $r . ' ';
|
||||
|
|
@ -37,7 +51,6 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
|
|||
}
|
||||
return rtrim($ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -6,29 +6,47 @@
|
|||
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($color, $config, $context) {
|
||||
|
||||
/**
|
||||
* @param string $color
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($color, $config, $context)
|
||||
{
|
||||
static $colors = null;
|
||||
if ($colors === null) $colors = $config->get('Core.ColorKeywords');
|
||||
if ($colors === null) {
|
||||
$colors = $config->get('Core.ColorKeywords');
|
||||
}
|
||||
|
||||
$color = trim($color);
|
||||
if ($color === '') return false;
|
||||
if ($color === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lower = strtolower($color);
|
||||
if (isset($colors[$lower])) return $colors[$lower];
|
||||
if (isset($colors[$lower])) {
|
||||
return $colors[$lower];
|
||||
}
|
||||
|
||||
if (strpos($color, 'rgb(') !== false) {
|
||||
// rgb literal handling
|
||||
$length = strlen($color);
|
||||
if (strpos($color, ')') !== $length - 1) return false;
|
||||
if (strpos($color, ')') !== $length - 1) {
|
||||
return false;
|
||||
}
|
||||
$triad = substr($color, 4, $length - 4 - 1);
|
||||
$parts = explode(',', $triad);
|
||||
if (count($parts) !== 3) return false;
|
||||
if (count($parts) !== 3) {
|
||||
return false;
|
||||
}
|
||||
$type = false; // to ensure that they're all the same type
|
||||
$new_parts = array();
|
||||
foreach ($parts as $part) {
|
||||
$part = trim($part);
|
||||
if ($part === '') return false;
|
||||
if ($part === '') {
|
||||
return false;
|
||||
}
|
||||
$length = strlen($part);
|
||||
if ($part[$length - 1] === '%') {
|
||||
// handle percents
|
||||
|
|
@ -37,9 +55,13 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
|
|||
} elseif ($type !== 'percentage') {
|
||||
return false;
|
||||
}
|
||||
$num = (float) substr($part, 0, $length - 1);
|
||||
if ($num < 0) $num = 0;
|
||||
if ($num > 100) $num = 100;
|
||||
$num = (float)substr($part, 0, $length - 1);
|
||||
if ($num < 0) {
|
||||
$num = 0;
|
||||
}
|
||||
if ($num > 100) {
|
||||
$num = 100;
|
||||
}
|
||||
$new_parts[] = "$num%";
|
||||
} else {
|
||||
// handle integers
|
||||
|
|
@ -48,10 +70,14 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
|
|||
} elseif ($type !== 'integer') {
|
||||
return false;
|
||||
}
|
||||
$num = (int) $part;
|
||||
if ($num < 0) $num = 0;
|
||||
if ($num > 255) $num = 255;
|
||||
$new_parts[] = (string) $num;
|
||||
$num = (int)$part;
|
||||
if ($num < 0) {
|
||||
$num = 0;
|
||||
}
|
||||
if ($num > 255) {
|
||||
$num = 255;
|
||||
}
|
||||
$new_parts[] = (string)$num;
|
||||
}
|
||||
}
|
||||
$new_triad = implode(',', $new_parts);
|
||||
|
|
@ -65,14 +91,15 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
|
|||
$color = '#' . $color;
|
||||
}
|
||||
$length = strlen($hex);
|
||||
if ($length !== 3 && $length !== 6) return false;
|
||||
if (!ctype_xdigit($hex)) return false;
|
||||
if ($length !== 3 && $length !== 6) {
|
||||
return false;
|
||||
}
|
||||
if (!ctype_xdigit($hex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $color;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -13,26 +13,36 @@ class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
|
|||
{
|
||||
|
||||
/**
|
||||
* List of HTMLPurifier_AttrDef objects that may process strings
|
||||
* List of objects that may process strings.
|
||||
* @type HTMLPurifier_AttrDef[]
|
||||
* @todo Make protected
|
||||
*/
|
||||
public $defs;
|
||||
|
||||
/**
|
||||
* @param $defs List of HTMLPurifier_AttrDef objects
|
||||
* @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
|
||||
*/
|
||||
public function __construct($defs) {
|
||||
public function __construct($defs)
|
||||
{
|
||||
$this->defs = $defs;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
foreach ($this->defs as $i => $def) {
|
||||
$result = $this->defs[$i]->validate($string, $config, $context);
|
||||
if ($result !== false) return $result;
|
||||
if ($result !== false) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Decorator which enables CSS properties to be disabled for specific elements.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
|
||||
{
|
||||
/**
|
||||
* @type HTMLPurifier_AttrDef
|
||||
*/
|
||||
public $def;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $element;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_AttrDef $def Definition to wrap
|
||||
* @param string $element Element to deny
|
||||
*/
|
||||
public function __construct($def, $element)
|
||||
{
|
||||
$this->def = $def;
|
||||
$this->element = $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if CurrentToken is set and equal to $this->element
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$token = $context->get('CurrentToken', true);
|
||||
if ($token && $token->name == $this->element) {
|
||||
return false;
|
||||
}
|
||||
return $this->def->validate($string, $config, $context);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -7,23 +7,37 @@
|
|||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_AttrDef_Integer
|
||||
*/
|
||||
protected $intValidator;
|
||||
|
||||
public function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
$this->intValidator = new HTMLPurifier_AttrDef_Integer();
|
||||
}
|
||||
|
||||
public function validate($value, $config, $context) {
|
||||
/**
|
||||
* @param string $value
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($value, $config, $context)
|
||||
{
|
||||
$value = $this->parseCDATA($value);
|
||||
if ($value === 'none') return $value;
|
||||
if ($value === 'none') {
|
||||
return $value;
|
||||
}
|
||||
// if we looped this we could support multiple filters
|
||||
$function_length = strcspn($value, '(');
|
||||
$function = trim(substr($value, 0, $function_length));
|
||||
if ($function !== 'alpha' &&
|
||||
$function !== 'Alpha' &&
|
||||
$function !== 'progid:DXImageTransform.Microsoft.Alpha'
|
||||
) return false;
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
$cursor = $function_length + 1;
|
||||
$parameters_length = strcspn($value, ')', $cursor);
|
||||
$parameters = substr($value, $cursor, $parameters_length);
|
||||
|
|
@ -32,15 +46,25 @@ class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
|
|||
$lookup = array();
|
||||
foreach ($params as $param) {
|
||||
list($key, $value) = explode('=', $param);
|
||||
$key = trim($key);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
if (isset($lookup[$key])) continue;
|
||||
if ($key !== 'opacity') continue;
|
||||
if (isset($lookup[$key])) {
|
||||
continue;
|
||||
}
|
||||
if ($key !== 'opacity') {
|
||||
continue;
|
||||
}
|
||||
$value = $this->intValidator->validate($value, $config, $context);
|
||||
if ($value === false) continue;
|
||||
$int = (int) $value;
|
||||
if ($int > 100) $value = '100';
|
||||
if ($int < 0) $value = '0';
|
||||
if ($value === false) {
|
||||
continue;
|
||||
}
|
||||
$int = (int)$value;
|
||||
if ($int > 100) {
|
||||
$value = '100';
|
||||
}
|
||||
if ($int < 0) {
|
||||
$value = '0';
|
||||
}
|
||||
$ret_params[] = "$key=$value";
|
||||
$lookup[$key] = true;
|
||||
}
|
||||
|
|
@ -48,7 +72,6 @@ class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
|
|||
$ret_function = "$function($ret_parameters)";
|
||||
return $ret_function;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -7,8 +7,8 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
{
|
||||
|
||||
/**
|
||||
* Local copy of component validators.
|
||||
*
|
||||
* Local copy of validators
|
||||
* @type HTMLPurifier_AttrDef[]
|
||||
* @note If we moved specific CSS property definitions to their own
|
||||
* classes instead of having them be assembled at run time by
|
||||
* CSSDefinition, this wouldn't be necessary. We'd instantiate
|
||||
|
|
@ -16,18 +16,28 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
*/
|
||||
protected $info = array();
|
||||
|
||||
public function __construct($config) {
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['font-style'] = $def->info['font-style'];
|
||||
$this->info['font-style'] = $def->info['font-style'];
|
||||
$this->info['font-variant'] = $def->info['font-variant'];
|
||||
$this->info['font-weight'] = $def->info['font-weight'];
|
||||
$this->info['font-size'] = $def->info['font-size'];
|
||||
$this->info['line-height'] = $def->info['line-height'];
|
||||
$this->info['font-family'] = $def->info['font-family'];
|
||||
$this->info['font-weight'] = $def->info['font-weight'];
|
||||
$this->info['font-size'] = $def->info['font-size'];
|
||||
$this->info['line-height'] = $def->info['line-height'];
|
||||
$this->info['font-family'] = $def->info['font-family'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
static $system_fonts = array(
|
||||
'caption' => true,
|
||||
'icon' => true,
|
||||
|
|
@ -39,7 +49,9 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
|
||||
// regular pre-processing
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
if ($string === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if it's one of the keywords
|
||||
$lowercase_string = strtolower($string);
|
||||
|
|
@ -54,15 +66,20 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
$final = ''; // output
|
||||
|
||||
for ($i = 0, $size = count($bits); $i < $size; $i++) {
|
||||
if ($bits[$i] === '') continue;
|
||||
if ($bits[$i] === '') {
|
||||
continue;
|
||||
}
|
||||
switch ($stage) {
|
||||
|
||||
// attempting to catch font-style, font-variant or font-weight
|
||||
case 0:
|
||||
case 0: // attempting to catch font-style, font-variant or font-weight
|
||||
foreach ($stage_1 as $validator_name) {
|
||||
if (isset($caught[$validator_name])) continue;
|
||||
if (isset($caught[$validator_name])) {
|
||||
continue;
|
||||
}
|
||||
$r = $this->info[$validator_name]->validate(
|
||||
$bits[$i], $config, $context);
|
||||
$bits[$i],
|
||||
$config,
|
||||
$context
|
||||
);
|
||||
if ($r !== false) {
|
||||
$final .= $r . ' ';
|
||||
$caught[$validator_name] = true;
|
||||
|
|
@ -70,15 +87,17 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
}
|
||||
}
|
||||
// all three caught, continue on
|
||||
if (count($caught) >= 3) $stage = 1;
|
||||
if ($r !== false) break;
|
||||
|
||||
// attempting to catch font-size and perhaps line-height
|
||||
case 1:
|
||||
if (count($caught) >= 3) {
|
||||
$stage = 1;
|
||||
}
|
||||
if ($r !== false) {
|
||||
break;
|
||||
}
|
||||
case 1: // attempting to catch font-size and perhaps line-height
|
||||
$found_slash = false;
|
||||
if (strpos($bits[$i], '/') !== false) {
|
||||
list($font_size, $line_height) =
|
||||
explode('/', $bits[$i]);
|
||||
explode('/', $bits[$i]);
|
||||
if ($line_height === '') {
|
||||
// ooh, there's a space after the slash!
|
||||
$line_height = false;
|
||||
|
|
@ -89,14 +108,19 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
$line_height = false;
|
||||
}
|
||||
$r = $this->info['font-size']->validate(
|
||||
$font_size, $config, $context);
|
||||
$font_size,
|
||||
$config,
|
||||
$context
|
||||
);
|
||||
if ($r !== false) {
|
||||
$final .= $r;
|
||||
// attempt to catch line-height
|
||||
if ($line_height === false) {
|
||||
// we need to scroll forward
|
||||
for ($j = $i + 1; $j < $size; $j++) {
|
||||
if ($bits[$j] === '') continue;
|
||||
if ($bits[$j] === '') {
|
||||
continue;
|
||||
}
|
||||
if ($bits[$j] === '/') {
|
||||
if ($found_slash) {
|
||||
return false;
|
||||
|
|
@ -116,7 +140,10 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
if ($found_slash) {
|
||||
$i = $j;
|
||||
$r = $this->info['line-height']->validate(
|
||||
$line_height, $config, $context);
|
||||
$line_height,
|
||||
$config,
|
||||
$context
|
||||
);
|
||||
if ($r !== false) {
|
||||
$final .= '/' . $r;
|
||||
}
|
||||
|
|
@ -126,13 +153,14 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
break;
|
||||
}
|
||||
return false;
|
||||
|
||||
// attempting to catch font-family
|
||||
case 2:
|
||||
case 2: // attempting to catch font-family
|
||||
$font_family =
|
||||
implode(' ', array_slice($bits, $i, $size - $i));
|
||||
$r = $this->info['font-family']->validate(
|
||||
$font_family, $config, $context);
|
||||
$font_family,
|
||||
$config,
|
||||
$context
|
||||
);
|
||||
if ($r !== false) {
|
||||
$final .= $r . ' ';
|
||||
// processing completed successfully
|
||||
|
|
@ -143,7 +171,6 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a font family list according to CSS spec
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $mask = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->mask = '_- ';
|
||||
for ($c = 'a'; $c <= 'z'; $c++) {
|
||||
$this->mask .= $c;
|
||||
}
|
||||
for ($c = 'A'; $c <= 'Z'; $c++) {
|
||||
$this->mask .= $c;
|
||||
}
|
||||
for ($c = '0'; $c <= '9'; $c++) {
|
||||
$this->mask .= $c;
|
||||
} // cast-y, but should be fine
|
||||
// special bytes used by UTF-8
|
||||
for ($i = 0x80; $i <= 0xFF; $i++) {
|
||||
// We don't bother excluding invalid bytes in this range,
|
||||
// because the our restriction of well-formed UTF-8 will
|
||||
// prevent these from ever occurring.
|
||||
$this->mask .= chr($i);
|
||||
}
|
||||
|
||||
/*
|
||||
PHP's internal strcspn implementation is
|
||||
O(length of string * length of mask), making it inefficient
|
||||
for large masks. However, it's still faster than
|
||||
preg_match 8)
|
||||
for (p = s1;;) {
|
||||
spanp = s2;
|
||||
do {
|
||||
if (*spanp == c || p == s1_end) {
|
||||
return p - s1;
|
||||
}
|
||||
} while (spanp++ < (s2_end - 1));
|
||||
c = *++p;
|
||||
}
|
||||
*/
|
||||
// possible optimization: invert the mask.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
static $generic_names = array(
|
||||
'serif' => true,
|
||||
'sans-serif' => true,
|
||||
'monospace' => true,
|
||||
'fantasy' => true,
|
||||
'cursive' => true
|
||||
);
|
||||
$allowed_fonts = $config->get('CSS.AllowedFonts');
|
||||
|
||||
// assume that no font names contain commas in them
|
||||
$fonts = explode(',', $string);
|
||||
$final = '';
|
||||
foreach ($fonts as $font) {
|
||||
$font = trim($font);
|
||||
if ($font === '') {
|
||||
continue;
|
||||
}
|
||||
// match a generic name
|
||||
if (isset($generic_names[$font])) {
|
||||
if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
|
||||
$final .= $font . ', ';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// match a quoted name
|
||||
if ($font[0] === '"' || $font[0] === "'") {
|
||||
$length = strlen($font);
|
||||
if ($length <= 2) {
|
||||
continue;
|
||||
}
|
||||
$quote = $font[0];
|
||||
if ($font[$length - 1] !== $quote) {
|
||||
continue;
|
||||
}
|
||||
$font = substr($font, 1, $length - 2);
|
||||
}
|
||||
|
||||
$font = $this->expandCSSEscape($font);
|
||||
|
||||
// $font is a pure representation of the font name
|
||||
|
||||
if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctype_alnum($font) && $font !== '') {
|
||||
// very simple font, allow it in unharmed
|
||||
$final .= $font . ', ';
|
||||
continue;
|
||||
}
|
||||
|
||||
// bugger out on whitespace. form feed (0C) really
|
||||
// shouldn't show up regardless
|
||||
$font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
|
||||
|
||||
// Here, there are various classes of characters which need
|
||||
// to be treated differently:
|
||||
// - Alphanumeric characters are essentially safe. We
|
||||
// handled these above.
|
||||
// - Spaces require quoting, though most parsers will do
|
||||
// the right thing if there aren't any characters that
|
||||
// can be misinterpreted
|
||||
// - Dashes rarely occur, but they fairly unproblematic
|
||||
// for parsing/rendering purposes.
|
||||
// The above characters cover the majority of Western font
|
||||
// names.
|
||||
// - Arbitrary Unicode characters not in ASCII. Because
|
||||
// most parsers give little thought to Unicode, treatment
|
||||
// of these codepoints is basically uniform, even for
|
||||
// punctuation-like codepoints. These characters can
|
||||
// show up in non-Western pages and are supported by most
|
||||
// major browsers, for example: "MS 明朝" is a
|
||||
// legitimate font-name
|
||||
// <http://ja.wikipedia.org/wiki/MS_明朝>. See
|
||||
// the CSS3 spec for more examples:
|
||||
// <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
|
||||
// You can see live samples of these on the Internet:
|
||||
// <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック>
|
||||
// However, most of these fonts have ASCII equivalents:
|
||||
// for example, 'MS Mincho', and it's considered
|
||||
// professional to use ASCII font names instead of
|
||||
// Unicode font names. Thanks Takeshi Terada for
|
||||
// providing this information.
|
||||
// The following characters, to my knowledge, have not been
|
||||
// used to name font names.
|
||||
// - Single quote. While theoretically you might find a
|
||||
// font name that has a single quote in its name (serving
|
||||
// as an apostrophe, e.g. Dave's Scribble), I haven't
|
||||
// been able to find any actual examples of this.
|
||||
// Internet Explorer's cssText translation (which I
|
||||
// believe is invoked by innerHTML) normalizes any
|
||||
// quoting to single quotes, and fails to escape single
|
||||
// quotes. (Note that this is not IE's behavior for all
|
||||
// CSS properties, just some sort of special casing for
|
||||
// font-family). So a single quote *cannot* be used
|
||||
// safely in the font-family context if there will be an
|
||||
// innerHTML/cssText translation. Note that Firefox 3.x
|
||||
// does this too.
|
||||
// - Double quote. In IE, these get normalized to
|
||||
// single-quotes, no matter what the encoding. (Fun
|
||||
// fact, in IE8, the 'content' CSS property gained
|
||||
// support, where they special cased to preserve encoded
|
||||
// double quotes, but still translate unadorned double
|
||||
// quotes into single quotes.) So, because their
|
||||
// fixpoint behavior is identical to single quotes, they
|
||||
// cannot be allowed either. Firefox 3.x displays
|
||||
// single-quote style behavior.
|
||||
// - Backslashes are reduced by one (so \\ -> \) every
|
||||
// iteration, so they cannot be used safely. This shows
|
||||
// up in IE7, IE8 and FF3
|
||||
// - Semicolons, commas and backticks are handled properly.
|
||||
// - The rest of the ASCII punctuation is handled properly.
|
||||
// We haven't checked what browsers do to unadorned
|
||||
// versions, but this is not important as long as the
|
||||
// browser doesn't /remove/ surrounding quotes (as IE does
|
||||
// for HTML).
|
||||
//
|
||||
// With these results in hand, we conclude that there are
|
||||
// various levels of safety:
|
||||
// - Paranoid: alphanumeric, spaces and dashes(?)
|
||||
// - International: Paranoid + non-ASCII Unicode
|
||||
// - Edgy: Everything except quotes, backslashes
|
||||
// - NoJS: Standards compliance, e.g. sod IE. Note that
|
||||
// with some judicious character escaping (since certain
|
||||
// types of escaping doesn't work) this is theoretically
|
||||
// OK as long as innerHTML/cssText is not called.
|
||||
// We believe that international is a reasonable default
|
||||
// (that we will implement now), and once we do more
|
||||
// extensive research, we may feel comfortable with dropping
|
||||
// it down to edgy.
|
||||
|
||||
// Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of
|
||||
// str(c)spn assumes that the string was already well formed
|
||||
// Unicode (which of course it is).
|
||||
if (strspn($font, $this->mask) !== strlen($font)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Historical:
|
||||
// In the absence of innerHTML/cssText, these ugly
|
||||
// transforms don't pose a security risk (as \\ and \"
|
||||
// might--these escapes are not supported by most browsers).
|
||||
// We could try to be clever and use single-quote wrapping
|
||||
// when there is a double quote present, but I have choosen
|
||||
// not to implement that. (NOTE: you can reduce the amount
|
||||
// of escapes by one depending on what quoting style you use)
|
||||
// $font = str_replace('\\', '\\5C ', $font);
|
||||
// $font = str_replace('"', '\\22 ', $font);
|
||||
// $font = str_replace("'", '\\27 ', $font);
|
||||
|
||||
// font possibly with spaces, requires quoting
|
||||
$final .= "'$font', ";
|
||||
}
|
||||
$final = rtrim($final, ', ');
|
||||
if ($final === '') {
|
||||
return false;
|
||||
}
|
||||
return $final;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates based on {ident} CSS grammar production
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = trim($string);
|
||||
|
||||
// early abort: '' and '0' (strings that convert to false) are invalid
|
||||
if (!$string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
|
||||
if (!preg_match($pattern, $string)) {
|
||||
return false;
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -5,20 +5,34 @@
|
|||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
|
||||
{
|
||||
public $def, $allow;
|
||||
/**
|
||||
* @type HTMLPurifier_AttrDef
|
||||
*/
|
||||
public $def;
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $allow;
|
||||
|
||||
/**
|
||||
* @param $def Definition to wrap
|
||||
* @param $allow Whether or not to allow !important
|
||||
* @param HTMLPurifier_AttrDef $def Definition to wrap
|
||||
* @param bool $allow Whether or not to allow !important
|
||||
*/
|
||||
public function __construct($def, $allow = false) {
|
||||
public function __construct($def, $allow = false)
|
||||
{
|
||||
$this->def = $def;
|
||||
$this->allow = $allow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts and removes !important if necessary
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context) {
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
// test for ! and important tokens
|
||||
$string = trim($string);
|
||||
$is_important = false;
|
||||
|
|
@ -32,7 +46,9 @@ class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
|
|||
}
|
||||
}
|
||||
$string = $this->def->validate($string, $config, $context);
|
||||
if ($this->allow && $is_important) $string .= ' !important';
|
||||
if ($this->allow && $is_important) {
|
||||
$string .= ' !important';
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Represents a Length as defined by CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_Length|string
|
||||
*/
|
||||
protected $min;
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_Length|string
|
||||
*/
|
||||
protected $max;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
|
||||
* @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable.
|
||||
*/
|
||||
public function __construct($min = null, $max = null)
|
||||
{
|
||||
$this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
|
||||
$this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
// Optimizations
|
||||
if ($string === '') {
|
||||
return false;
|
||||
}
|
||||
if ($string === '0') {
|
||||
return '0';
|
||||
}
|
||||
if (strlen($string) === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$length = HTMLPurifier_Length::make($string);
|
||||
if (!$length->isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->min) {
|
||||
$c = $length->compareTo($this->min);
|
||||
if ($c === false) {
|
||||
return false;
|
||||
}
|
||||
if ($c < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($this->max) {
|
||||
$c = $length->compareTo($this->max);
|
||||
if ($c === false) {
|
||||
return false;
|
||||
}
|
||||
if ($c > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $length->toString();
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates shorthand CSS property list-style.
|
||||
* @warning Does not support url tokens that have internal spaces.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Local copy of validators.
|
||||
* @type HTMLPurifier_AttrDef[]
|
||||
* @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
|
||||
*/
|
||||
protected $info;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['list-style-type'] = $def->info['list-style-type'];
|
||||
$this->info['list-style-position'] = $def->info['list-style-position'];
|
||||
$this->info['list-style-image'] = $def->info['list-style-image'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
// regular pre-processing
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// assumes URI doesn't have spaces in it
|
||||
$bits = explode(' ', strtolower($string)); // bits to process
|
||||
|
||||
$caught = array();
|
||||
$caught['type'] = false;
|
||||
$caught['position'] = false;
|
||||
$caught['image'] = false;
|
||||
|
||||
$i = 0; // number of catches
|
||||
$none = false;
|
||||
|
||||
foreach ($bits as $bit) {
|
||||
if ($i >= 3) {
|
||||
return;
|
||||
} // optimization bit
|
||||
if ($bit === '') {
|
||||
continue;
|
||||
}
|
||||
foreach ($caught as $key => $status) {
|
||||
if ($status !== false) {
|
||||
continue;
|
||||
}
|
||||
$r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
|
||||
if ($r === false) {
|
||||
continue;
|
||||
}
|
||||
if ($r === 'none') {
|
||||
if ($none) {
|
||||
continue;
|
||||
} else {
|
||||
$none = true;
|
||||
}
|
||||
if ($key == 'image') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$caught[$key] = $r;
|
||||
$i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$i) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
|
||||
// construct type
|
||||
if ($caught['type']) {
|
||||
$ret[] = $caught['type'];
|
||||
}
|
||||
|
||||
// construct image
|
||||
if ($caught['image']) {
|
||||
$ret[] = $caught['image'];
|
||||
}
|
||||
|
||||
// construct position
|
||||
if ($caught['position']) {
|
||||
$ret[] = $caught['position'];
|
||||
}
|
||||
|
||||
if (empty($ret)) {
|
||||
return false;
|
||||
}
|
||||
return implode(' ', $ret);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -13,9 +13,9 @@
|
|||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of component definition to defer validation to.
|
||||
* @type HTMLPurifier_AttrDef
|
||||
* @todo Make protected
|
||||
*/
|
||||
public $single;
|
||||
|
|
@ -27,32 +27,45 @@ class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
|
|||
public $max;
|
||||
|
||||
/**
|
||||
* @param $single HTMLPurifier_AttrDef to multiply
|
||||
* @param $max Max number of values allowed (usually four)
|
||||
* @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
|
||||
* @param int $max Max number of values allowed (usually four)
|
||||
*/
|
||||
public function __construct($single, $max = 4) {
|
||||
public function __construct($single, $max = 4)
|
||||
{
|
||||
$this->single = $single;
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->mungeRgb($this->parseCDATA($string));
|
||||
if ($string === '') {
|
||||
return false;
|
||||
}
|
||||
$parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
|
||||
$length = count($parts);
|
||||
$final = '';
|
||||
for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
|
||||
if (ctype_space($parts[$i])) continue;
|
||||
if (ctype_space($parts[$i])) {
|
||||
continue;
|
||||
}
|
||||
$result = $this->single->validate($parts[$i], $config, $context);
|
||||
if ($result !== false) {
|
||||
$final .= $result . ' ';
|
||||
$num++;
|
||||
}
|
||||
}
|
||||
if ($final === '') return false;
|
||||
if ($final === '') {
|
||||
return false;
|
||||
}
|
||||
return rtrim($final);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -7,32 +7,44 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
|
|||
{
|
||||
|
||||
/**
|
||||
* Bool indicating whether or not only positive values allowed.
|
||||
* Indicates whether or not only positive values are allowed.
|
||||
* @type bool
|
||||
*/
|
||||
protected $non_negative = false;
|
||||
|
||||
/**
|
||||
* @param $non_negative Bool indicating whether negatives are forbidden
|
||||
* @param bool $non_negative indicates whether negatives are forbidden
|
||||
*/
|
||||
public function __construct($non_negative = false) {
|
||||
public function __construct($non_negative = false)
|
||||
{
|
||||
$this->non_negative = $non_negative;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return string|bool
|
||||
* @warning Some contexts do not pass $config, $context. These
|
||||
* variables should not be used without checking HTMLPurifier_Length
|
||||
*/
|
||||
public function validate($number, $config, $context) {
|
||||
|
||||
public function validate($number, $config, $context)
|
||||
{
|
||||
$number = $this->parseCDATA($number);
|
||||
|
||||
if ($number === '') return false;
|
||||
if ($number === '0') return '0';
|
||||
if ($number === '') {
|
||||
return false;
|
||||
}
|
||||
if ($number === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
$sign = '';
|
||||
switch ($number[0]) {
|
||||
case '-':
|
||||
if ($this->non_negative) return false;
|
||||
if ($this->non_negative) {
|
||||
return false;
|
||||
}
|
||||
$sign = '-';
|
||||
case '+':
|
||||
$number = substr($number, 1);
|
||||
|
|
@ -44,14 +56,20 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
|
|||
}
|
||||
|
||||
// Period is the only non-numeric character allowed
|
||||
if (strpos($number, '.') === false) return false;
|
||||
if (strpos($number, '.') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($left, $right) = explode('.', $number, 2);
|
||||
|
||||
if ($left === '' && $right === '') return false;
|
||||
if ($left !== '' && !ctype_digit($left)) return false;
|
||||
if ($left === '' && $right === '') {
|
||||
return false;
|
||||
}
|
||||
if ($left !== '' && !ctype_digit($left)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$left = ltrim($left, '0');
|
||||
$left = ltrim($left, '0');
|
||||
$right = rtrim($right, '0');
|
||||
|
||||
if ($right === '') {
|
||||
|
|
@ -59,11 +77,8 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
|
|||
} elseif (!ctype_digit($right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $sign . $left . '.' . $right;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a Percentage as defined by the CSS spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance to defer number validation to.
|
||||
* @type HTMLPurifier_AttrDef_CSS_Number
|
||||
*/
|
||||
protected $number_def;
|
||||
|
||||
/**
|
||||
* @param bool $non_negative Whether to forbid negative values
|
||||
*/
|
||||
public function __construct($non_negative = false)
|
||||
{
|
||||
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
if ($string === '') {
|
||||
return false;
|
||||
}
|
||||
$length = strlen($string);
|
||||
if ($length === 1) {
|
||||
return false;
|
||||
}
|
||||
if ($string[$length - 1] !== '%') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$number = substr($string, 0, $length - 1);
|
||||
$number = $this->number_def->validate($number, $config, $context);
|
||||
|
||||
if ($number === false) {
|
||||
return false;
|
||||
}
|
||||
return "$number%";
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -8,8 +8,14 @@
|
|||
class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
static $allowed_values = array(
|
||||
'line-through' => true,
|
||||
'overline' => true,
|
||||
|
|
@ -18,7 +24,9 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
|
|||
|
||||
$string = strtolower($this->parseCDATA($string));
|
||||
|
||||
if ($string === 'none') return $string;
|
||||
if ($string === 'none') {
|
||||
return $string;
|
||||
}
|
||||
|
||||
$parts = explode(' ', $string);
|
||||
$final = '';
|
||||
|
|
@ -28,11 +36,11 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
|
|||
}
|
||||
}
|
||||
$final = rtrim($final);
|
||||
if ($final === '') return false;
|
||||
if ($final === '') {
|
||||
return false;
|
||||
}
|
||||
return $final;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -12,25 +12,39 @@
|
|||
class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
|
||||
{
|
||||
|
||||
public function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(true); // always embedded
|
||||
}
|
||||
|
||||
public function validate($uri_string, $config, $context) {
|
||||
/**
|
||||
* @param string $uri_string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($uri_string, $config, $context)
|
||||
{
|
||||
// parse the URI out of the string and then pass it onto
|
||||
// the parent object
|
||||
|
||||
$uri_string = $this->parseCDATA($uri_string);
|
||||
if (strpos($uri_string, 'url(') !== 0) return false;
|
||||
if (strpos($uri_string, 'url(') !== 0) {
|
||||
return false;
|
||||
}
|
||||
$uri_string = substr($uri_string, 4);
|
||||
$new_length = strlen($uri_string) - 1;
|
||||
if ($uri_string[$new_length] != ')') return false;
|
||||
if ($uri_string[$new_length] != ')') {
|
||||
return false;
|
||||
}
|
||||
$uri = trim(substr($uri_string, 0, $new_length));
|
||||
|
||||
if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
|
||||
$quote = $uri[0];
|
||||
$new_length = strlen($uri) - 1;
|
||||
if ($uri[$new_length] !== $quote) return false;
|
||||
if ($uri[$new_length] !== $quote) {
|
||||
return false;
|
||||
}
|
||||
$uri = substr($uri, 1, $new_length - 1);
|
||||
}
|
||||
|
||||
|
|
@ -38,15 +52,23 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
|
|||
|
||||
$result = parent::validate($uri, $config, $context);
|
||||
|
||||
if ($result === false) return false;
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// extra sanity check; should have been done by URI
|
||||
$result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
|
||||
|
||||
// suspicious characters are ()'; we're going to percent encode
|
||||
// them for safety.
|
||||
$result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
|
||||
|
||||
// there's an extra bug where ampersands lose their escaping on
|
||||
// an innerHTML cycle, so a very unlucky query parameter could
|
||||
// then change the meaning of the URL. Unfortunately, there's
|
||||
// not much we can do about that...
|
||||
return "url(\"$result\")";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Dummy AttrDef that mimics another AttrDef, BUT it generates clones
|
||||
* with make.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
|
||||
{
|
||||
/**
|
||||
* What we're cloning.
|
||||
* @type HTMLPurifier_AttrDef
|
||||
*/
|
||||
protected $clone;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_AttrDef $clone
|
||||
*/
|
||||
public function __construct($clone)
|
||||
{
|
||||
$this->clone = $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $v
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($v, $config, $context)
|
||||
{
|
||||
return $this->clone->validate($v, $config, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return HTMLPurifier_AttrDef
|
||||
*/
|
||||
public function make($string)
|
||||
{
|
||||
return clone $this->clone;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -12,9 +12,10 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
|
|||
|
||||
/**
|
||||
* Lookup table of valid values.
|
||||
* @type array
|
||||
* @todo Make protected
|
||||
*/
|
||||
public $valid_values = array();
|
||||
public $valid_values = array();
|
||||
|
||||
/**
|
||||
* Bool indicating whether or not enumeration is case sensitive.
|
||||
|
|
@ -23,17 +24,23 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
|
|||
protected $case_sensitive = false; // values according to W3C spec
|
||||
|
||||
/**
|
||||
* @param $valid_values List of valid values
|
||||
* @param $case_sensitive Bool indicating whether or not case sensitive
|
||||
* @param array $valid_values List of valid values
|
||||
* @param bool $case_sensitive Whether or not case sensitive
|
||||
*/
|
||||
public function __construct(
|
||||
$valid_values = array(), $case_sensitive = false
|
||||
) {
|
||||
public function __construct($valid_values = array(), $case_sensitive = false)
|
||||
{
|
||||
$this->valid_values = array_flip($valid_values);
|
||||
$this->case_sensitive = $case_sensitive;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = trim($string);
|
||||
if (!$this->case_sensitive) {
|
||||
// we may want to do full case-insensitive libraries
|
||||
|
|
@ -45,11 +52,13 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
|
|||
}
|
||||
|
||||
/**
|
||||
* @param $string In form of comma-delimited list of case-insensitive
|
||||
* @param string $string In form of comma-delimited list of case-insensitive
|
||||
* valid values. Example: "foo,bar,baz". Prepend "s:" to make
|
||||
* case sensitive
|
||||
* @return HTMLPurifier_AttrDef_Enum
|
||||
*/
|
||||
public function make($string) {
|
||||
public function make($string)
|
||||
{
|
||||
if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
|
||||
$string = substr($string, 2);
|
||||
$sensitive = true;
|
||||
|
|
@ -59,7 +68,6 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
|
|||
$values = explode(',', $string);
|
||||
return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a boolean attribute
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $minimized = true;
|
||||
|
||||
/**
|
||||
* @param bool $name
|
||||
*/
|
||||
public function __construct($name = false)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string Name of attribute
|
||||
* @return HTMLPurifier_AttrDef_HTML_Bool
|
||||
*/
|
||||
public function make($string)
|
||||
{
|
||||
return new HTMLPurifier_AttrDef_HTML_Bool($string);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -5,7 +5,14 @@
|
|||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
|
||||
{
|
||||
protected function split($string, $config, $context) {
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function split($string, $config, $context)
|
||||
{
|
||||
// really, this twiddle should be lazy loaded
|
||||
$name = $config->getDefinition('HTML')->doctype->name;
|
||||
if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
|
||||
|
|
@ -14,13 +21,20 @@ class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
|
|||
return preg_split('/\s+/', $string);
|
||||
}
|
||||
}
|
||||
protected function filter($tokens, $config, $context) {
|
||||
|
||||
/**
|
||||
* @param array $tokens
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array
|
||||
*/
|
||||
protected function filter($tokens, $config, $context)
|
||||
{
|
||||
$allowed = $config->get('Attr.AllowedClasses');
|
||||
$forbidden = $config->get('Attr.ForbiddenClasses');
|
||||
$ret = array();
|
||||
foreach ($tokens as $token) {
|
||||
if (
|
||||
($allowed === null || isset($allowed[$token])) &&
|
||||
if (($allowed === null || isset($allowed[$token])) &&
|
||||
!isset($forbidden[$token]) &&
|
||||
// We need this O(n) check because of PHP's array
|
||||
// implementation that casts -0 to 0.
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validates a color according to the HTML spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
static $colors = null;
|
||||
if ($colors === null) {
|
||||
$colors = $config->get('Core.ColorKeywords');
|
||||
}
|
||||
|
||||
$string = trim($string);
|
||||
|
||||
if (empty($string)) {
|
||||
return false;
|
||||
}
|
||||
$lower = strtolower($string);
|
||||
if (isset($colors[$lower])) {
|
||||
return $colors[$lower];
|
||||
}
|
||||
if ($string[0] === '#') {
|
||||
$hex = substr($string, 1);
|
||||
} else {
|
||||
$hex = $string;
|
||||
}
|
||||
|
||||
$length = strlen($hex);
|
||||
if ($length !== 3 && $length !== 6) {
|
||||
return false;
|
||||
}
|
||||
if (!ctype_xdigit($hex)) {
|
||||
return false;
|
||||
}
|
||||
if ($length === 3) {
|
||||
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
|
||||
}
|
||||
return "#$hex";
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Special-case enum attribute definition that lazy loads allowed frame targets
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
|
||||
{
|
||||
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
public $valid_values = false; // uninitialized value
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
protected $case_sensitive = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
if ($this->valid_values === false) {
|
||||
$this->valid_values = $config->get('Attr.AllowedFrameTargets');
|
||||
}
|
||||
return parent::validate($string, $config, $context);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue