[XML formats] Ensure elements are connected to DOM before further manipulation (#2806)

We are setting xmlns attributes at the root element but PHP would
still attach redundant ones to the DOM elements created with `createElementNS`.
That was because PHP reconciles namespace attributes when appending elements to DOM
but since we previously only attached the elements after all children were attached,
the reconciliation algorithm was not able to see the root element’s attributes.

To fix this, let’s attach each element to its parent immediately after it is created.
This commit is contained in:
Jan Tojnar 2022-06-09 18:33:23 +02:00 committed by GitHub
parent 37f211a37e
commit 1af6cbeb1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 43 deletions

View File

@ -28,17 +28,17 @@ class AtomFormat extends FormatAbstract{
$document = new DomDocument('1.0', $this->getCharset()); $document = new DomDocument('1.0', $this->getCharset());
$document->formatOutput = true; $document->formatOutput = true;
$feed = $document->createElementNS(self::ATOM_NS, 'feed'); $feed = $document->createElementNS(self::ATOM_NS, 'feed');
$feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS);
$document->appendChild($feed); $document->appendChild($feed);
$feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS);
$title = $document->createElement('title'); $title = $document->createElement('title');
$feed->appendChild($title);
$title->setAttribute('type', 'text'); $title->setAttribute('type', 'text');
$title->appendChild($document->createTextNode($extraInfos['name'])); $title->appendChild($document->createTextNode($extraInfos['name']));
$feed->appendChild($title);
$id = $document->createElement('id'); $id = $document->createElement('id');
$id->appendChild($document->createTextNode($feedUrl));
$feed->appendChild($id); $feed->appendChild($id);
$id->appendChild($document->createTextNode($feedUrl));
$uriparts = parse_url($uri); $uriparts = parse_url($uri);
if(!empty($extraInfos['icon'])) { if(!empty($extraInfos['icon'])) {
@ -47,38 +47,38 @@ class AtomFormat extends FormatAbstract{
$iconUrl = $uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico'; $iconUrl = $uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico';
} }
$icon = $document->createElement('icon'); $icon = $document->createElement('icon');
$icon->appendChild($document->createTextNode($iconUrl));
$feed->appendChild($icon); $feed->appendChild($icon);
$icon->appendChild($document->createTextNode($iconUrl));
$logo = $document->createElement('logo'); $logo = $document->createElement('logo');
$logo->appendChild($document->createTextNode($iconUrl));
$feed->appendChild($logo); $feed->appendChild($logo);
$logo->appendChild($document->createTextNode($iconUrl));
$feedTimestamp = gmdate(DATE_ATOM, $this->lastModified); $feedTimestamp = gmdate(DATE_ATOM, $this->lastModified);
$updated = $document->createElement('updated'); $updated = $document->createElement('updated');
$updated->appendChild($document->createTextNode($feedTimestamp));
$feed->appendChild($updated); $feed->appendChild($updated);
$updated->appendChild($document->createTextNode($feedTimestamp));
// since we can't guarantee that all items have an author, // since we can't guarantee that all items have an author,
// a global feed author is mandatory // a global feed author is mandatory
$feedAuthor = 'RSS-Bridge'; $feedAuthor = 'RSS-Bridge';
$author = $document->createElement('author'); $author = $document->createElement('author');
$authorName = $document->createElement('name');
$authorName->appendChild($document->createTextNode($feedAuthor));
$author->appendChild($authorName);
$feed->appendChild($author); $feed->appendChild($author);
$authorName = $document->createElement('name');
$author->appendChild($authorName);
$authorName->appendChild($document->createTextNode($feedAuthor));
$linkAlternate = $document->createElement('link'); $linkAlternate = $document->createElement('link');
$feed->appendChild($linkAlternate);
$linkAlternate->setAttribute('rel', 'alternate'); $linkAlternate->setAttribute('rel', 'alternate');
$linkAlternate->setAttribute('type', 'text/html'); $linkAlternate->setAttribute('type', 'text/html');
$linkAlternate->setAttribute('href', $uri); $linkAlternate->setAttribute('href', $uri);
$feed->appendChild($linkAlternate);
$linkSelf = $document->createElement('link'); $linkSelf = $document->createElement('link');
$feed->appendChild($linkSelf);
$linkSelf->setAttribute('rel', 'self'); $linkSelf->setAttribute('rel', 'self');
$linkSelf->setAttribute('type', 'application/atom+xml'); $linkSelf->setAttribute('type', 'application/atom+xml');
$linkSelf->setAttribute('href', $feedUrl); $linkSelf->setAttribute('href', $feedUrl);
$feed->appendChild($linkSelf);
foreach($this->getItems() as $item) { foreach($this->getItems() as $item) {
$entryTimestamp = $item->getTimestamp(); $entryTimestamp = $item->getTimestamp();
@ -111,39 +111,40 @@ class AtomFormat extends FormatAbstract{
$entryContent = ' '; $entryContent = ' ';
$entry = $document->createElement('entry'); $entry = $document->createElement('entry');
$feed->appendChild($entry);
$title = $document->createElement('title'); $title = $document->createElement('title');
$entry->appendChild($title);
$title->setAttribute('type', 'html'); $title->setAttribute('type', 'html');
$title->appendChild($document->createTextNode($entryTitle)); $title->appendChild($document->createTextNode($entryTitle));
$entry->appendChild($title);
$entryTimestamp = gmdate(DATE_ATOM, $entryTimestamp); $entryTimestamp = gmdate(DATE_ATOM, $entryTimestamp);
$published = $document->createElement('published'); $published = $document->createElement('published');
$published->appendChild($document->createTextNode($entryTimestamp));
$entry->appendChild($published); $entry->appendChild($published);
$published->appendChild($document->createTextNode($entryTimestamp));
$updated = $document->createElement('updated'); $updated = $document->createElement('updated');
$updated->appendChild($document->createTextNode($entryTimestamp));
$entry->appendChild($updated); $entry->appendChild($updated);
$updated->appendChild($document->createTextNode($entryTimestamp));
$id = $document->createElement('id'); $id = $document->createElement('id');
$id->appendChild($document->createTextNode($entryID));
$entry->appendChild($id); $entry->appendChild($id);
$id->appendChild($document->createTextNode($entryID));
if (!empty($entryUri)) { if (!empty($entryUri)) {
$entryLinkAlternate = $document->createElement('link'); $entryLinkAlternate = $document->createElement('link');
$entry->appendChild($entryLinkAlternate);
$entryLinkAlternate->setAttribute('rel', 'alternate'); $entryLinkAlternate->setAttribute('rel', 'alternate');
$entryLinkAlternate->setAttribute('type', 'text/html'); $entryLinkAlternate->setAttribute('type', 'text/html');
$entryLinkAlternate->setAttribute('href', $entryUri); $entryLinkAlternate->setAttribute('href', $entryUri);
$entry->appendChild($entryLinkAlternate);
} }
if (!empty($item->getAuthor())) { if (!empty($item->getAuthor())) {
$author = $document->createElement('author'); $author = $document->createElement('author');
$authorName = $document->createElement('name');
$authorName->appendChild($document->createTextNode($item->getAuthor()));
$author->appendChild($authorName);
$entry->appendChild($author); $entry->appendChild($author);
$authorName = $document->createElement('name');
$author->appendChild($authorName);
$authorName->appendChild($document->createTextNode($item->getAuthor()));
} }
$content = $document->createElement('content'); $content = $document->createElement('content');
@ -153,25 +154,23 @@ class AtomFormat extends FormatAbstract{
foreach($item->getEnclosures() as $enclosure) { foreach($item->getEnclosures() as $enclosure) {
$entryEnclosure = $document->createElement('link'); $entryEnclosure = $document->createElement('link');
$entry->appendChild($entryEnclosure);
$entryEnclosure->setAttribute('rel', 'enclosure'); $entryEnclosure->setAttribute('rel', 'enclosure');
$entryEnclosure->setAttribute('type', getMimeType($enclosure)); $entryEnclosure->setAttribute('type', getMimeType($enclosure));
$entryEnclosure->setAttribute('href', $enclosure); $entryEnclosure->setAttribute('href', $enclosure);
$entry->appendChild($entryEnclosure);
} }
foreach($item->getCategories() as $category) { foreach($item->getCategories() as $category) {
$entryCategory = $document->createElement('category'); $entryCategory = $document->createElement('category');
$entryCategory->setAttribute('term', $category);
$entry->appendChild($entryCategory); $entry->appendChild($entryCategory);
$entryCategory->setAttribute('term', $category);
} }
if (!empty($item->thumbnail)) { if (!empty($item->thumbnail)) {
$thumbnail = $document->createElementNS(self::MRSS_NS, 'media:thumbnail'); $thumbnail = $document->createElementNS(self::MRSS_NS, 'thumbnail');
$thumbnail->setAttribute('url', $item->thumbnail);
$entry->appendChild($thumbnail); $entry->appendChild($thumbnail);
$thumbnail->setAttribute('url', $item->thumbnail);
} }
$feed->appendChild($entry);
} }
$toReturn = $document->saveXML(); $toReturn = $document->saveXML();

View File

@ -48,26 +48,26 @@ class MrssFormat extends FormatAbstract {
$document = new DomDocument('1.0', $this->getCharset()); $document = new DomDocument('1.0', $this->getCharset());
$document->formatOutput = true; $document->formatOutput = true;
$feed = $document->createElement('rss'); $feed = $document->createElement('rss');
$document->appendChild($feed);
$feed->setAttribute('version', '2.0'); $feed->setAttribute('version', '2.0');
$feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:atom', self::ATOM_NS); $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:atom', self::ATOM_NS);
$feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS); $feed->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MRSS_NS);
$document->appendChild($feed);
$channel = $document->createElement('channel'); $channel = $document->createElement('channel');
$feed->appendChild($channel); $feed->appendChild($channel);
$title = $extraInfos['name']; $title = $extraInfos['name'];
$channelTitle = $document->createElement('title'); $channelTitle = $document->createElement('title');
$channelTitle->appendChild($document->createTextNode($title));
$channel->appendChild($channelTitle); $channel->appendChild($channelTitle);
$channelTitle->appendChild($document->createTextNode($title));
$link = $document->createElement('link'); $link = $document->createElement('link');
$link->appendChild($document->createTextNode($uri));
$channel->appendChild($link); $channel->appendChild($link);
$link->appendChild($document->createTextNode($uri));
$description = $document->createElement('description'); $description = $document->createElement('description');
$description->appendChild($document->createTextNode($extraInfos['name']));
$channel->appendChild($description); $channel->appendChild($description);
$description->appendChild($document->createTextNode($extraInfos['name']));
$icon = $extraInfos['icon']; $icon = $extraInfos['icon'];
if (!empty($icon) && in_array(substr($icon, -4), self::ALLOWED_IMAGE_EXT)) { if (!empty($icon) && in_array(substr($icon, -4), self::ALLOWED_IMAGE_EXT)) {
@ -84,17 +84,17 @@ class MrssFormat extends FormatAbstract {
$feedImage->appendChild($iconLink); $feedImage->appendChild($iconLink);
} }
$linkAlternate = $document->createElementNS(self::ATOM_NS, 'atom:link'); $linkAlternate = $document->createElementNS(self::ATOM_NS, 'link');
$channel->appendChild($linkAlternate);
$linkAlternate->setAttribute('rel', 'alternate'); $linkAlternate->setAttribute('rel', 'alternate');
$linkAlternate->setAttribute('type', 'text/html'); $linkAlternate->setAttribute('type', 'text/html');
$linkAlternate->setAttribute('href', $uri); $linkAlternate->setAttribute('href', $uri);
$channel->appendChild($linkAlternate);
$linkSelf = $document->createElementNS(self::ATOM_NS, 'atom:link'); $linkSelf = $document->createElementNS(self::ATOM_NS, 'link');
$channel->appendChild($linkSelf);
$linkSelf->setAttribute('rel', 'self'); $linkSelf->setAttribute('rel', 'self');
$linkSelf->setAttribute('type', 'application/atom+xml'); $linkSelf->setAttribute('type', 'application/atom+xml');
$linkSelf->setAttribute('href', $feedUrl); $linkSelf->setAttribute('href', $feedUrl);
$channel->appendChild($linkSelf);
foreach($this->getItems() as $item) { foreach($this->getItems() as $item) {
$itemTimestamp = $item->getTimestamp(); $itemTimestamp = $item->getTimestamp();
@ -113,51 +113,50 @@ class MrssFormat extends FormatAbstract {
$entryID = hash('sha1', $itemTitle . $itemContent); $entryID = hash('sha1', $itemTitle . $itemContent);
$entry = $document->createElement('item'); $entry = $document->createElement('item');
$channel->appendChild($entry);
if (!empty($itemTitle)) { if (!empty($itemTitle)) {
$entryTitle = $document->createElement('title'); $entryTitle = $document->createElement('title');
$entryTitle->appendChild($document->createTextNode($itemTitle));
$entry->appendChild($entryTitle); $entry->appendChild($entryTitle);
$entryTitle->appendChild($document->createTextNode($itemTitle));
} }
if (!empty($itemUri)) { if (!empty($itemUri)) {
$entryLink = $document->createElement('link'); $entryLink = $document->createElement('link');
$entryLink->appendChild($document->createTextNode($itemUri));
$entry->appendChild($entryLink); $entry->appendChild($entryLink);
$entryLink->appendChild($document->createTextNode($itemUri));
} }
$entryGuid = $document->createElement('guid'); $entryGuid = $document->createElement('guid');
$entryGuid->setAttribute('isPermaLink', $isPermaLink); $entryGuid->setAttribute('isPermaLink', $isPermaLink);
$entryGuid->appendChild($document->createTextNode($entryID));
$entry->appendChild($entryGuid); $entry->appendChild($entryGuid);
$entryGuid->appendChild($document->createTextNode($entryID));
if (!empty($itemTimestamp)) { if (!empty($itemTimestamp)) {
$entryPublished = $document->createElement('pubDate'); $entryPublished = $document->createElement('pubDate');
$entryPublished->appendChild($document->createTextNode(gmdate(DATE_RFC2822, $itemTimestamp)));
$entry->appendChild($entryPublished); $entry->appendChild($entryPublished);
$entryPublished->appendChild($document->createTextNode(gmdate(DATE_RFC2822, $itemTimestamp)));
} }
if (!empty($itemContent)) { if (!empty($itemContent)) {
$entryDescription = $document->createElement('description'); $entryDescription = $document->createElement('description');
$entryDescription->appendChild($document->createTextNode($itemContent));
$entry->appendChild($entryDescription); $entry->appendChild($entryDescription);
$entryDescription->appendChild($document->createTextNode($itemContent));
} }
foreach($item->getEnclosures() as $enclosure) { foreach($item->getEnclosures() as $enclosure) {
$entryEnclosure = $document->createElementNS(self::MRSS_NS, 'media:content'); $entryEnclosure = $document->createElementNS(self::MRSS_NS, 'content');
$entry->appendChild($entryEnclosure);
$entryEnclosure->setAttribute('url', $enclosure); $entryEnclosure->setAttribute('url', $enclosure);
$entryEnclosure->setAttribute('type', getMimeType($enclosure)); $entryEnclosure->setAttribute('type', getMimeType($enclosure));
$entry->appendChild($entryEnclosure);
} }
$entryCategories = ''; $entryCategories = '';
foreach($item->getCategories() as $category) { foreach($item->getCategories() as $category) {
$entryCategory = $document->createElement('category'); $entryCategory = $document->createElement('category');
$entryCategory->appendChild($document->createTextNode($category));
$entry->appendChild($entryCategory); $entry->appendChild($entryCategory);
$entryCategory->appendChild($document->createTextNode($category));
} }
$channel->appendChild($entry);
} }
$toReturn = $document->saveXML(); $toReturn = $document->saveXML();