'PHP.net frontpage news', 'releases' => 'New PHP release', 'conferences' => 'Conference announcement', 'cfp' => 'Call for Papers', ]; public const WEBROOT = "https://www.php.net"; public const PHPWEB = __DIR__ . '/../../'; public const ARCHIVE_FILE_REL = 'archive/archive.xml'; public const ARCHIVE_FILE_ABS = self::PHPWEB . self::ARCHIVE_FILE_REL; public const ARCHIVE_ENTRIES_REL = 'archive/entries/'; public const ARCHIVE_ENTRIES_ABS = self::PHPWEB . self::ARCHIVE_ENTRIES_REL; public const IMAGE_PATH_REL = 'images/news/'; public const IMAGE_PATH_ABS = self::PHPWEB . self::IMAGE_PATH_REL; protected $title = ''; protected $categories = []; protected $conf_time = 0; protected $image = []; protected $content = ''; protected $id = ''; public function setTitle(string $title): self { $this->title = $title; return $this; } public function setCategories(array $cats): self { foreach ($cats as $cat) { if (!isset(self::CATEGORIES[$cat])) { throw new \Exception("Unknown category: $cat"); } } $this->categories = $cats; return $this; } public function addCategory(string $cat): self { if (!isset(self::CATEGORIES[$cat])) { throw new \Exception("Unknown category: $cat"); } if (!in_array($cat, $this->categories, false)) { $this->categories[] = $cat; } return $this; } public function isConference(): bool { return (bool)array_intersect($this->categories, ['cfp', 'conferences']); } public function setConfTime(int $time): self { $this->conf_time = $time; return $this; } public function setImage(string $path, string $title, ?string $link): self { if (basename($path) !== $path) { throw new \Exception('path must be a simple file name under ' . self::IMAGE_PATH_REL); } if (!file_exists(self::IMAGE_PATH_ABS . $path)) { throw new \Exception('Image not found at web-php/' . self::IMAGE_PATH_REL . $path); } $this->image = [ 'path' => $path, 'title' => $title, 'link' => $link, ]; return $this; } public function setContent(string $content): self { if (empty($content)) { throw new \Exception('Content must not be empty'); } $this->content = $content; return $this; } public function getId(): string { return $this->id; } public function save(): self { if (empty($this->id)) { $this->id = self::selectNextId(); } // Create the XML document. $dom = new \DOMDocument("1.0", "utf-8"); $dom->formatOutput = true; $dom->preserveWhiteSpace = false; $item = $dom->createElementNs("http://www.w3.org/2005/Atom", "entry"); $href = self::WEBROOT . ($this->isConference() ? '/conferences/index.php' : '/index.php'); $archive = self::WEBROOT . "/archive/" . date('Y', $_SERVER['REQUEST_TIME']) . ".php#{$this->id}"; $link = ($this->image['link'] ?? null) ?: $archive; self::ce($dom, "title", $this->title, [], $item); self::ce($dom, "id", $archive, [], $item); self::ce($dom, "published", date(DATE_ATOM), [], $item); self::ce($dom, "updated", date(DATE_ATOM), [], $item); self::ce($dom, "link", null, ['href' => "{$href}#{$this->id}", "rel" => "alternate", "type" => "text/html"], $item); self::ce($dom, "link", null, ['href' => $link, 'rel' => 'via', 'type' => 'text/html'], $item); if (!empty($this->conf_time)) { $item->appendChild($dom->createElementNs("http://php.net/ns/news", "finalTeaserDate", date("Y-m-d", $this->conf_time))); } foreach ($this->categories as $cat) { self::ce($dom, "category", null, ['term' => $cat, "label" => self::CATEGORIES[$cat]], $item); } if ($this->image['path'] ?? '') { $image = $item->appendChild($dom->createElementNs("http://php.net/ns/news", "newsImage", $this->image['path'])); $image->setAttribute("link", $this->image['link']); $image->setAttribute("title", $this->image['title']); } $content = self::ce($dom, "content", null, [], $item); // Slurp content into our DOM. $tdoc = new \DOMDocument("1.0", "utf-8"); $tdoc->formatOutput = true; if ($tdoc->loadXML("
{$this->content}
")) { $content->setAttribute("type", "xhtml"); $div = $content->appendChild($dom->createElement("div")); $div->setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); foreach ($tdoc->firstChild->childNodes as $node) { $div->appendChild($dom->importNode($node, true)); } } else { fwrite(STDERR, "There is something wrong with your xhtml, falling back to html"); $content->setAttribute("type", "html"); $content->nodeValue = $this->content; } $dom->appendChild($item); $dom->save(self::ARCHIVE_ENTRIES_ABS . $this->id . ".xml"); return $this; } public function updateArchiveXML(): self { if (empty($this->id)) { throw new \Exception('Entry must be saved before updating archive XML'); } $arch = new \DOMDocument("1.0", "utf-8"); $arch->formatOutput = true; $arch->preserveWhiteSpace = false; $arch->load(self::ARCHIVE_FILE_ABS); $first = $arch->createElementNs("http://www.w3.org/2001/XInclude", "xi:include"); $first->setAttribute("href", "entries/{$this->id}.xml"); $second = $arch->getElementsByTagNameNs("http://www.w3.org/2001/XInclude", "include")->item(0); $arch->documentElement->insertBefore($first, $second); $arch->save(self::ARCHIVE_FILE_ABS); return $this; } private static function selectNextId(): string { $filename = date("Y-m-d", $_SERVER["REQUEST_TIME"]); $count = 0; do { $count++; $id = $filename . "-" . $count; $basename = "{$id}.xml"; } while (file_exists(self::ARCHIVE_ENTRIES_ABS . $basename)); return $id; } private static function ce(\DOMDocument $d, string $name, $value, array $attrs = [], ?\DOMNode $to = null) { if ($value) { $n = $d->createElement($name, $value); } else { $n = $d->createElement($name); } foreach ($attrs as $k => $v) { $n->setAttribute($k, $v); } if ($to) { return $to->appendChild($n); } return $n; } }