diff --git a/.gitignore b/.gitignore
index 102173e832..57ea182b2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,11 @@
-avatar/*
-files/*
-file/*
-local/*
-_darcs/*
-logs/*
-log/*
-run/*
+avatar/
+files/
+file/
+local/
+_darcs/
+logs/
+log/
+run/
config.php
.htaccess
httpd.conf
diff --git a/CONFIGURE b/CONFIGURE
index 92ae78204e..3fbc83e0bc 100644
--- a/CONFIGURE
+++ b/CONFIGURE
@@ -497,9 +497,9 @@ Profile management.
biolimit: max character length of bio; 0 means no limit; null means to use
the site text limit default.
-backup: whether users can backup their own profiles. Defaults to true.
+backup: whether users can backup their own profiles. Defaults to false.
restore: whether users can restore their profiles from backup files. Defaults
- to true.
+ to false.
delete: whether users can delete their own accounts. Defaults to false.
move: whether users can move their accounts to another server. Defaults
to true.
diff --git a/INSTALL b/INSTALL
index ff755f2471..2ec73cb397 100644
--- a/INSTALL
+++ b/INSTALL
@@ -26,16 +26,12 @@ PHP modules
The following software packages are *required* for this software to
run correctly.
-- PHP 5.5+ For newer versions, some functions that are used may be
- disabled by default, such as the pcntl_* family. See the
- section on 'Queues and daemons' for more information.
-- MariaDB 5+ GNU Social uses, by default, a MariaDB server for data
- storage. Versions 5.x and 10.x have both reportedly
- worked well. It is also possible to run MySQL 5.5+.
-- Web server Apache, lighttpd and nginx will all work. CGI mode is
- recommended and also some variant of 'suexec' (or a
- proper setup php-fpm pool)
- NOTE: mod_rewrite or its equivalent is extremely useful.
+- PHP 5.6+ PHP7.x is also supported.
+- MariaDB 5+ MariaDB 10.x is also supported.
+- Web server Apache, lighttpd and nginx will all work, see sample
+ configuration files in the web root. Please use PHP-FPM
+ and configure mod_rewrite (or equivalent) for an optimal
+ experience.
Your PHP installation must include the following PHP extensions for a
functional setup of GNU Social:
@@ -49,22 +45,22 @@ functional setup of GNU Social:
- php5-mysqlnd The native driver for PHP5 MariaDB connections. If you
use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough.
-Or, for PHP7, some or all of these will be necessary. PHP7 support is still
-experimental and not necessarily working:
+Or, for PHP7, some or all of these will be necessary. PHP7 works and on
+the development servers we are successful running PHP7.2. This is a good
+list of PHP modules you will want installed with PHP7:
php7.0-bcmath
php7.0-curl
php7.0-exif
php7.0-gd
php7.0-intl
php7.0-mbstring
- php7.0-mysqlnd
+ php7.0-mysql
php7.0-opcache
php7.0-readline
php7.0-xmlwriter
-The above package names are for Debian based systems. In the case of
-Arch Linux, PHP is compiled with support for most extensions but they
-require manual enabling in the relevant php.ini file (mostly php5-gmp).
+NOTE: In Arch Linux, at least PHP5 requires manual enabling in the
+relevant php.ini for some modules, most notably 'gmp'.
Better performance
------------------
@@ -74,19 +70,10 @@ For some functionality, you will also need the following extensions:
- opcache Improves performance a _lot_. Included in PHP, must be
enabled manually in php.ini for most distributions. Find
and set at least: opcache.enable=1
-- mailparse Efficient parsing of email requires this extension.
- Submission by email or SMS-over-email uses this.
-- sphinx A client for the sphinx server, an alternative to MySQL
- or Postgresql fulltext search. You will also need a
- Sphinx server to serve the search queries.
- gettext For multiple languages. Default on many PHP installs;
will be emulated if not present.
- exif For thumbnails to be properly oriented.
-You may also experience better performance from your site if you configure
-a PHP cache/accelerator. Most distributions come with "opcache" support.
-Enable it in your php.ini where it is documented together with its settings.
-
Installation
============
diff --git a/README.md b/README.md
index 7dfe8ae05c..5822b366ff 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,7 @@ So far it includes the following changes:
- Backing up a user's account is more and more complete.
- Emojis 😸 (utf8mb4 support)
+- Fully qualified group mentions (!group@example.com)
The last release, 1.1.3, gave us these improvements:
diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php
index f2c70f5452..de00325494 100644
--- a/actions/apistatusesupdate.php
+++ b/actions/apistatusesupdate.php
@@ -46,7 +46,7 @@
/api/statuses/update.:format
@par Formats (:format)
- xml, json
+ xml, json, atom
@par HTTP Method(s)
POST
@@ -339,6 +339,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$this->showSingleXmlStatus($this->notice);
} elseif ($this->format == 'json') {
$this->show_single_json_status($this->notice);
+ } elseif ($this->format == 'atom') {
+ $this->showSingleAtomStatus($this->notice);
}
}
}
diff --git a/actions/blockedfromgroup.php b/actions/blockedfromgroup.php
index a2e7c5767f..d2873fe467 100644
--- a/actions/blockedfromgroup.php
+++ b/actions/blockedfromgroup.php
@@ -151,7 +151,7 @@ class GroupBlockList extends ProfileList
$this->group = $group;
}
- function newListItem($profile)
+ function newListItem(Profile $profile)
{
return new GroupBlockListItem($profile, $this->group, $this->action);
}
diff --git a/actions/groupqueue.php b/actions/groupqueue.php
index c50eff36f8..98da77e01a 100644
--- a/actions/groupqueue.php
+++ b/actions/groupqueue.php
@@ -153,7 +153,7 @@ class GroupqueueAction extends GroupAction
// @todo FIXME: documentation missing.
class GroupQueueList extends GroupMemberList
{
- function newListItem($profile)
+ function newListItem(Profile $profile)
{
return new GroupQueueListItem($profile, $this->group, $this->action);
}
diff --git a/actions/newnotice.php b/actions/newnotice.php
index 5aa76a94e9..170e5bcdf8 100644
--- a/actions/newnotice.php
+++ b/actions/newnotice.php
@@ -47,6 +47,8 @@ class NewnoticeAction extends FormAction
{
protected $form = 'Notice';
+ protected $inreplyto = null;
+
/**
* Title of the page
*
@@ -75,6 +77,11 @@ class NewnoticeAction extends FormAction
}
}
+ if ($this->int('inreplyto')) {
+ // Throws exception if the inreplyto Notice is given but not found.
+ $this->inreplyto = Notice::getByID($this->int('inreplyto'));
+ }
+
// Backwards compatibility for "share this" widget things.
// If no 'content', use 'status_textarea'
$this->formOpts['content'] = $this->trimmed('content') ?: $this->trimmed('status_textarea');
@@ -132,13 +139,6 @@ class NewnoticeAction extends FormAction
return;
}
- if ($this->int('inreplyto')) {
- // Throws exception if the inreplyto Notice is given but not found.
- $parent = Notice::getByID($this->int('inreplyto'));
- } else {
- $parent = null;
- }
-
$act = new Activity();
$act->verb = ActivityVerb::POST;
$act->time = time();
@@ -157,9 +157,9 @@ class NewnoticeAction extends FormAction
$act->context = new ActivityContext();
- if ($parent instanceof Notice) {
- $act->context->replyToID = $parent->getUri();
- $act->context->replyToUrl = $parent->getUrl(true); // maybe we don't have to send true here to force a URL?
+ if ($this->inreplyto instanceof Notice) {
+ $act->context->replyToID = $this->inreplyto->getUri();
+ $act->context->replyToUrl = $this->inreplyto->getUrl(true); // maybe we don't have to send true here to force a URL?
}
if ($this->scoped->shareLocation()) {
@@ -188,14 +188,14 @@ class NewnoticeAction extends FormAction
// FIXME: We should be able to get the attentions from common_render_content!
// and maybe even directly save whether they're local or not!
- $act->context->attention = common_get_attentions($content, $this->scoped, $parent);
+ $act->context->attention = common_get_attentions($content, $this->scoped, $this->inreplyto);
// $options gets filled with possible scoping settings
ToSelector::fillActivity($this, $act, $options);
$actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE;
- $actobj->content = common_render_content($content, $this->scoped, $parent);
+ $actobj->content = common_render_content($content, $this->scoped, $this->inreplyto);
// Finally add the activity object to our activity
$act->objects[] = $actobj;
@@ -224,6 +224,9 @@ class NewnoticeAction extends FormAction
if ($this->getInfo() && $this->stored instanceof Notice) {
$this->showNotice($this->stored);
} elseif (!$this->getError()) {
+ if (!GNUsocial::isAjax() && $this->inreplyto instanceof Notice) {
+ $this->showNotice($this->inreplyto);
+ }
parent::showContent();
}
}
diff --git a/actions/noticesearch.php b/actions/noticesearch.php
index 2886700f6a..0d6fb51fb4 100644
--- a/actions/noticesearch.php
+++ b/actions/noticesearch.php
@@ -185,7 +185,7 @@ class SearchNoticeList extends NoticeList {
$this->terms = $terms;
}
- function newListItem($notice)
+ function newListItem(Notice $notice)
{
return new SearchNoticeListItem($notice, $this->out, $this->terms);
}
diff --git a/actions/peopletagged.php b/actions/peopletagged.php
index 1b0f897c11..db2420a8a3 100644
--- a/actions/peopletagged.php
+++ b/actions/peopletagged.php
@@ -167,7 +167,7 @@ class PeopletagMemberList extends ProfileList
$this->peopletag = $peopletag;
}
- function newListItem($profile)
+ function newListItem(Profile $profile)
{
return new PeopletagMemberListItem($profile, $this->peopletag, $this->action);
}
diff --git a/actions/peopletagsubscribers.php b/actions/peopletagsubscribers.php
index e5be8a3ff4..927cf66e64 100644
--- a/actions/peopletagsubscribers.php
+++ b/actions/peopletagsubscribers.php
@@ -167,7 +167,7 @@ class PeopletagSubscriberList extends ProfileList
$this->peopletag = $peopletag;
}
- function newListItem($profile)
+ function newListItem(Profile $profile)
{
return new PeopletagSubscriberListItem($profile, $this->peopletag, $this->action);
}
diff --git a/actions/showstream.php b/actions/showstream.php
index 1e70ecd3ac..79a3706d77 100644
--- a/actions/showstream.php
+++ b/actions/showstream.php
@@ -113,6 +113,18 @@ class ShowstreamAction extends NoticestreamAction
$this->target->getNickname(), $this->tag)));
}
+ if (!$this->target->isLocal()) {
+ // remote profiles at least have Atom, but we can't guarantee anything else
+ return array(
+ new Feed(Feed::ATOM,
+ $this->target->getAtomFeed(),
+ // TRANS: Title for link to notice feed.
+ // TRANS: %s is a user nickname.
+ sprintf(_('Notice feed for %s (Atom)'),
+ $this->target->getNickname()))
+ );
+ }
+
return array(new Feed(Feed::JSON,
common_local_url('ApiTimelineUser',
array(
@@ -139,10 +151,7 @@ class ShowstreamAction extends NoticestreamAction
sprintf(_('Notice feed for %s (RSS 2.0)'),
$this->target->getNickname())),
new Feed(Feed::ATOM,
- common_local_url('ApiTimelineUser',
- array(
- 'id' => $this->target->getID(),
- 'format' => 'atom')),
+ $this->target->getAtomFeed(),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
@@ -221,13 +230,14 @@ class ShowstreamAction extends NoticestreamAction
$this->showEmptyListMessage();
}
- $args = array('nickname' => $this->target->getNickname());
+ // either nickname or id will be used, depending on which action (showstream, userbyid...)
+ $args = array('nickname' => $this->target->getNickname(), 'id' => $this->target->getID());
if (!empty($this->tag))
{
$args['tag'] = $this->tag;
}
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
- 'showstream', $args);
+ $this->getActionName(), $args);
}
function showAnonymousMessage()
diff --git a/classes/Conversation.php b/classes/Conversation.php
index 1dba2c1f4a..d18321deba 100644
--- a/classes/Conversation.php
+++ b/classes/Conversation.php
@@ -36,6 +36,7 @@ class Conversation extends Managed_DataObject
public $__table = 'conversation'; // table name
public $id; // int(4) primary_key not_null auto_increment
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
+ public $url; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
@@ -45,6 +46,7 @@ class Conversation extends Managed_DataObject
'fields' => array(
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'),
'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 191, 'description' => 'URI of the conversation'),
+ 'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'Resolvable URL, preferrably remote (local can be generated on the fly)'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
),
@@ -89,15 +91,21 @@ class Conversation extends Managed_DataObject
*
* @return Conversation the new conversation DO
*/
- static function create($uri=null, $created=null)
+ static function create(ActivityContext $ctx=null, $created=null)
{
// Be aware that the Notice does not have an id yet since it's not inserted!
$conv = new Conversation();
$conv->created = $created ?: common_sql_now();
- $conv->uri = $uri ?: sprintf('%s%s=%s:%s=%s',
+ if ($ctx instanceof ActivityContext) {
+ $conv->uri = $ctx->conversation;
+ $conv->url = $ctx->conversation_url;
+ } else {
+ $conv->uri = sprintf('%s%s=%s:%s=%s',
TagURI::mint(),
'objectType', 'thread',
'nonce', common_random_hexstr(8));
+ $conv->url = null; // locally generated Conversation objects don't get static URLs stored
+ }
// This insert throws exceptions on failure
$conv->insert();
diff --git a/classes/File.php b/classes/File.php
index b0da3f09f3..67b87efd0d 100644
--- a/classes/File.php
+++ b/classes/File.php
@@ -194,10 +194,14 @@ class File extends Managed_DataObject
}
$redir = File_redirection::where($given_url);
- $file = $redir->getFile();
-
- if (!$file instanceof File || empty($file->id)) {
+ try {
+ $file = $redir->getFile();
+ } catch (EmptyPkeyValueException $e) {
+ common_log(LOG_ERR, 'File_redirection::where gave object with empty file_id for given_url '._ve($given_url));
+ throw new ServerException('URL processing failed without new File object');
+ } catch (NoResultException $e) {
// This should not happen
+ common_log(LOG_ERR, 'File_redirection after discovery could still not return a File object.');
throw new ServerException('URL processing failed without new File object');
}
@@ -731,16 +735,18 @@ class File extends Managed_DataObject
$dupfile = new File();
// First we find file entries that would be duplicates of this when shortened
// ... and we'll just throw the dupes out the window for now! It's already so borken.
- $dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = "%1$s"', $file->shortenedurl));
+ $dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = %1$s', $dupfile->_quote($file->shortenedurl)));
// Leave one of the URLs in the database by using ->find(true) (fetches first entry)
if ($dupfile->find(true)) {
print "\nShortening url entry for $table id: {$file->id} [";
$orig = clone($dupfile);
+ $origurl = $dupfile->url; // save for logging purposes
$dupfile->url = $file->shortenedurl; // make sure it's only 191 chars from now on
$dupfile->update($orig);
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
// only start deleting with this fetch.
while($dupfile->fetch()) {
+ common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl));
print ".";
$dupfile->delete();
}
diff --git a/classes/File_redirection.php b/classes/File_redirection.php
index d1b266c90b..742a6143cc 100644
--- a/classes/File_redirection.php
+++ b/classes/File_redirection.php
@@ -445,8 +445,8 @@ class File_redirection extends Managed_DataObject
}
public function getFile() {
- if(empty($this->file) && $this->file_id) {
- $this->file = File::getKV('id', $this->file_id);
+ if (!$this->file instanceof File) {
+ $this->file = File::getByID($this->file_id);
}
return $this->file;
diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php
index b3757448ad..8388f12e72 100644
--- a/classes/Foreign_link.php
+++ b/classes/Foreign_link.php
@@ -89,7 +89,7 @@ class Foreign_link extends Managed_DataObject
return $flink;
}
- function set_flags($noticesend, $noticerecv, $replysync, $friendsync)
+ function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync)
{
if ($noticesend) {
$this->noticesync |= FOREIGN_NOTICE_SEND;
@@ -109,6 +109,12 @@ class Foreign_link extends Managed_DataObject
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY;
}
+ if ($repeatsync) {
+ $this->noticesync |= FOREIGN_NOTICE_SEND_REPEAT;
+ } else {
+ $this->noticesync &= ~FOREIGN_NOTICE_SEND_REPEAT;
+ }
+
if ($friendsync) {
$this->friendsync |= FOREIGN_FRIEND_RECV;
} else {
diff --git a/classes/Notice.php b/classes/Notice.php
index d5a0e5f6d2..15b96ae670 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -320,6 +320,21 @@ class Notice extends Managed_DataObject
}
}
+ public function getSelfLink()
+ {
+ if ($this->isLocal()) {
+ return common_local_url('ApiStatusesShow', array('id' => $this->getID(), 'format' => 'atom'));
+ }
+
+ $selfLink = $this->getPref('ostatus', 'self');
+
+ if (!common_valid_http_url($selfLink)) {
+ throw new InvalidUrlException($selfLink);
+ }
+
+ return $selfLink;
+ }
+
public function getObjectType($canonical=false) {
if (is_null($this->object_type) || $this->object_type==='') {
throw new NoObjectTypeException($this);
@@ -442,6 +457,7 @@ class Notice extends Managed_DataObject
static function saveNew($profile_id, $content, $source, array $options=null) {
$defaults = array('uri' => null,
'url' => null,
+ 'self' => null,
'conversation' => null, // URI of conversation
'reply_to' => null, // This will override convo URI if the parent is known
'repeat_of' => null, // This will override convo URI if the repeated notice is known
@@ -624,8 +640,13 @@ class Notice extends Managed_DataObject
} else {
// Conversation entry with specified URI was not found, so we must create it.
common_debug('Conversation URI not found, so we will create it with the URI given in the options to Notice::saveNew: '.$options['conversation']);
+ $convctx = new ActivityContext();
+ $convctx->conversation = $options['conversation'];
+ if (array_key_exists('conversation_url', $options)) {
+ $convctx->conversation_url = $options['conversation_url'];
+ }
// The insert in Conversation::create throws exception on failure
- $conv = Conversation::create($options['conversation'], $notice->created);
+ $conv = Conversation::create($convctx, $notice->created);
}
$notice->conversation = $conv->getID();
unset($conv);
@@ -706,6 +727,10 @@ class Notice extends Managed_DataObject
}
}
+ if ($self && common_valid_http_url($self)) {
+ $notice->setPref('ostatus', 'self', $self);
+ }
+
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves.
@@ -765,6 +790,9 @@ class Notice extends Managed_DataObject
// implied object
$options['uri'] = $act->id;
$options['url'] = $act->link;
+ if ($act->selfLink) {
+ $options['self'] = $act->selfLink;
+ }
} else {
$actobj = count($act->objects)===1 ? $act->objects[0] : null;
if (!is_null($actobj) && !empty($actobj->id)) {
@@ -775,6 +803,9 @@ class Notice extends Managed_DataObject
$options['url'] = $actobj->id;
}
}
+ if ($actobj->selfLink) {
+ $options['self'] = $actobj->selfLink;
+ }
}
$defaults = array(
@@ -784,6 +815,7 @@ class Notice extends Managed_DataObject
'reply_to' => null,
'repeat_of' => null,
'scope' => null,
+ 'self' => null,
'source' => 'unknown',
'tags' => array(),
'uri' => null,
@@ -921,7 +953,7 @@ class Notice extends Managed_DataObject
// Conversation entry with specified URI was not found, so we must create it.
common_debug('Conversation URI not found, so we will create it with the URI given in the context of the activity: '.$act->context->conversation);
// The insert in Conversation::create throws exception on failure
- $conv = Conversation::create($act->context->conversation, $stored->created);
+ $conv = Conversation::create($act->context, $stored->created);
}
$stored->conversation = $conv->getID();
unset($conv);
@@ -1020,6 +1052,14 @@ class Notice extends Managed_DataObject
throw new ServerException('Supposedly saved Notice has no ID.');
}
+ if ($self && common_valid_http_url($self)) {
+ $stored->setPref('ostatus', 'self', $self);
+ }
+
+ if ($self && common_valid_http_url($self)) {
+ $stored->setPref('ostatus', 'self', $self);
+ }
+
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves.
@@ -1578,12 +1618,12 @@ class Notice extends Managed_DataObject
if (common_config('group', 'addtag')) {
// we automatically add a tag for every group name, too
-
- $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->nickname),
- 'notice_id' => $this->id));
+ common_debug('Adding hashtag matching group nickname: '._ve($group->getNickname()));
+ $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->getNickname()),
+ 'notice_id' => $this->getID()));
if (is_null($tag)) {
- $this->saveTag($group->nickname);
+ $this->saveTag($group->getNickname());
}
}
@@ -2008,6 +2048,7 @@ class Notice extends Managed_DataObject
$conv = Conversation::getKV('id', $this->conversation);
if ($conv instanceof Conversation) {
$ctx->conversation = $conv->uri;
+ $ctx->conversation_url = $conv->url;
}
}
@@ -2070,9 +2111,12 @@ class Notice extends Managed_DataObject
}
}
+ try {
+ $act->selfLink = $this->getSelfLink();
+ } catch (InvalidUrlException $e) {
+ $act->selfLink = null;
+ }
if ($this->isLocal()) {
- $act->selfLink = common_local_url('ApiStatusesShow', array('id' => $this->id,
- 'format' => 'atom'));
$act->editLink = $act->selfLink;
}
@@ -2170,8 +2214,13 @@ class Notice extends Managed_DataObject
$object->title = sprintf('New %1$s by %2$s', ActivityObject::canonicalType($object->type), $this->getProfile()->getNickname());
$object->content = $this->getRendered();
$object->link = $this->getUrl();
+ try {
+ $object->selfLink = $this->getSelfLink();
+ } catch (InvalidUrlException $e) {
+ $object->selfLink = null;
+ }
- $object->extra[] = array('status_net', array('notice_id' => $this->id));
+ $object->extra[] = array('statusnet:notice_id', null, $this->id);
Event::handle('EndActivityObjectFromNotice', array($this, &$object));
}
@@ -3195,4 +3244,27 @@ class Notice extends Managed_DataObject
}
}
}
+
+ public function delPref($namespace, $topic) {
+ return Notice_prefs::setData($this, $namespace, $topic, null);
+ }
+
+ public function getPref($namespace, $topic, $default=null) {
+ // If you want an exception to be thrown, call Notice_prefs::getData directly
+ try {
+ return Notice_prefs::getData($this, $namespace, $topic, $default);
+ } catch (NoResultException $e) {
+ return null;
+ }
+ }
+
+ // The same as getPref but will fall back to common_config value for the same namespace/topic
+ public function getConfigPref($namespace, $topic)
+ {
+ return Notice_prefs::getConfigData($this, $namespace, $topic);
+ }
+
+ public function setPref($namespace, $topic, $data) {
+ return Notice_prefs::setData($this, $namespace, $topic, $data);
+ }
}
diff --git a/classes/Notice_prefs.php b/classes/Notice_prefs.php
new file mode 100644
index 0000000000..1bbf309da4
--- /dev/null
+++ b/classes/Notice_prefs.php
@@ -0,0 +1,172 @@
+.
+ *
+ * @category Data
+ * @package GNUsocial
+ * @author Mikael Nordfeldth
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://www.gnu.org/software/social/
+ */
+
+class Notice_prefs extends Managed_DataObject
+{
+ public $__table = 'notice_prefs'; // table name
+ public $notice_id; // int(4) primary_key not_null
+ public $namespace; // varchar(191) not_null
+ public $topic; // varchar(191) not_null
+ public $data; // text
+ public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00
+ public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
+
+ public static function schemaDef()
+ {
+ return array(
+ 'fields' => array(
+ 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'),
+ 'namespace' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'namespace, like pluginname or category'),
+ 'topic' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'preference key, i.e. description, age...'),
+ 'data' => array('type' => 'blob', 'description' => 'topic data, may be anything'),
+ 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
+ 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
+ ),
+ 'primary key' => array('notice_id', 'namespace', 'topic'),
+ 'foreign keys' => array(
+ 'notice_prefs_notice_id_fkey' => array('notice', array('notice_id' => 'id')),
+ ),
+ 'indexes' => array(
+ 'notice_prefs_notice_id_idx' => array('notice_id'),
+ ),
+ );
+ }
+
+ static function getNamespacePrefs(Notice $notice, $namespace, array $topic=array())
+ {
+ if (empty($topic)) {
+ $prefs = new Notice_prefs();
+ $prefs->notice_id = $notice->getID();
+ $prefs->namespace = $namespace;
+ $prefs->find();
+ } else {
+ $prefs = self::pivotGet('notice_id', $notice->getID(), array('namespace'=>$namespace, 'topic'=>$topic));
+ }
+
+ if (empty($prefs->N)) {
+ throw new NoResultException($prefs);
+ }
+
+ return $prefs;
+ }
+
+ static function getNamespace(Notice $notice, $namespace, array $topic=array())
+ {
+ $prefs = self::getNamespacePrefs($notice, $namespace, $topic);
+ return $prefs->fetchAll();
+ }
+
+ static function getAll(Notice $notice)
+ {
+ try {
+ $prefs = self::listFind('notice_id', array($notice->getID()));
+ } catch (NoResultException $e) {
+ return array();
+ }
+
+ $list = array();
+ while ($prefs->fetch()) {
+ if (!isset($list[$prefs->namespace])) {
+ $list[$prefs->namespace] = array();
+ }
+ $list[$prefs->namespace][$prefs->topic] = $prefs->data;
+ }
+ return $list;
+ }
+
+ static function getTopic(Notice $notice, $namespace, $topic) {
+ return self::getByPK(array('notice_id' => $notice->getID(),
+ 'namespace' => $namespace,
+ 'topic' => $topic));
+ }
+
+ static function getData(Notice $notice, $namespace, $topic, $def=null) {
+ try {
+ $pref = self::getTopic($notice, $namespace, $topic);
+ } catch (NoResultException $e) {
+ if ($def === null) {
+ // If no default value was set, continue the exception.
+ throw $e;
+ }
+ // If there was a default value, return that.
+ return $def;
+ }
+ return $pref->data;
+ }
+
+ static function getConfigData(Notice $notice, $namespace, $topic) {
+ try {
+ $data = self::getData($notice, $namespace, $topic);
+ } catch (NoResultException $e) {
+ $data = common_config($namespace, $topic);
+ }
+ return $data;
+ }
+
+ /*
+ * Sets a notice preference based on Notice, namespace and topic
+ *
+ * @param Notice $notice Which notice this is for
+ * @param string $namespace Under which namespace (pluginname etc.)
+ * @param string $topic Preference name (think key in key-val store)
+ * @param string $data Data to be put into preference storage, null means delete
+ *
+ * @return true if changes are made, false if no action taken
+ * @throws ServerException if preference could not be saved
+ */
+ static function setData(Notice $notice, $namespace, $topic, $data=null) {
+ try {
+ $pref = self::getTopic($notice, $namespace, $topic);
+ if (is_null($data)) {
+ $pref->delete();
+ } else {
+ $orig = clone($pref);
+ $pref->data = $data;
+ $pref->update($orig);
+ }
+ return true;
+ } catch (NoResultException $e) {
+ if (is_null($data)) {
+ return false; // No action taken
+ }
+ }
+
+ $pref = new Notice_prefs();
+ $pref->notice_id = $notice->getID();
+ $pref->namespace = $namespace;
+ $pref->topic = $topic;
+ $pref->data = $data;
+ $pref->created = common_sql_now();
+
+ if ($pref->insert() === false) {
+ throw new ServerException('Could not save notice preference.');
+ }
+ return true;
+ }
+}
diff --git a/classes/Profile.php b/classes/Profile.php
index fb6a621273..c2cc9b26e2 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -89,9 +89,14 @@ class Profile extends Managed_DataObject
public function getUser()
{
if (!isset($this->_user[$this->id])) {
- $user = User::getKV('id', $this->id);
- if (!$user instanceof User) {
- throw new NoSuchUserException(array('id'=>$this->id));
+ $cur_user = common_current_user();
+ if (($cur_user instanceof User) && $cur_user->sameAs($this)) {
+ $user = $cur_user;
+ } else {
+ $user = User::getKV('id', $this->id);
+ if (!$user instanceof User) {
+ throw new NoSuchUserException(array('id'=>$this->id));
+ }
}
$this->_user[$this->id] = $user;
}
@@ -941,11 +946,6 @@ class Profile extends Managed_DataObject
function delete($useWhere=false)
{
- // just in case it hadn't been done before... (usually set before adding deluser to queue handling!)
- if (!$this->hasRole(Profile_role::DELETED)) {
- $this->grantRole(Profile_role::DELETED);
- }
-
$this->_deleteNotices();
$this->_deleteSubscriptions();
$this->_deleteTags();
@@ -957,6 +957,7 @@ class Profile extends Managed_DataObject
// not on individual objects.
$related = array('Reply',
'Group_member',
+ 'Profile_role'
);
Event::handle('ProfileDeleteRelated', array($this, &$related));
@@ -965,6 +966,8 @@ class Profile extends Managed_DataObject
$inst->profile_id = $this->id;
$inst->delete();
}
+
+ $this->grantRole(Profile_role::DELETED);
$localuser = User::getKV('id', $this->id);
if ($localuser instanceof User) {
@@ -1532,6 +1535,14 @@ class Profile extends Managed_DataObject
}
return $url;
}
+ public function getHtmlTitle()
+ {
+ try {
+ return $this->getAcctUri(false);
+ } catch (ProfileNoAcctUriException $e) {
+ return $this->getNickname();
+ }
+ }
public function getNickname()
{
@@ -1612,14 +1623,13 @@ class Profile extends Managed_DataObject
return !empty($block);
}
- function getAtomFeed()
+ public function getAtomFeed()
{
$feed = null;
if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
- $user = User::getKV('id', $this->id);
- if (!empty($user)) {
- $feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
+ if ($this->isLocal()) {
+ $feed = common_local_url('ApiTimelineUser', array('id' => $this->getID(),
'format' => 'atom'));
}
Event::handle('EndProfileGetAtomFeed', array($this, $feed));
diff --git a/classes/User.php b/classes/User.php
index 952b74134b..b4f263235b 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -289,6 +289,11 @@ class User extends Managed_DataObject
// TRANS: Profile data could not be inserted for some reason.
throw new ServerException(_m('Could not insert profile data for new user.'));
}
+
+ // Necessary because id has been known to be reissued.
+ if ($profile->hasRole(Profile_role::DELETED)) {
+ $profile->revokeRole(Profile_role::DELETED);
+ }
$user->id = $id;
diff --git a/db/core.php b/db/core.php
index f654d79d99..56213eb338 100644
--- a/db/core.php
+++ b/db/core.php
@@ -41,6 +41,7 @@ $classes = array('Schema_version',
'Notice',
'Notice_location',
'Notice_source',
+ 'Notice_prefs',
'Reply',
'Consumer',
'Token',
diff --git a/doc-src/twitterapi b/doc-src/twitterapi
index 97f4c2ae95..69303a9628 100644
--- a/doc-src/twitterapi
+++ b/doc-src/twitterapi
@@ -14,7 +14,7 @@ check out [Beginner’s Guide to OAuth](http://hueniverse.com/oauth/)).
To use OAuth, you'll need to register your client application via the web interface
and obtain a consumer key and secret. You can find the interface for application
-registration at [http://%%site.server%%/%%site.path%%settings/oauthapps](http://%%site.server%%/%%site.path%%settings/oauthapps).
+registration at [%%action.oauthappssettings%%](%%action.oauthappssettings%%).
## JSONP callbacks
diff --git a/extlib/Auth/OpenID/Parse.php b/extlib/Auth/OpenID/Parse.php
index 0461bdcff7..af0b86af7e 100644
--- a/extlib/Auth/OpenID/Parse.php
+++ b/extlib/Auth/OpenID/Parse.php
@@ -218,21 +218,11 @@ class Auth_OpenID_Parse {
function match($regexp, $text, &$match)
{
- if (!is_callable('mb_ereg_search_init')) {
- if (!preg_match($regexp, $text, $match)) {
- return false;
- }
- $match = $match[0];
- return true;
+ if (preg_match($regexp, $text, $match)) {
+ return true;
}
- $regexp = substr($regexp, 1, strlen($regexp) - 2 - strlen($this->_re_flags));
- mb_ereg_search_init($text);
- if (!mb_ereg_search($regexp)) {
- return false;
- }
- $match = mb_ereg_search_getregs();
- return true;
+ return false;
}
/**
diff --git a/extlib/DB.php b/extlib/DB.php
index f36f919fe9..1aa9477065 100644
--- a/extlib/DB.php
+++ b/extlib/DB.php
@@ -426,7 +426,7 @@ define('DB_PORTABILITY_ALL', 63);
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB
@@ -577,7 +577,7 @@ class DB
*/
function apiVersion()
{
- return '1.8.2';
+ return '1.9.2';
}
// }}}
@@ -772,7 +772,7 @@ class DB
$parsed['dbsyntax'] = $str;
}
- if (!count($dsn)) {
+ if (!strlen($dsn)) {
return $parsed;
}
@@ -941,7 +941,7 @@ class DB
* @author Stig Bakken
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_Error extends PEAR_Error
@@ -959,18 +959,32 @@ class DB_Error extends PEAR_Error
*
* @see PEAR_Error
*/
- function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
+ function __construct($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
$level = E_USER_NOTICE, $debuginfo = null)
{
if (is_int($code)) {
- $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code,
- $mode, $level, $debuginfo, common_log(LOG_ERR, var_export($debuginfo,true)));
+ parent::__construct('DB Error: ' . DB::errorMessage($code), $code,
+ $mode, $level, $debuginfo);
} else {
- $this->PEAR_Error("DB Error: $code", DB_ERROR,
+ parent::__construct("DB Error: $code", DB_ERROR,
$mode, $level, $debuginfo);
}
}
+ /**
+ * Workaround to both avoid the "Redefining already defined constructor"
+ * PHP error and provide backward compatibility in case someone is calling
+ * DB_Error() dynamically
+ */
+ public function __call($method, $arguments)
+ {
+ if ($method == 'DB_Error') {
+ return call_user_func_array(array($this, '__construct'), $arguments);
+ }
+ trigger_error(
+ 'Call to undefined method DB_Error::' . $method . '()', E_USER_ERROR
+ );
+ }
// }}}
}
@@ -988,7 +1002,7 @@ class DB_Error extends PEAR_Error
* @author Stig Bakken
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_result
@@ -1095,7 +1109,7 @@ class DB_result
*
* @return void
*/
- function DB_result(&$dbh, $result, $options = array())
+ function __construct(&$dbh, $result, $options = array())
{
$this->autofree = $dbh->options['autofree'];
$this->dbh = &$dbh;
@@ -1453,7 +1467,7 @@ class DB_result
* @author Stig Bakken
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @see DB_common::setFetchMode()
*/
@@ -1468,7 +1482,7 @@ class DB_row
*
* @return void
*/
- function DB_row(&$arr)
+ function __construct(&$arr)
{
foreach ($arr as $key => $value) {
$this->$key = &$arr[$key];
diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php
index 1a7b34665d..e26cf8efa0 100644
--- a/extlib/DB/DataObject.php
+++ b/extlib/DB/DataObject.php
@@ -15,7 +15,7 @@
* @author Alan Knowles
* @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
- * @version CVS: $Id: DataObject.php 320069 2011-11-28 04:34:08Z alan_k $
+ * @version CVS: $Id: DataObject.php 336751 2015-05-12 04:39:50Z alan_k $
* @link http://pear.php.net/package/DB_DataObject
*/
@@ -410,7 +410,7 @@ class DB_DataObject extends DB_DataObject_Overload
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug($n, "find",1);
}
- if (!$this->__table) {
+ if (!strlen($this->tableName())) {
// xdebug can backtrace this!
trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
}
@@ -2073,6 +2073,9 @@ class DB_DataObject extends DB_DataObject_Overload
if (count($args)) {
$this->__table = $args[0];
}
+ if (empty($this->__table)) {
+ return '';
+ }
if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) {
return strtolower($this->__table);
}
@@ -2421,7 +2424,7 @@ class DB_DataObject extends DB_DataObject_Overload
$dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
if (!$dsn) {
- if (!$this->_database && !empty($this->__table)) {
+ if (!$this->_database && !strlen($this->tableName())) {
$this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null;
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
@@ -3522,7 +3525,7 @@ class DB_DataObject extends DB_DataObject_Overload
if ($joinCol !== false) {
$this->raiseError(
"joinAdd: You cannot target a join column in the " .
- "'link from' table ({$obj->__table}). " .
+ "'link from' table ({$obj->tableName()}). " .
"Either remove the fourth argument to joinAdd() ".
"({$joinCol}), or alter your links.ini file.",
DB_DATAOBJECT_ERROR_NODATA);
@@ -3605,7 +3608,7 @@ class DB_DataObject extends DB_DataObject_Overload
if (!$items) {
$this->raiseError(
- "joinAdd: No table definition for {$obj->__table}",
+ "joinAdd: No table definition for {$obj->tableName()}",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
@@ -3800,6 +3803,7 @@ class DB_DataObject extends DB_DataObject_Overload
*/
function autoJoin($cfg = array())
{
+ global $_DB_DATAOBJECT;
//var_Dump($cfg);exit;
$pre_links = $this->links();
if (!empty($cfg['links'])) {
@@ -3807,7 +3811,8 @@ class DB_DataObject extends DB_DataObject_Overload
}
$map = $this->links( );
-
+ $this->databaseStructure();
+ $dbstructure = $_DB_DATAOBJECT['INI'][$this->_database];
//print_r($map);
$tabdef = $this->table();
@@ -3874,6 +3879,12 @@ class DB_DataObject extends DB_DataObject_Overload
list($tab,$col) = explode(':', $info);
// what about multiple joins on the same table!!!
+
+ // if links point to a table that does not exist - ignore.
+ if (!isset($dbstructure[$tab])) {
+ continue;
+ }
+
$xx = DB_DataObject::factory($tab);
if (!is_object($xx) || !is_a($xx, 'DB_DataObject')) {
continue;
diff --git a/extlib/DB/DataObject/Generator.php b/extlib/DB/DataObject/Generator.php
index a712e6d9eb..c7f87161c3 100644
--- a/extlib/DB/DataObject/Generator.php
+++ b/extlib/DB/DataObject/Generator.php
@@ -15,7 +15,7 @@
* @author Alan Knowles
* @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
- * @version CVS: $Id: Generator.php 315531 2011-08-26 02:21:29Z alan_k $
+ * @version CVS: $Id: Generator.php 336719 2015-05-05 10:37:33Z alan_k $
* @link http://pear.php.net/package/DB_DataObject
*/
@@ -406,7 +406,7 @@ class DB_DataObject_Generator extends DB_DataObject
* Currenly only works with mysql / mysqli / posgtreas
* to use, you must set option: generate_links=true
*
- * @author Pascal Schni
+ * @author Pascal Sch�ni
*/
function _createForiegnKeys()
@@ -507,7 +507,7 @@ class DB_DataObject_Generator extends DB_DataObject
* Currenly only works with mysql / mysqli
* to use, you must set option: generate_links=true
*
- * @author Pascal Schni
+ * @author Pascal Sch�ni
*/
function generateForeignKeys()
{
@@ -895,7 +895,7 @@ class DB_DataObject_Generator extends DB_DataObject
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
- $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
+ $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
foreach($this->tables as $this->table) {
@@ -976,8 +976,12 @@ class DB_DataObject_Generator extends DB_DataObject
$head .= $this->derivedHookExtendsDocBlock();
- // requires
- $head .= "require_once '{$this->_extendsFile}';\n\n";
+ // requires - if you set extends_location = (blank) then no require line will be set
+ // this can be used if you have an autoloader
+
+ if (!empty($this->_extendsFile)) {
+ $head .= "require_once '{$this->_extendsFile}';\n\n";
+ }
// add dummy class header in...
// class
$head .= $this->derivedHookClassDocBlock();
@@ -1039,10 +1043,11 @@ class DB_DataObject_Generator extends DB_DataObject
continue;
}
- $p = str_repeat(' ',max(2, (30 - strlen($t->name))));
+ $pad = str_repeat(' ',max(2, (30 - strlen($t->name))));
$length = empty($t->len) ? '' : '('.$t->len.')';
- $body .=" {$var} \${$t->name}; {$p}// {$t->type}$length {$t->flags}\n";
+ $flags = strlen($t->flags) ? (' '. trim($t->flags)) : '';
+ $body .=" {$var} \${$t->name}; {$pad}// {$t->type}{$length}{$flags}\n";
// can not do set as PEAR::DB table info doesnt support it.
//if (substr($t->Type,0,3) == "set")
@@ -1283,7 +1288,7 @@ class DB_DataObject_Generator extends DB_DataObject
$class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
- $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
+ $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
$classname = $this->classname = $this->getClassNameFromTableName($this->table);
diff --git a/extlib/DB/common.php b/extlib/DB/common.php
index 27829a072a..73e3eb69f7 100644
--- a/extlib/DB/common.php
+++ b/extlib/DB/common.php
@@ -42,7 +42,7 @@ require_once 'PEAR.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_common extends PEAR
@@ -145,7 +145,7 @@ class DB_common extends PEAR
*
* @return void
*/
- function DB_common()
+ function __construct()
{
$this->PEAR('DB_Error');
}
diff --git a/extlib/DB/dbase.php b/extlib/DB/dbase.php
index df36f972e3..17750cd5d2 100644
--- a/extlib/DB/dbase.php
+++ b/extlib/DB/dbase.php
@@ -41,7 +41,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_dbase extends DB_common
@@ -140,13 +140,13 @@ class DB_dbase extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_dbase()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/fbsql.php b/extlib/DB/fbsql.php
index b719da38e2..c32a08d120 100644
--- a/extlib/DB/fbsql.php
+++ b/extlib/DB/fbsql.php
@@ -41,7 +41,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class functional since Release 1.7.0
*/
@@ -124,13 +124,13 @@ class DB_fbsql extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_fbsql()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/ibase.php b/extlib/DB/ibase.php
index 209ac6c3a1..60e07b5fc3 100644
--- a/extlib/DB/ibase.php
+++ b/extlib/DB/ibase.php
@@ -49,7 +49,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class became stable in Release 1.7.0
*/
@@ -180,13 +180,13 @@ class DB_ibase extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_ibase()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/ifx.php b/extlib/DB/ifx.php
index e3150f92fb..5c5709f79e 100644
--- a/extlib/DB/ifx.php
+++ b/extlib/DB/ifx.php
@@ -48,7 +48,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_ifx extends DB_common
@@ -167,13 +167,13 @@ class DB_ifx extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_ifx()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/msql.php b/extlib/DB/msql.php
index c303bb9067..adcedf7a07 100644
--- a/extlib/DB/msql.php
+++ b/extlib/DB/msql.php
@@ -47,7 +47,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class not functional until Release 1.7.0
*/
@@ -126,13 +126,13 @@ class DB_msql extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_msql()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/mssql.php b/extlib/DB/mssql.php
index e25caf144e..d68ebfa61e 100644
--- a/extlib/DB/mssql.php
+++ b/extlib/DB/mssql.php
@@ -49,7 +49,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_mssql extends DB_common
@@ -179,13 +179,13 @@ class DB_mssql extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_mssql()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/mysql.php b/extlib/DB/mysql.php
index 21132d62df..ffefbc49fd 100644
--- a/extlib/DB/mysql.php
+++ b/extlib/DB/mysql.php
@@ -41,7 +41,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_mysql extends DB_common
@@ -162,13 +162,13 @@ class DB_mysql extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_mysql()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/mysqli.php b/extlib/DB/mysqli.php
index 5f081f7c4f..4f56f0fdac 100644
--- a/extlib/DB/mysqli.php
+++ b/extlib/DB/mysqli.php
@@ -43,7 +43,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class functional since Release 1.6.3
*/
@@ -224,13 +224,13 @@ class DB_mysqli extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_mysqli()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
@@ -497,7 +497,11 @@ class DB_mysqli extends DB_common
*/
function freeResult($result)
{
- return is_resource($result) ? mysqli_free_result($result) : false;
+ if (! $result instanceof mysqli_result) {
+ return false;
+ }
+ mysqli_free_result($result);
+ return true;
}
// }}}
@@ -1031,6 +1035,10 @@ class DB_mysqli extends DB_common
? $this->mysqli_types[$tmp->type]
: 'unknown',
// http://bugs.php.net/?id=36579
+ // Doc Bug #36579: mysqli_fetch_field length handling
+ // https://bugs.php.net/bug.php?id=62426
+ // Bug #62426: mysqli_fetch_field_direct returns incorrect
+ // length on UTF8 fields
'len' => $tmp->length,
'flags' => $flags,
);
diff --git a/extlib/DB/oci8.php b/extlib/DB/oci8.php
index 9685c60e0c..1ca7a04e22 100644
--- a/extlib/DB/oci8.php
+++ b/extlib/DB/oci8.php
@@ -47,7 +47,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_oci8 extends DB_common
@@ -173,13 +173,13 @@ class DB_oci8 extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_oci8()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/odbc.php b/extlib/DB/odbc.php
index 75d4fe74ff..a33406a654 100644
--- a/extlib/DB/odbc.php
+++ b/extlib/DB/odbc.php
@@ -44,7 +44,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_odbc extends DB_common
@@ -153,13 +153,13 @@ class DB_odbc extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_odbc()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/pgsql.php b/extlib/DB/pgsql.php
index adfd6bf0a4..098d9f040a 100644
--- a/extlib/DB/pgsql.php
+++ b/extlib/DB/pgsql.php
@@ -43,7 +43,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_pgsql extends DB_common
@@ -148,13 +148,13 @@ class DB_pgsql extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_pgsql()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/sqlite.php b/extlib/DB/sqlite.php
index 7adfb4c475..9c5c8b3523 100644
--- a/extlib/DB/sqlite.php
+++ b/extlib/DB/sqlite.php
@@ -47,7 +47,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_sqlite extends DB_common
@@ -152,13 +152,13 @@ class DB_sqlite extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_sqlite()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/DB/storage.php b/extlib/DB/storage.php
index 9ac23c825e..640d86f2b9 100644
--- a/extlib/DB/storage.php
+++ b/extlib/DB/storage.php
@@ -38,7 +38,7 @@ require_once 'DB.php';
* @author Stig Bakken
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_storage extends PEAR
@@ -94,7 +94,7 @@ class DB_storage extends PEAR
* a reference to this object
*
*/
- function DB_storage($table, $keycolumn, &$dbh, $validator = null)
+ function __construct($table, $keycolumn, &$dbh, $validator = null)
{
$this->PEAR('DB_Error');
$this->_table = $table;
diff --git a/extlib/DB/sybase.php b/extlib/DB/sybase.php
index d87b18caab..14d054b246 100644
--- a/extlib/DB/sybase.php
+++ b/extlib/DB/sybase.php
@@ -46,7 +46,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
- * @version Release: 1.8.2
+ * @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_sybase extends DB_common
@@ -141,13 +141,13 @@ class DB_sybase extends DB_common
// {{{ constructor
/**
- * This constructor calls $this->DB_common()
+ * This constructor calls parent::__construct()
*
* @return void
*/
- function DB_sybase()
+ function __construct()
{
- $this->DB_common();
+ parent::__construct();
}
// }}}
diff --git a/extlib/HTMLPurifier/HTMLPurifier.includes.php b/extlib/HTMLPurifier/HTMLPurifier.includes.php
index fdb58c2d37..e8bce5c850 100644
--- a/extlib/HTMLPurifier/HTMLPurifier.includes.php
+++ b/extlib/HTMLPurifier/HTMLPurifier.includes.php
@@ -7,7 +7,7 @@
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
* FILE, changes will be overwritten the next time the script is run.
*
- * @version 4.7.0
+ * @version 4.9.3
*
* @warning
* You must *not* include any other HTML Purifier files before this file,
@@ -137,6 +137,8 @@ require 'HTMLPurifier/AttrTransform/SafeObject.php';
require 'HTMLPurifier/AttrTransform/SafeParam.php';
require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
require 'HTMLPurifier/AttrTransform/TargetBlank.php';
+require 'HTMLPurifier/AttrTransform/TargetNoopener.php';
+require 'HTMLPurifier/AttrTransform/TargetNoreferrer.php';
require 'HTMLPurifier/AttrTransform/Textarea.php';
require 'HTMLPurifier/ChildDef/Chameleon.php';
require 'HTMLPurifier/ChildDef/Custom.php';
@@ -175,6 +177,8 @@ require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
require 'HTMLPurifier/HTMLModule/Tables.php';
require 'HTMLPurifier/HTMLModule/Target.php';
require 'HTMLPurifier/HTMLModule/TargetBlank.php';
+require 'HTMLPurifier/HTMLModule/TargetNoopener.php';
+require 'HTMLPurifier/HTMLModule/TargetNoreferrer.php';
require 'HTMLPurifier/HTMLModule/Text.php';
require 'HTMLPurifier/HTMLModule/Tidy.php';
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
@@ -225,5 +229,6 @@ require 'HTMLPurifier/URIScheme/https.php';
require 'HTMLPurifier/URIScheme/mailto.php';
require 'HTMLPurifier/URIScheme/news.php';
require 'HTMLPurifier/URIScheme/nntp.php';
+require 'HTMLPurifier/URIScheme/tel.php';
require 'HTMLPurifier/VarParser/Flexible.php';
require 'HTMLPurifier/VarParser/Native.php';
diff --git a/extlib/HTMLPurifier/HTMLPurifier.php b/extlib/HTMLPurifier/HTMLPurifier.php
index c6041bc113..b4605ebc6e 100644
--- a/extlib/HTMLPurifier/HTMLPurifier.php
+++ b/extlib/HTMLPurifier/HTMLPurifier.php
@@ -19,7 +19,7 @@
*/
/*
- HTML Purifier 4.7.0 - Standards Compliant HTML Filtering
+ HTML Purifier 4.9.3 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or
@@ -58,12 +58,12 @@ class HTMLPurifier
* Version of HTML Purifier.
* @type string
*/
- public $version = '4.7.0';
+ public $version = '4.9.3';
/**
* Constant with version of HTML Purifier.
*/
- const VERSION = '4.7.0';
+ const VERSION = '4.9.3';
/**
* Global configuration object.
@@ -104,7 +104,7 @@ class HTMLPurifier
/**
* Initializes the purifier.
*
- * @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object
+ * @param HTMLPurifier_Config|mixed $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).
diff --git a/extlib/HTMLPurifier/HTMLPurifier.safe-includes.php b/extlib/HTMLPurifier/HTMLPurifier.safe-includes.php
index 9dea6d1ed5..a3261f8a32 100644
--- a/extlib/HTMLPurifier/HTMLPurifier.safe-includes.php
+++ b/extlib/HTMLPurifier/HTMLPurifier.safe-includes.php
@@ -131,6 +131,8 @@ 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/TargetNoopener.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoreferrer.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
@@ -169,6 +171,8 @@ 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/TargetNoopener.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoreferrer.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
@@ -219,5 +223,6 @@ require_once $__dir . '/HTMLPurifier/URIScheme/https.php';
require_once $__dir . '/HTMLPurifier/URIScheme/mailto.php';
require_once $__dir . '/HTMLPurifier/URIScheme/news.php';
require_once $__dir . '/HTMLPurifier/URIScheme/nntp.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/tel.php';
require_once $__dir . '/HTMLPurifier/VarParser/Flexible.php';
require_once $__dir . '/HTMLPurifier/VarParser/Native.php';
diff --git a/extlib/HTMLPurifier/HTMLPurifier/Arborize.php b/extlib/HTMLPurifier/HTMLPurifier/Arborize.php
index 9e6617be5d..d2e9d22a20 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/Arborize.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/Arborize.php
@@ -19,8 +19,8 @@ class HTMLPurifier_Arborize
if ($token instanceof HTMLPurifier_Token_End) {
$token->start = null; // [MUT]
$r = array_pop($stack);
- assert($r->name === $token->name);
- assert(empty($token->attr));
+ //assert($r->name === $token->name);
+ //assert(empty($token->attr));
$r->endCol = $token->col;
$r->endLine = $token->line;
$r->endArmor = $token->armor;
@@ -32,7 +32,7 @@ class HTMLPurifier_Arborize
$stack[] = $node;
}
}
- assert(count($stack) == 1);
+ //assert(count($stack) == 1);
return $stack[0];
}
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrCollections.php b/extlib/HTMLPurifier/HTMLPurifier/AttrCollections.php
index 4f6c2e39a2..c7b17cf144 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrCollections.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrCollections.php
@@ -21,6 +21,11 @@ class HTMLPurifier_AttrCollections
* @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
*/
public function __construct($attr_types, $modules)
+ {
+ $this->doConstruct($attr_types, $modules);
+ }
+
+ public function doConstruct($attr_types, $modules)
{
// load extensions from the modules
foreach ($modules as $module) {
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrDef.php b/extlib/HTMLPurifier/HTMLPurifier/AttrDef.php
index 5ac06522b9..739646fa7c 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrDef.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrDef.php
@@ -86,7 +86,13 @@ abstract class HTMLPurifier_AttrDef
*/
protected function mungeRgb($string)
{
- return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
+ $p = '\s*(\d+(\.\d+)?([%]?))\s*';
+
+ if (preg_match('/(rgba|hsla)\(/', $string)) {
+ return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string);
+ }
+
+ return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string);
}
/**
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS.php b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS.php
index 02c1641fb2..ad2cb90ad1 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS.php
@@ -25,15 +25,42 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
$css = $this->parseCDATA($css);
$definition = $config->getCSSDefinition();
+ $allow_duplicates = $config->get("CSS.AllowDuplicates");
- // we're going to break the spec and explode by semicolons.
- // This is because semicolon rarely appears in escaped form
- // Doing this is generally flaky but fast
- // IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
- // for details
- $declarations = explode(';', $css);
+ // According to the CSS2.1 spec, the places where a
+ // non-delimiting semicolon can appear are in strings
+ // escape sequences. So here is some dumb hack to
+ // handle quotes.
+ $len = strlen($css);
+ $accum = "";
+ $declarations = array();
+ $quoted = false;
+ for ($i = 0; $i < $len; $i++) {
+ $c = strcspn($css, ";'\"", $i);
+ $accum .= substr($css, $i, $c);
+ $i += $c;
+ if ($i == $len) break;
+ $d = $css[$i];
+ if ($quoted) {
+ $accum .= $d;
+ if ($d == $quoted) {
+ $quoted = false;
+ }
+ } else {
+ if ($d == ";") {
+ $declarations[] = $accum;
+ $accum = "";
+ } else {
+ $accum .= $d;
+ $quoted = $d;
+ }
+ }
+ }
+ if ($accum != "") $declarations[] = $accum;
+
$propvalues = array();
+ $new_declarations = '';
/**
* Name of the current CSS property being validated.
@@ -83,7 +110,11 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
if ($result === false) {
continue;
}
- $propvalues[$property] = $result;
+ if ($allow_duplicates) {
+ $new_declarations .= "$property:$result;";
+ } else {
+ $propvalues[$property] = $result;
+ }
}
$context->destroy('CurrentCSSProperty');
@@ -92,7 +123,6 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
// slightly inefficient, but it's the only way of getting rid of
// duplicates. Perhaps config to optimize it, but not now.
- $new_declarations = '';
foreach ($propvalues as $prop => $value) {
$new_declarations .= "$prop:$value;";
}
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/Color.php b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/Color.php
index 16d2a6b98c..d7287a00c2 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/Color.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/Color.php
@@ -6,6 +6,16 @@
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
{
+ /**
+ * @type HTMLPurifier_AttrDef_CSS_AlphaValue
+ */
+ protected $alpha;
+
+ public function __construct()
+ {
+ $this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+ }
+
/**
* @param string $color
* @param HTMLPurifier_Config $config
@@ -29,59 +39,104 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
return $colors[$lower];
}
- if (strpos($color, 'rgb(') !== false) {
- // rgb literal handling
+ if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
$length = strlen($color);
if (strpos($color, ')') !== $length - 1) {
return false;
}
- $triad = substr($color, 4, $length - 4 - 1);
- $parts = explode(',', $triad);
- if (count($parts) !== 3) {
+
+ // get used function : rgb, rgba, hsl or hsla
+ $function = $matches[1];
+
+ $parameters_size = 3;
+ $alpha_channel = false;
+ if (substr($function, -1) === 'a') {
+ $parameters_size = 4;
+ $alpha_channel = true;
+ }
+
+ /*
+ * Allowed types for values :
+ * parameter_position => [type => max_value]
+ */
+ $allowed_types = array(
+ 1 => array('percentage' => 100, 'integer' => 255),
+ 2 => array('percentage' => 100, 'integer' => 255),
+ 3 => array('percentage' => 100, 'integer' => 255),
+ );
+ $allow_different_types = false;
+
+ if (strpos($function, 'hsl') !== false) {
+ $allowed_types = array(
+ 1 => array('integer' => 360),
+ 2 => array('percentage' => 100),
+ 3 => array('percentage' => 100),
+ );
+ $allow_different_types = true;
+ }
+
+ $values = trim(str_replace($function, '', $color), ' ()');
+
+ $parts = explode(',', $values);
+ if (count($parts) !== $parameters_size) {
return false;
}
- $type = false; // to ensure that they're all the same type
+
+ $type = false;
$new_parts = array();
+ $i = 0;
+
foreach ($parts as $part) {
+ $i++;
$part = trim($part);
+
if ($part === '') {
return false;
}
- $length = strlen($part);
- if ($part[$length - 1] === '%') {
- // handle percents
- if (!$type) {
- $type = 'percentage';
- } elseif ($type !== 'percentage') {
+
+ // different check for alpha channel
+ if ($alpha_channel === true && $i === count($parts)) {
+ $result = $this->alpha->validate($part, $config, $context);
+
+ if ($result === false) {
return false;
}
- $num = (float)substr($part, 0, $length - 1);
- if ($num < 0) {
- $num = 0;
- }
- if ($num > 100) {
- $num = 100;
- }
- $new_parts[] = "$num%";
+
+ $new_parts[] = (string)$result;
+ continue;
+ }
+
+ if (substr($part, -1) === '%') {
+ $current_type = 'percentage';
} else {
- // handle integers
- if (!$type) {
- $type = 'integer';
- } elseif ($type !== 'integer') {
- return false;
- }
- $num = (int)$part;
- if ($num < 0) {
- $num = 0;
- }
- if ($num > 255) {
- $num = 255;
- }
- $new_parts[] = (string)$num;
+ $current_type = 'integer';
+ }
+
+ if (!array_key_exists($current_type, $allowed_types[$i])) {
+ return false;
+ }
+
+ if (!$type) {
+ $type = $current_type;
+ }
+
+ if ($allow_different_types === false && $type != $current_type) {
+ return false;
+ }
+
+ $max_value = $allowed_types[$i][$current_type];
+
+ if ($current_type == 'integer') {
+ // Return value between range 0 -> $max_value
+ $new_parts[] = (int)max(min($part, $max_value), 0);
+ } elseif ($current_type == 'percentage') {
+ $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%';
}
}
- $new_triad = implode(',', $new_parts);
- $color = "rgb($new_triad)";
+
+ $new_values = implode(',', $new_parts);
+
+ $color = $function . '(' . $new_values . ')';
} else {
// hexadecimal handling
if ($color[0] === '#') {
@@ -100,6 +155,7 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
}
return $color;
}
+
}
// vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php
index 86101020dc..74e24c8816 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php
@@ -130,6 +130,8 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
// . See
// the CSS3 spec for more examples:
//
+ // You can see live samples of these on the Internet:
+ //
// However, most of these fonts have ASCII equivalents:
// for example, 'MS Mincho', and it's considered
// professional to use ASCII font names instead of
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/URI.php b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/URI.php
index f9434230e2..6617acace5 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/URI.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/CSS/URI.php
@@ -33,6 +33,9 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
return false;
}
$uri_string = substr($uri_string, 4);
+ if (strlen($uri_string) == 0) {
+ return false;
+ }
$new_length = strlen($uri_string) - 1;
if ($uri_string[$new_length] != ')') {
return false;
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/HTML/ID.php b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/HTML/ID.php
index 3d86efb44c..4ba45610fe 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/HTML/ID.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/HTML/ID.php
@@ -72,18 +72,26 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
// we purposely avoid using regex, hopefully this is faster
- if (ctype_alpha($id)) {
- $result = true;
- } else {
- if (!ctype_alpha(@$id[0])) {
+ if ($config->get('Attr.ID.HTML5') === true) {
+ if (preg_match('/[\t\n\x0b\x0c ]/', $id)) {
return false;
}
- // primitive style of regexps, I suppose
- $trim = trim(
- $id,
- 'A..Za..z0..9:-._'
- );
- $result = ($trim === '');
+ } else {
+ if (ctype_alpha($id)) {
+ // OK
+ } else {
+ if (!ctype_alpha(@$id[0])) {
+ return false;
+ }
+ // primitive style of regexps, I suppose
+ $trim = trim(
+ $id,
+ 'A..Za..z0..9:-._'
+ );
+ if ($trim !== '') {
+ return false;
+ }
+ }
}
$regexp = $config->get('Attr.IDBlacklistRegexp');
@@ -91,14 +99,14 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
return false;
}
- if (!$this->selector && $result) {
+ if (!$this->selector) {
$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;
+ return $id;
}
}
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/URI/Host.php b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/URI/Host.php
index e7df800b1e..3b4d186743 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrDef/URI/Host.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrDef/URI/Host.php
@@ -76,24 +76,33 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
// fairly well supported.
$underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
+ // Based off of RFC 1738, but amended so that
+ // as per RFC 3696, the top label need only not be all numeric.
// The productions describing this are:
$a = '[a-z]'; // alpha
$an = '[a-z0-9]'; // alphanum
$and = "[a-z0-9-$underscore]"; // alphanum | "-"
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
- $domainlabel = "$an($and*$an)?";
- // toplabel = alpha | alpha *( alphanum | "-" ) alphanum
- $toplabel = "$a($and*$an)?";
+ $domainlabel = "$an(?:$and*$an)?";
+ // AMENDED as per RFC 3696
+ // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+ // side condition: not all numeric
+ $toplabel = "$an(?:$and*$an)?";
// hostname = *( domainlabel "." ) toplabel [ "." ]
- if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
- return $string;
+ if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) {
+ if (!ctype_digit($matches[1])) {
+ return $string;
+ }
}
+ // PHP 5.3 and later support this functionality natively
+ if (function_exists('idn_to_ascii')) {
+ $string = idn_to_ascii($string);
+
// If we have Net_IDNA2 support, we can support IRIs by
// punycoding them. (This is the most portable thing to do,
// since otherwise we have to assume browsers support
-
- if ($config->get('Core.EnableIDNA')) {
+ } elseif ($config->get('Core.EnableIDNA')) {
$idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
// we need to encode each period separately
$parts = explode('.', $string);
@@ -114,13 +123,14 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
}
}
$string = implode('.', $new_parts);
- if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
- return $string;
- }
} catch (Exception $e) {
// XXX error reporting
}
}
+ // Try again
+ if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
+ return $string;
+ }
return false;
}
}
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrTransform/ImgRequired.php b/extlib/HTMLPurifier/HTMLPurifier/AttrTransform/ImgRequired.php
index 7df6cb3e1b..235ebb34b6 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/AttrTransform/ImgRequired.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrTransform/ImgRequired.php
@@ -32,8 +32,7 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
if ($src) {
$alt = $config->get('Attr.DefaultImageAlt');
if ($alt === null) {
- // truncate if the alt is too long
- $attr['alt'] = substr(basename($attr['src']), 0, 40);
+ $attr['alt'] = basename($attr['src']);
} else {
$attr['alt'] = $alt;
}
diff --git a/extlib/HTMLPurifier/HTMLPurifier/AttrTransform/TargetNoopener.php b/extlib/HTMLPurifier/HTMLPurifier/AttrTransform/TargetNoopener.php
new file mode 100644
index 0000000000..1db3c6c09e
--- /dev/null
+++ b/extlib/HTMLPurifier/HTMLPurifier/AttrTransform/TargetNoopener.php
@@ -0,0 +1,37 @@
+get('CSS.MaxImgLength');
+ $this->info['min-width'] =
+ $this->info['max-width'] =
+ $this->info['min-height'] =
+ $this->info['max-height'] =
$this->info['width'] =
$this->info['height'] =
$max === null ?
@@ -370,6 +374,19 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
);
$this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
+ $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
+ new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
+ ));
+
+ $this->info['border-top-left-radius'] =
+ $this->info['border-top-right-radius'] =
+ $this->info['border-bottom-right-radius'] =
+ $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
+ // TODO: support SLASH syntax
+ $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
+
}
/**
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ChildDef/List.php b/extlib/HTMLPurifier/HTMLPurifier/ChildDef/List.php
index 891b9f6f5b..4fc70e0efa 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/ChildDef/List.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/ChildDef/List.php
@@ -38,13 +38,19 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
return false;
}
+ // if li is not allowed, delete parent node
+ if (!isset($config->getHTMLDefinition()->info['li'])) {
+ trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING);
+ return false;
+ }
+
// the new set of children
$result = array();
// a little sanity check to make sure it's not ALL whitespace
$all_whitespace = true;
- $current_li = false;
+ $current_li = null;
foreach ($children as $node) {
if (!empty($node->is_whitespace)) {
@@ -65,7 +71,7 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
// to handle non-list elements; non-list elements should
// not be appended to an existing li; only li created
// for non-list. This distinction is not currently made.
- if ($current_li === false) {
+ if ($current_li === null) {
$current_li = new HTMLPurifier_Node_Element('li');
$result[] = $current_li;
}
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ChildDef/Table.php b/extlib/HTMLPurifier/HTMLPurifier/ChildDef/Table.php
index 3e4a0f2182..cb6b3e6cdc 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/ChildDef/Table.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/ChildDef/Table.php
@@ -203,7 +203,7 @@ class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
$current_tr_tbody->children[] = $node;
break;
case '#PCDATA':
- assert($node->is_whitespace);
+ //assert($node->is_whitespace);
if ($current_tr_tbody === null) {
$ret[] = $node;
} else {
diff --git a/extlib/HTMLPurifier/HTMLPurifier/Config.php b/extlib/HTMLPurifier/HTMLPurifier/Config.php
index 2b2db0c264..3648364b30 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/Config.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/Config.php
@@ -21,7 +21,7 @@ class HTMLPurifier_Config
* HTML Purifier's version
* @type string
*/
- public $version = '4.7.0';
+ public $version = '4.9.3';
/**
* Whether or not to automatically finalize
@@ -333,7 +333,7 @@ class HTMLPurifier_Config
}
// Raw type might be negative when using the fully optimized form
- // of stdclass, which indicates allow_null == true
+ // of stdClass, which indicates allow_null == true
$rtype = is_int($def) ? $def : $def->type;
if ($rtype < 0) {
$type = -$rtype;
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema.php b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema.php
index bfbb0f92f5..655c0e97ae 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema.php
@@ -24,11 +24,11 @@ class HTMLPurifier_ConfigSchema
*
* array(
* 'Namespace' => array(
- * 'Directive' => new stdclass(),
+ * 'Directive' => new stdClass(),
* )
* )
*
- * The stdclass may have the following properties:
+ * The stdClass may have the following properties:
*
* - If isAlias isn't set:
* - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
@@ -39,8 +39,8 @@ class HTMLPurifier_ConfigSchema
* - namespace: Namespace this directive aliases to
* - name: Directive name this directive aliases to
*
- * In certain degenerate cases, stdclass will actually be an integer. In
- * that case, the value is equivalent to an stdclass with the type
+ * In certain degenerate cases, stdClass will actually be an integer. In
+ * that case, the value is equivalent to an stdClass with the type
* property set to the integer. If the integer is negative, type is
* equal to the absolute value of integer, and allow_null is true.
*
@@ -105,7 +105,7 @@ class HTMLPurifier_ConfigSchema
*/
public function add($key, $default, $type, $allow_null)
{
- $obj = new stdclass();
+ $obj = new stdClass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
if ($allow_null) {
$obj->allow_null = true;
@@ -152,14 +152,14 @@ class HTMLPurifier_ConfigSchema
*/
public function addAlias($key, $new_key)
{
- $obj = new stdclass;
+ $obj = new stdClass;
$obj->key = $new_key;
$obj->isAlias = true;
$this->info[$key] = $obj;
}
/**
- * Replaces any stdclass that only has the type property with type integer.
+ * Replaces any stdClass that only has the type property with type integer.
*/
public function postProcess()
{
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema.ser b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema.ser
index 1e6ccd2275..371e948f1c 100644
Binary files a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema.ser and b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema.ser differ
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt
new file mode 100644
index 0000000000..735d4b7a10
--- /dev/null
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt
@@ -0,0 +1,10 @@
+Attr.ID.HTML5
+TYPE: bool/null
+DEFAULT: null
+VERSION: 4.8.0
+--DESCRIPTION--
+In HTML5, restrictions on the format of the id attribute have been significantly
+relaxed, such that any string is valid so long as it contains no spaces and
+is at least one character. In lieu of a general HTML5 compatibility flag,
+set this configuration directive to true to use the relaxed rules.
+--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt
new file mode 100644
index 0000000000..4d054b1f07
--- /dev/null
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt
@@ -0,0 +1,11 @@
+CSS.AllowDuplicates
+TYPE: bool
+DEFAULT: false
+VERSION: 4.8.0
+--DESCRIPTION--
+
+ By default, HTML Purifier removes duplicate CSS properties,
+ like color:red; color:blue
. If this is set to
+ true, duplicate properties are allowed.
+
+--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
index b2b83d9ab6..2e0cc81044 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
@@ -1,5 +1,5 @@
Cache.SerializerPermissions
-TYPE: int
+TYPE: int/null
VERSION: 4.3.0
DEFAULT: 0755
--DESCRIPTION--
@@ -8,4 +8,9 @@ DEFAULT: 0755
Directory permissions of the files and directories created inside
the DefinitionCache/Serializer or other custom serializer path.
+
+ In HTML Purifier 4.8.0, this also supports NULL
,
+ which means that no chmod'ing or directory creation shall
+ occur.
+
--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt
new file mode 100644
index 0000000000..b2b6ab1496
--- /dev/null
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt
@@ -0,0 +1,16 @@
+Core.AggressivelyRemoveScript
+TYPE: bool
+VERSION: 4.9.0
+DEFAULT: true
+--DESCRIPTION--
+
+ This directive enables aggressive pre-filter removal of
+ script tags. This is not necessary for security,
+ but it can help work around a bug in libxml where embedded
+ HTML elements inside script sections cause the parser to
+ choke. To revert to pre-4.9.0 behavior, set this to false.
+ This directive has no effect if %Core.Trusted is true,
+ %Core.RemoveScriptContents is false, or %Core.HiddenElements
+ does not contain script.
+
+--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt
new file mode 100644
index 0000000000..392b436493
--- /dev/null
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt
@@ -0,0 +1,36 @@
+Core.LegacyEntityDecoder
+TYPE: bool
+VERSION: 4.9.0
+DEFAULT: false
+--DESCRIPTION--
+
+ Prior to HTML Purifier 4.9.0, entities were decoded by performing
+ a global search replace for all entities whose decoded versions
+ did not have special meanings under HTML, and replaced them with
+ their decoded versions. We would match all entities, even if they did
+ not have a trailing semicolon, but only if there weren't any trailing
+ alphanumeric characters.
+
+
+Original | Text | Attribute |
+¥ | ¥ | ¥ |
+¥ | ¥ | ¥ |
+¥a | ¥a | ¥a |
+¥= | ¥= | ¥= |
+
+
+ In HTML Purifier 4.9.0, we changed the behavior of entity parsing
+ to match entities that had missing trailing semicolons in less
+ cases, to more closely match HTML5 parsing behavior:
+
+
+Original | Text | Attribute |
+¥ | ¥ | ¥ |
+¥ | ¥ | ¥ |
+¥a | ¥a | ¥a |
+¥= | ¥= | ¥= |
+
+
+ This flag reverts back to pre-HTML Purifier 4.9.0 behavior.
+
+--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt
new file mode 100644
index 0000000000..dd514c0def
--- /dev/null
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt
@@ -0,0 +1,10 @@
+--# vim: et sw=4 sts=4
+HTML.TargetNoopener
+TYPE: bool
+VERSION: 4.8.0
+DEFAULT: TRUE
+--DESCRIPTION--
+If enabled, noopener rel attributes are added to links which have
+a target attribute associated with them. This prevents malicious
+destinations from overwriting the original window.
+--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
new file mode 100644
index 0000000000..cb5a0b0e5e
--- /dev/null
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
@@ -0,0 +1,9 @@
+HTML.TargetNoreferrer
+TYPE: bool
+VERSION: 4.8.0
+DEFAULT: TRUE
+--DESCRIPTION--
+If enabled, noreferrer rel attributes are added to links which have
+a target attribute associated with them. This prevents malicious
+destinations from overwriting the original window.
+--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
index 666635a5ff..eb97307e20 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
@@ -8,6 +8,7 @@ array (
'ftp' => true,
'nntp' => true,
'news' => true,
+ 'tel' => true,
)
--DESCRIPTION--
Whitelist that defines the schemes that a URI is allowed to have. This
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
index 728e378cbe..834bc08c0b 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
@@ -1,5 +1,5 @@
URI.DefaultScheme
-TYPE: string
+TYPE: string/null
DEFAULT: 'http'
--DESCRIPTION--
@@ -7,4 +7,9 @@ DEFAULT: 'http'
Defines through what scheme the output will be served, in order to
select the proper object validator when no scheme information is present.
+
+
+ Starting with HTML Purifier 4.9.0, the default scheme can be null, in
+ which case we reject all URIs which do not have explicit schemes.
+
--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
index 179f94eb03..58c81dcc44 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
+++ b/extlib/HTMLPurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
@@ -9,75 +9,75 @@ DEFAULT: NULL
absolute URIs into another URI, usually a URI redirection service.
This directive accepts a URI, formatted with a %s
where
the url-encoded original URI should be inserted (sample:
- https://searx.laquadrature.net/?q=%s
).
-
-
+ http://www.google.com/url?q=%s
).
+
+
Uses for this directive:
-
-
+
+
-
- Prevent PageRank leaks, while being fairly transparent
- to users (you may also want to add some client side JavaScript to
- override the text in the statusbar). Notice:
- Many security experts believe that this form of protection does not deter spam-bots.
+ Prevent PageRank leaks, while being fairly transparent
+ to users (you may also want to add some client side JavaScript to
+ override the text in the statusbar). Notice:
+ Many security experts believe that this form of protection does not deter spam-bots.
-
- Redirect users to a splash page telling them they are leaving your
- website. While this is poor usability practice, it is often mandated
- in corporate environments.
+ Redirect users to a splash page telling them they are leaving your
+ website. While this is poor usability practice, it is often mandated
+ in corporate environments.
-
-
+
+
Prior to HTML Purifier 3.1.1, this directive also enabled the munging
of browsable external resources, which could break things if your redirection
script was a splash page or used meta
tags. To revert to
previous behavior, please use %URI.MungeResources.
-
-
+
+
You may want to also use %URI.MungeSecretKey along with this directive
in order to enforce what URIs your redirector script allows. Open
redirector scripts can be a security risk and negatively affect the
reputation of your domain name.
-
-
+
+
Starting with HTML Purifier 3.1.1, there is also these substitutions:
-
-
+
+
-
- Key |
- Description |
- Example <a href=""> |
-
+
+ Key |
+ Description |
+ Example <a href=""> |
+
-
- %r |
- 1 - The URI embeds a resource (blank) - The URI is merely a link |
- |
-
-
- %n |
- The name of the tag this URI came from |
- a |
-
-
- %m |
- The name of the attribute this URI came from |
- href |
-
-
- %p |
- The name of the CSS property this URI came from, or blank if irrelevant |
- |
-
+
+ %r |
+ 1 - The URI embeds a resource (blank) - The URI is merely a link |
+ |
+
+
+ %n |
+ The name of the tag this URI came from |
+ a |
+
+
+ %m |
+ The name of the attribute this URI came from |
+ href |
+
+
+ %p |
+ The name of the CSS property this URI came from, or blank if irrelevant |
+ |
+
-
-
+
+
Admittedly, these letters are somewhat arbitrary; the only stipulation
was that they couldn't be a through f. r is for resource (I would have preferred
e, but you take what you can get), n is for name, m
was picked because it came after n (and I couldn't use a), p is for
property.
-
- --# vim: et sw=4 sts=4
+
+--# vim: et sw=4 sts=4
diff --git a/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache.php b/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache.php
index 67bb5b1e69..9aa8ff354f 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache.php
@@ -118,7 +118,7 @@ abstract class HTMLPurifier_DefinitionCache
/**
* Clears all expired (older version or revision) objects from cache
- * @note Be carefuly implementing this method as flush. Flush must
+ * @note Be careful implementing this method as flush. Flush must
* not interfere with other Definition types, and cleanup()
* should not be repeatedly called by userland code.
* @param HTMLPurifier_Config $config
diff --git a/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache/Serializer.php b/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache/Serializer.php
index ce268d91b4..952e48d470 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache/Serializer.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/DefinitionCache/Serializer.php
@@ -97,6 +97,12 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
}
$dir = $this->generateDirectoryPath($config);
$dh = opendir($dir);
+ // Apparently, on some versions of PHP, readdir will return
+ // an empty string if you pass an invalid argument to readdir.
+ // So you need this test. See #49.
+ if (false === $dh) {
+ return false;
+ }
while (false !== ($filename = readdir($dh))) {
if (empty($filename)) {
continue;
@@ -106,6 +112,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
}
unlink($dir . '/' . $filename);
}
+ closedir($dh);
+ return true;
}
/**
@@ -119,6 +127,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
}
$dir = $this->generateDirectoryPath($config);
$dh = opendir($dir);
+ // See #49 (and above).
+ if (false === $dh) {
+ return false;
+ }
while (false !== ($filename = readdir($dh))) {
if (empty($filename)) {
continue;
@@ -131,6 +143,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
unlink($dir . '/' . $filename);
}
}
+ closedir($dh);
+ return true;
}
/**
@@ -186,11 +200,9 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
if ($result !== false) {
// set permissions of the new file (no execute)
$chmod = $config->get('Cache.SerializerPermissions');
- if (!$chmod) {
- $chmod = 0644; // invalid config or simpletest
+ if ($chmod !== null) {
+ chmod($file, $chmod & 0666);
}
- $chmod = $chmod & 0666;
- chmod($file, $chmod);
}
return $result;
}
@@ -204,8 +216,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
{
$directory = $this->generateDirectoryPath($config);
$chmod = $config->get('Cache.SerializerPermissions');
- if (!$chmod) {
- $chmod = 0755; // invalid config or simpletest
+ if ($chmod === null) {
+ // TODO: This races
+ if (is_dir($directory)) return true;
+ return mkdir($directory);
}
if (!is_dir($directory)) {
$base = $this->generateBaseDirectoryPath($config);
@@ -219,15 +233,16 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} elseif (!$this->_testPermissions($base, $chmod)) {
return false;
}
- mkdir($directory, $chmod);
- if (!$this->_testPermissions($directory, $chmod)) {
+ if (!mkdir($directory, $chmod)) {
trigger_error(
- 'Base directory ' . $base . ' does not exist,
- please create or change using %Cache.SerializerPath',
+ 'Could not create directory ' . $directory . '',
E_USER_WARNING
);
return false;
}
+ if (!$this->_testPermissions($directory, $chmod)) {
+ return false;
+ }
} elseif (!$this->_testPermissions($directory, $chmod)) {
return false;
}
@@ -256,7 +271,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
);
return false;
}
- if (function_exists('posix_getuid')) {
+ if (function_exists('posix_getuid') && $chmod !== null) {
// POSIX system, we can give more specific advice
if (fileowner($dir) === posix_getuid()) {
// we can chmod it ourselves
diff --git a/extlib/HTMLPurifier/HTMLPurifier/Encoder.php b/extlib/HTMLPurifier/HTMLPurifier/Encoder.php
index fef9b58906..b94f175423 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/Encoder.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/Encoder.php
@@ -101,6 +101,14 @@ class HTMLPurifier_Encoder
* It will parse according to UTF-8 and return a valid UTF8 string, with
* non-SGML codepoints excluded.
*
+ * Specifically, it will permit:
+ * \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}
+ * Source: https://www.w3.org/TR/REC-xml/#NT-Char
+ * Arguably this function should be modernized to the HTML5 set
+ * of allowed characters:
+ * https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
+ * which simultaneously expand and restrict the set of allowed characters.
+ *
* @param string $str The string to clean
* @param bool $force_php
* @return string
@@ -122,15 +130,12 @@ class HTMLPurifier_Encoder
* function that needs to be able to understand UTF-8 characters.
* As of right now, only smart lossless character encoding converters
* would need that, and I'm probably not going to implement them.
- * Once again, PHP 6 should solve all our problems.
*/
public static function cleanUTF8($str, $force_php = false)
{
// UTF-8 validity is checked since PHP 4.3.5
// This is an optimization: if the string is already valid UTF-8, no
// need to do PHP stuff. 99% of the time, this will be the case.
- // The regexp matches the XML char production, as well as well as excluding
- // non-SGML codepoints U+007F to U+009F
if (preg_match(
'/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
$str
@@ -255,6 +260,7 @@ class HTMLPurifier_Encoder
// 7F-9F is not strictly prohibited by XML,
// but it is non-SGML, and thus we don't allow it
(0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
+ (0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
(0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
)
) {
diff --git a/extlib/HTMLPurifier/HTMLPurifier/EntityParser.php b/extlib/HTMLPurifier/HTMLPurifier/EntityParser.php
index 61529dcd9d..c372b5a6a6 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/EntityParser.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/EntityParser.php
@@ -16,6 +16,138 @@ class HTMLPurifier_EntityParser
*/
protected $_entity_lookup;
+ /**
+ * Callback regex string for entities in text.
+ * @type string
+ */
+ protected $_textEntitiesRegex;
+
+ /**
+ * Callback regex string for entities in attributes.
+ * @type string
+ */
+ protected $_attrEntitiesRegex;
+
+ /**
+ * Tests if the beginning of a string is a semi-optional regex
+ */
+ protected $_semiOptionalPrefixRegex;
+
+ public function __construct() {
+ // From
+ // http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon
+ $semi_optional = "quot|QUOT|lt|LT|gt|GT|amp|AMP|AElig|Aacute|Acirc|Agrave|Aring|Atilde|Auml|COPY|Ccedil|ETH|Eacute|Ecirc|Egrave|Euml|Iacute|Icirc|Igrave|Iuml|Ntilde|Oacute|Ocirc|Ograve|Oslash|Otilde|Ouml|REG|THORN|Uacute|Ucirc|Ugrave|Uuml|Yacute|aacute|acirc|acute|aelig|agrave|aring|atilde|auml|brvbar|ccedil|cedil|cent|copy|curren|deg|divide|eacute|ecirc|egrave|eth|euml|frac12|frac14|frac34|iacute|icirc|iexcl|igrave|iquest|iuml|laquo|macr|micro|middot|nbsp|not|ntilde|oacute|ocirc|ograve|ordf|ordm|oslash|otilde|ouml|para|plusmn|pound|raquo|reg|sect|shy|sup1|sup2|sup3|szlig|thorn|times|uacute|ucirc|ugrave|uml|uuml|yacute|yen|yuml";
+
+ // NB: three empty captures to put the fourth match in the right
+ // place
+ $this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/";
+
+ $this->_textEntitiesRegex =
+ '/&(?:'.
+ // hex
+ '[#]x([a-fA-F0-9]+);?|'.
+ // dec
+ '[#]0*(\d+);?|'.
+ // string (mandatory semicolon)
+ // NB: order matters: match semicolon preferentially
+ '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
+ // string (optional semicolon)
+ "($semi_optional)".
+ ')/';
+
+ $this->_attrEntitiesRegex =
+ '/&(?:'.
+ // hex
+ '[#]x([a-fA-F0-9]+);?|'.
+ // dec
+ '[#]0*(\d+);?|'.
+ // string (mandatory semicolon)
+ // NB: order matters: match semicolon preferentially
+ '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
+ // string (optional semicolon)
+ // don't match if trailing is equals or alphanumeric (URL
+ // like)
+ "($semi_optional)(?![=;A-Za-z0-9])".
+ ')/';
+
+ }
+
+ /**
+ * Substitute entities with the parsed equivalents. Use this on
+ * textual data in an HTML document (as opposed to attributes.)
+ *
+ * @param string $string String to have entities parsed.
+ * @return string Parsed string.
+ */
+ public function substituteTextEntities($string)
+ {
+ return preg_replace_callback(
+ $this->_textEntitiesRegex,
+ array($this, 'entityCallback'),
+ $string
+ );
+ }
+
+ /**
+ * Substitute entities with the parsed equivalents. Use this on
+ * attribute contents in documents.
+ *
+ * @param string $string String to have entities parsed.
+ * @return string Parsed string.
+ */
+ public function substituteAttrEntities($string)
+ {
+ return preg_replace_callback(
+ $this->_attrEntitiesRegex,
+ array($this, 'entityCallback'),
+ $string
+ );
+ }
+
+ /**
+ * Callback function for substituteNonSpecialEntities() that does the work.
+ *
+ * @param array $matches PCRE matches array, with 0 the entire match, and
+ * either index 1, 2 or 3 set with a hex value, dec value,
+ * or string (respectively).
+ * @return string Replacement string.
+ */
+
+ protected function entityCallback($matches)
+ {
+ $entity = $matches[0];
+ $hex_part = @$matches[1];
+ $dec_part = @$matches[2];
+ $named_part = empty($matches[3]) ? @$matches[4] : $matches[3];
+ if ($hex_part !== NULL && $hex_part !== "") {
+ return HTMLPurifier_Encoder::unichr(hexdec($hex_part));
+ } elseif ($dec_part !== NULL && $dec_part !== "") {
+ return HTMLPurifier_Encoder::unichr((int) $dec_part);
+ } else {
+ if (!$this->_entity_lookup) {
+ $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
+ }
+ if (isset($this->_entity_lookup->table[$named_part])) {
+ return $this->_entity_lookup->table[$named_part];
+ } else {
+ // exact match didn't match anything, so test if
+ // any of the semicolon optional match the prefix.
+ // Test that this is an EXACT match is important to
+ // prevent infinite loop
+ if (!empty($matches[3])) {
+ return preg_replace_callback(
+ $this->_semiOptionalPrefixRegex,
+ array($this, 'entityCallback'),
+ $entity
+ );
+ }
+ return $entity;
+ }
+ }
+ }
+
+ // LEGACY CODE BELOW
+
/**
* Callback regex string for parsing entities.
* @type string
@@ -144,7 +276,7 @@ class HTMLPurifier_EntityParser
$entity;
} else {
return isset($this->_special_ent2dec[$matches[3]]) ?
- $this->_special_ent2dec[$matches[3]] :
+ $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
$entity;
}
}
diff --git a/extlib/HTMLPurifier/HTMLPurifier/Filter/ExtractStyleBlocks.php b/extlib/HTMLPurifier/HTMLPurifier/Filter/ExtractStyleBlocks.php
index 08e62c16bf..66f70b0fc0 100644
--- a/extlib/HTMLPurifier/HTMLPurifier/Filter/ExtractStyleBlocks.php
+++ b/extlib/HTMLPurifier/HTMLPurifier/Filter/ExtractStyleBlocks.php
@@ -95,7 +95,10 @@ class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
if ($tidy !== null) {
$this->_tidy = $tidy;
}
- $html = preg_replace_callback('##isU', array($this, 'styleCallback'), $html);
+ // NB: this must be NON-greedy because if we have
+ //
+ // we must not grab foo