1<?php 2 3namespace phpweb\News; 4 5class Entry 6{ 7 public const CATEGORIES = [ 8 'frontpage' => 'PHP.net frontpage news', 9 'releases' => 'New PHP release', 10 'conferences' => 'Conference announcement', 11 'cfp' => 'Call for Papers', 12 ]; 13 14 public const WEBROOT = "https://www.php.net"; 15 16 public const PHPWEB = __DIR__ . '/../../'; 17 18 public const ARCHIVE_FILE_REL = 'archive/archive.xml'; 19 20 public const ARCHIVE_FILE_ABS = self::PHPWEB . self::ARCHIVE_FILE_REL; 21 22 public const ARCHIVE_ENTRIES_REL = 'archive/entries/'; 23 24 public const ARCHIVE_ENTRIES_ABS = self::PHPWEB . self::ARCHIVE_ENTRIES_REL; 25 26 public const IMAGE_PATH_REL = 'images/news/'; 27 28 public const IMAGE_PATH_ABS = self::PHPWEB . self::IMAGE_PATH_REL; 29 30 protected $title = ''; 31 32 protected $categories = []; 33 34 protected $conf_time = 0; 35 36 protected $image = []; 37 38 protected $content = ''; 39 40 protected $id = ''; 41 42 public function setTitle(string $title): self { 43 $this->title = $title; 44 return $this; 45 } 46 47 public function setCategories(array $cats): self { 48 foreach ($cats as $cat) { 49 if (!isset(self::CATEGORIES[$cat])) { 50 throw new \Exception("Unknown category: $cat"); 51 } 52 } 53 $this->categories = $cats; 54 return $this; 55 } 56 57 public function addCategory(string $cat): self { 58 if (!isset(self::CATEGORIES[$cat])) { 59 throw new \Exception("Unknown category: $cat"); 60 } 61 if (!in_array($cat, $this->categories, false)) { 62 $this->categories[] = $cat; 63 } 64 return $this; 65 } 66 67 public function isConference(): bool { 68 return (bool)array_intersect($this->categories, ['cfp', 'conferences']); 69 } 70 71 public function setConfTime(int $time): self { 72 $this->conf_time = $time; 73 return $this; 74 } 75 76 public function setImage(string $path, string $title, ?string $link): self { 77 if (basename($path) !== $path) { 78 throw new \Exception('path must be a simple file name under ' . self::IMAGE_PATH_REL); 79 } 80 if (!file_exists(self::IMAGE_PATH_ABS . $path)) { 81 throw new \Exception('Image not found at web-php/' . self::IMAGE_PATH_REL . $path); 82 } 83 $this->image = [ 84 'path' => $path, 85 'title' => $title, 86 'link' => $link, 87 ]; 88 return $this; 89 } 90 91 public function setContent(string $content): self { 92 if (empty($content)) { 93 throw new \Exception('Content must not be empty'); 94 } 95 $this->content = $content; 96 return $this; 97 } 98 99 public function getId(): string { 100 return $this->id; 101 } 102 103 public function save(): self { 104 if (empty($this->id)) { 105 $this->id = self::selectNextId(); 106 } 107 108 // Create the XML document. 109 $dom = new \DOMDocument("1.0", "utf-8"); 110 $dom->formatOutput = true; 111 $dom->preserveWhiteSpace = false; 112 $item = $dom->createElementNs("http://www.w3.org/2005/Atom", "entry"); 113 114 $href = self::WEBROOT . ($this->isConference() ? '/conferences/index.php' : '/index.php'); 115 $archive = self::WEBROOT . "/archive/" . date('Y', $_SERVER['REQUEST_TIME']) . ".php#{$this->id}"; 116 $link = ($this->image['link'] ?? null) ?: $archive; 117 118 self::ce($dom, "title", $this->title, [], $item); 119 self::ce($dom, "id", $archive, [], $item); 120 self::ce($dom, "published", date(DATE_ATOM), [], $item); 121 self::ce($dom, "updated", date(DATE_ATOM), [], $item); 122 self::ce($dom, "link", null, ['href' => "{$href}#{$this->id}", "rel" => "alternate", "type" => "text/html"], $item); 123 self::ce($dom, "link", null, ['href' => $link, 'rel' => 'via', 'type' => 'text/html'], $item); 124 125 if (!empty($this->conf_time)) { 126 $item->appendChild($dom->createElementNs("http://php.net/ns/news", "finalTeaserDate", date("Y-m-d", $this->conf_time))); 127 } 128 129 foreach ($this->categories as $cat) { 130 self::ce($dom, "category", null, ['term' => $cat, "label" => self::CATEGORIES[$cat]], $item); 131 } 132 133 if ($this->image['path'] ?? '') { 134 $image = $item->appendChild($dom->createElementNs("http://php.net/ns/news", "newsImage", $this->image['path'])); 135 $image->setAttribute("link", $this->image['link']); 136 $image->setAttribute("title", $this->image['title']); 137 } 138 139 $content = self::ce($dom, "content", null, [], $item); 140 141 // Slurp content into our DOM. 142 $tdoc = new \DOMDocument("1.0", "utf-8"); 143 $tdoc->formatOutput = true; 144 if ($tdoc->loadXML("<div>{$this->content} </div>")) { 145 $content->setAttribute("type", "xhtml"); 146 $div = $content->appendChild($dom->createElement("div")); 147 $div->setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 148 foreach ($tdoc->firstChild->childNodes as $node) { 149 $div->appendChild($dom->importNode($node, true)); 150 } 151 } else { 152 fwrite(STDERR, "There is something wrong with your xhtml, falling back to html"); 153 $content->setAttribute("type", "html"); 154 $content->nodeValue = $this->content; 155 } 156 157 $dom->appendChild($item); 158 $dom->save(self::ARCHIVE_ENTRIES_ABS . $this->id . ".xml"); 159 160 return $this; 161 } 162 163 public function updateArchiveXML(): self { 164 if (empty($this->id)) { 165 throw new \Exception('Entry must be saved before updating archive XML'); 166 } 167 168 $arch = new \DOMDocument("1.0", "utf-8"); 169 $arch->formatOutput = true; 170 $arch->preserveWhiteSpace = false; 171 $arch->load(self::ARCHIVE_FILE_ABS); 172 173 $first = $arch->createElementNs("http://www.w3.org/2001/XInclude", "xi:include"); 174 $first->setAttribute("href", "entries/{$this->id}.xml"); 175 176 $second = $arch->getElementsByTagNameNs("http://www.w3.org/2001/XInclude", "include")->item(0); 177 $arch->documentElement->insertBefore($first, $second); 178 $arch->save(self::ARCHIVE_FILE_ABS); 179 180 return $this; 181 } 182 183 private static function selectNextId(): string { 184 $filename = date("Y-m-d", $_SERVER["REQUEST_TIME"]); 185 $count = 0; 186 do { 187 $count++; 188 $id = $filename . "-" . $count; 189 $basename = "{$id}.xml"; 190 } while (file_exists(self::ARCHIVE_ENTRIES_ABS . $basename)); 191 192 return $id; 193 } 194 195 private static function ce(\DOMDocument $d, string $name, $value, array $attrs = [], ?\DOMNode $to = null) { 196 if ($value) { 197 $n = $d->createElement($name, $value); 198 } else { 199 $n = $d->createElement($name); 200 } 201 foreach ($attrs as $k => $v) { 202 $n->setAttribute($k, $v); 203 } 204 if ($to) { 205 return $to->appendChild($n); 206 } 207 return $n; 208 } 209} 210 211