com web/php: Refactor createNewsEntry for non-interactive use: bin/createNewsEntry bin/createReleaseEntry include/news_entry .inc

From: Date: Wed, 17 Jun 2020 22:04:52 +0000
Subject: com web/php: Refactor createNewsEntry for non-interactive use: bin/createNewsEntry bin/createReleaseEntry include/news_entry .inc
Groups: php.webmaster 
Request: Send a blank email to php-webmaster+get-28676@lists.php.net to get a copy of this message
Commit: 7971068c37a43fdd24f37542682729ab8bc6e7dd Author: Sara Golemon <pollita@php.net> Wed, 17 Jun 2020 20:19:30 +0000 Parents: 0df7236ff958b62aae073a66adc9fa25b8f3096a Branches: master Link: http://git.php.net/?p=web/php.git;a=commitdiff;h=7971068c37a43fdd24f37542682729ab8bc6e7dd Log: Refactor createNewsEntry for non-interactive use Add bin/createReleaseEntry script for common release announcements Changed paths: M bin/createNewsEntry A bin/createReleaseEntry A include/news_entry.inc

diff --git a/bin/createNewsEntry b/bin/createNewsEntry index c83b18471..bf843cd08 100755 --- a/bin/createNewsEntry +++ b/bin/createNewsEntry @@ -2,20 +2,8 @@ <?php PHP_SAPI == 'cli' or die("Please run this script using the cli sapi"); -// Script config -const BASE = "https://www.php.net"; -const PHPWEB = __DIR__ . '/../'; -const ARCHIVE_FILE_REL = 'archive/archive.xml'; -const ARCHIVE_FILE_ABS = PHPWEB . ARCHIVE_FILE_REL; -const ARCHIVE_ENTRIES_REL = 'archive/entries/'; -const ARCHIVE_ENTRIES_ABS = PHPWEB . ARCHIVE_ENTRIES_REL; - -$categories = [ - 'frontpage' => 'PHP.net frontpage news', - 'releases' => 'New PHP release', - 'conferences' => 'Conference announcement', - 'cfp' => 'Call for Papers', -]; +require(__DIR__ . '/../include/news_entry.inc'); +use phpweb\news\Entry; $imageRestriction = [ 'width' => 400, @@ -23,119 +11,38 @@ $imageRestriction = [ ]; // Create an entry! -if (!file_exists(ARCHIVE_FILE_ABS)) { - fwrite(STDERR, "Can't find " . ARCHIVE_FILE_REL . ", are you sure you are in phpweb/?\n"); +if (!file_exists(Entry::ARCHIVE_FILE_ABS)) { + fwrite(STDERR, "Can't find " . Entry::ARCHIVE_FILE_REL . ", are you sure you are in phpweb/?\n"); exit(1); } -$id = getNextId(); -createNewsEntry($id); -updateArchiveXML($id, ARCHIVE_FILE_ABS); - -fwrite(STDOUT, "File saved.\nPlease git diff " . ARCHIVE_FILE_REL . " and sanity-check the changes before committing\n"); -fwrite(STDOUT, "NOTE: Remeber to git add " . ARCHIVE_ENTRIES_REL . $id . ".xml !!\n"); - -// Implementation functions - -function getNextId(): string { - $filename = date("Y-m-d", $_SERVER["REQUEST_TIME"]); - $count = 0; - do { - ++$count; - $id = $filename . "-" . $count; - $basename = "{$id}.xml"; - fprintf(STDOUT, "Trying $basename\n"); - } while (file_exists(ARCHIVE_ENTRIES_ABS . $basename)); - - return $id; +if ($_SERVER['argc'] > 1) { + // getopt based non-interactive mode + $entry = parseOptions(); +} else { + // Classic interactive prompts + $entry = getEntry(); } -function createNewsEntry(string $id): void { - global $categories; +$entry->save()->updateArchiveXML(); - $entry = getEntry($id); - - // 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"); - - ce($dom, "title", $entry['title'], [], $item); - ce($dom, "id", $entry['archive'], array(), $item); - ce($dom, "published", date(DATE_ATOM), [], $item); - ce($dom, "updated", date(DATE_ATOM), [], $item); - ce($dom, "link", null, ['href' => "{$entry['href']}#id$id", "rel" => "alternate", "type" => "text/html"], $item); - ce($dom, "link", null, ['href' => $entry['via'], 'rel' => 'via', 'type' => 'text/html'], $item); - - if (isset($entry['conf-time'])) { - $item->appendChild($dom->createElementNs("http://php.net/ns/news", "finalTeaserDate", date("Y-m-d", $entry['conf-time']))); - } - - foreach ($entry['categories'] as $cat) { - ce($dom, "category", null, ['term' => $cat, "label" => $categories[$cat]], $item); - } +fwrite(STDOUT, "File saved.\nPlease git diff " . Entry::ARCHIVE_FILE_REL . " and sanity-check the changes before committing\n"); +fwrite(STDOUT, "NOTE: Remeber to git add " . Entry::ARCHIVE_ENTRIES_REL . $entry->getId() . ".xml !!\n"); - if ($entry['image'] ?? false) { - $image = $item->appendChild($dom->createElementNs("http://php.net/ns/news", "newsImage", $entry['image']['path'])); - $image->setAttribute("link", $entry['image']['link']); - $image->setAttribute("title", $entry['image']['title']); - } - - $content = ce($dom, "content", null, [], $item); - - // Slurp content into our DOM. - $tdoc = new DOMDocument("1.0", "utf-8"); - $tdoc->formatOutput = true; - if ($tdoc->loadXML("<div>{$entry['content']} </div>")) { - $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 = $entry['content']; - } - - $dom->appendChild($item); - $dom->save(ARCHIVE_ENTRIES_ABS . $id . ".xml"); -} +// Implementation functions -function ce(DOMDocument $d, string $name, $value, array $attrs = [], ?DOMNode $to = null) { - if ($value) { - $n = $d->createElement($name, $value); - } else { - $n = $d->createElement($name); +function getEntry(): Entry { + $entry = new Entry; + $entry->setTitle(getTitle()); + $entry->setCategories(selectCategories()); + if ($entry->isConference()) { + $entry->setConfTime(getConfTime()); } - foreach($attrs as $k => $v) { - $n->setAttribute($k, $v); - } - if ($to) { - return $to->appendChild($n); - } - return $n; -} -function getEntry(string $id): array { - $entry = []; + $image = getImage(); + $entry->setImage($image['path'] ?? '', $image['title'] ?? '', $image['link'] ?? ''); - $entry['title'] = getTitle(); - - $entry['categories'] = selectCategories(); - if (array_intersect($entry['categories'], ['cfp', 'conferences'])) { - $entry['href'] = BASE . '/conferences/index.php'; - $entry['conf-time'] = getConfTime(); - } else { - $entry['href'] = BASE . '/index.php'; - } - - $entry['image'] = getImage(); - $entry['content'] = getContent(); - $entry['archive'] = BASE . "/archive/" . date('Y', $_SERVER['REQUEST_TIME']) . ".php#$id"; - $entry['via'] = $entry['image']['link'] ?? $entry['archive']; + $entry->setContent(getContent()); return $entry; } @@ -150,12 +57,11 @@ function getTitle(): string { } function selectCategories(): array { for(;;) { - global $categories; - $ids = array_keys($categories); + $ids = array_keys(Entry::CATEGORIES); fwrite(STDOUT, "Categories:\n"); foreach($ids as $n => $id) { - fprintf(STDOUT, "\t%d: %-11s\t [%s]\n", $n, $categories[$id], $id); + fprintf(STDOUT, "\t%d: %-11s\t [%s]\n", $n, Entry::CATEGORIES[$id], $id); } fwrite(STDOUT, "Please select appropriate categories, seperated with space: "); @@ -166,8 +72,8 @@ function selectCategories(): array { for(;;) { }, array_filter( explode(" ", rtrim(fgets(STDIN))), - function ($c) use ($categories) { - return is_numeric($c) && ($c >= 0) && ($c < count($categories)); + function ($c) { + return is_numeric($c) && ($c >= 0) && ($c < count(Entry::CATEGORIES)); }) ); @@ -212,7 +118,7 @@ function getImage(): ?array { fwrite(STDOUT, "Enter the image name (note: the image has to exist in './images/news'): "); $path = basename(rtrim(fgets(STDIN))); - if (true === file_exists(PHPWEB . "/images/news/$path")) { + if (true === file_exists(Entry::PHPWEB . "/images/news/$path")) { $isValidImage = true; if (extension_loaded('gd')) { @@ -267,3 +173,83 @@ function updateArchiveXML(string $id, string $archiveFile): void { $arch->documentElement->insertBefore($first, $second); $arch->save($archiveFile); } + +function parseOptions(): Entry { + $opts = getopt('h', [ + 'help', + 'title:', + 'category:', + 'conf-time:', + 'image-path:', + 'image-title:', + 'image-link:', + 'content:', + 'content-file:', + ]); + if (isset($opts['h']) || isset($opts['help'])) { + echo "Usage: {$_SERVER['argv'][0]} --title 'Name of event' --category cfp ( --content 'text' | --content-file '-') [...options]\n\n"; + echo " --title 'value' The title of the entry (required)\n"; + echo " --category 'value' 'frontpage', 'release', 'cfp', or 'conference' (required; may repeat)\n"; + echo " --conf-time 'value' When the event will be occurign (cfp and conference categories only)\n"; + echo " --content 'value' Text content for the entry, may include XHTML\n"; + echo " --content-file 'value' Name of file to load content from, may not be specified with --content\n"; + echo " --image-path 'value' Basename of image file in " . Entry::IMAGE_PATH_REL . "\n"; + echo " --image-title 'value' Title for the image provided\n"; + echo " --image-link 'value' URI to direct to when clicking the image\n"; + exit(0); + } + + $entry = new Entry; + if (!isset($opts['title'])) { + fwrite(STDERR, "--title required\n"); + exit(1); + } + $entry->setTitle($opts['title']); + + if (empty($opts['category'])) { + fwrite(STDERR, "--category required\n"); + exit(1); + } + if (is_string($opts['category'])) { + $opts['category'] = [ $opts['category'] ]; + } + foreach ($opts['category'] as $cat) { + $entry->addCategory($cat); + } + if ($entry->isConference()) { + if (empty($opts['conf-time'])) { + fwrite(STDERR, "--conf-time required for conferences\n"); + exit(1); + } + $t = strtotime($opts['conf-time']); + if (!is_int($t)) { + fwrite(STDERR, "Error parsing --conf-time\n"); + exit(1); + } + $entry->setConfTime($t); + } elseif (!empty($opts['conf-time'])) { + fwrite(STDERR, "--conf-time not allowed with non-conference events\n"); + exit(1); + } + + $entry->setImage($opts['image-path'] ?? '', $opts['image-title'] ?? '', $opts['image-link'] ?? ''); + + if (isset($opts['content'])) { + if (isset($opts['content-file'])) { + fwrite(STDERR, "--content and --content-file may not be specified together\n"); + exit(1); + } + $entry->setContent($opts['content']); + } elseif (isset($opts['content-file'])) { + if ($opts['content-file'] === '-') { + $entry->setContent(stream_get_contents(STDIN)); + } else { + $entry->setContent(file_get_contents($opts['content-file'])); + } + } else { + fwrite(STDERR, "--content or --content-file required\n"); + exit(1); + } + + return $entry; +} diff --git a/bin/createReleaseEntry b/bin/createReleaseEntry new file mode 100755 index 000000000..732510486 --- /dev/null +++ b/bin/createReleaseEntry @@ -0,0 +1,48 @@ +#!/usr/bin/env php +<?php +PHP_SAPI == 'cli' or die("Please run this script using the cli sapi"); + +require(__DIR__ . '/../include/news_entry.inc'); +use phpweb\news\Entry; + +if (!file_exists(Entry::ARCHIVE_FILE_ABS)) { + fwrite(STDERR, "Can't find " . Entry::ARCHIVE_FILE_REL . ", are you sure you are in phpweb/?\n"); + exit(1); +} + +$opts = getopt('v:',['security']); +if (!isset($opts['v'])) { + echo "Usage: {$_SERVER['argv'][0]} -v 8.0.8 [ --security ]\n"; + exit(0); +} +$version = $opts['v']; +if (!preg_match('/^(\d+)\.(\d+)\.\d+?$/', $version, $matches)) { + fwrite(STDERR, "Unable to parse version identifier\n"); + exit(1); +} +$major = $matches[1]; +$branch = "{$major}.{$matches[2]}"; +$security = isset($opts['security']) ? 'security' : 'bug fix'; + +// Create content. +$template = <<<EOD +<p>The PHP development team announces the immediate availability of PHP $version. This is a $security release.</p> + +<p>All PHP $branch users are encouraged to upgrade to this version.</p> + +<p>For source downloads of PHP $version please visit our <a href="https://www.php.net/downloads.php">downloads page</a>, +Windows source and binaries can be found on <a href="https://windows.php.net/download/">windows.php.net/download/</a>. +The list of changes is recorded in the <a href="https://www.php.net/ChangeLog-{$major}.php#{$version}">ChangeLog</a>. +</p> +EOD; + +$entry = (new Entry) + ->setTitle("PHP $version Released!") + ->setCategories(['releases','frontpage']) + ->setContent($template); + +$entry->save()->updateArchiveXML(); + +fwrite(STDOUT, "File saved.\nPlease git diff " . Entry::ARCHIVE_FILE_REL . " and sanity-check the changes before committing\n"); +fwrite(STDOUT, "NOTE: Remeber to git add " . Entry::ARCHIVE_ENTRIES_REL . $entry->getId() . ".xml !!\n"); + diff --git a/include/news_entry.inc b/include/news_entry.inc new file mode 100755 index 000000000..db9c7bdc5 --- /dev/null +++ b/include/news_entry.inc @@ -0,0 +1,210 @@ +<?php + +namespace phpweb\news; + +class Entry { + const CATEGORIES = [ + 'frontpage' => 'PHP.net frontpage news', + 'releases' => 'New PHP release', + 'conferences' => 'Conference announcement', + 'cfp' => 'Call for Papers', + ]; + + const WEBROOT = "https://www.php.net"; + const PHPWEB = __DIR__ . '/../'; + const ARCHIVE_FILE_REL = 'archive/archive.xml'; + const ARCHIVE_FILE_ABS = self::PHPWEB . self::ARCHIVE_FILE_REL; + const ARCHIVE_ENTRIES_REL = 'archive/entries/'; + const ARCHIVE_ENTRIES_ABS = self::PHPWEB . self::ARCHIVE_ENTRIES_REL; + const IMAGE_PATH_REL = 'images/news/'; + const IMAGE_PATH_ABS = self::PHPWEB . self::IMAGE_PATH_REL; + + + protected $title = ''; + public function getTitle(): string { + return $this->title; + } + + public function setTitle(string $title): self { + $this->title = $title; + return $this; + } + + protected $categories = []; + 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)) { + $this->categories[] = $cat; + } + return $this; + } + public function getCategories(): array { + return $this->categories; + } + public function isConference(): bool { + return (bool)array_intersect($this->categories, ['cfp', 'conferences']); + } + + protected $conf_time = 0; + public function setConfTime(int $time): self { + $this->conf_time = $time; + return $this; + } + public function getConfTime(): int { + return $this->conf_time; + } + + protected $image = []; + 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 getImage(): array { + return $this->image; + } + + protected $content = ''; + public function setContent(string $content): self { + if (empty($content)) { + throw new \Exception('Content must not be empty'); + } + $this->content = $content; + return $this; + } + public function getContent(): string { + return $this->content; + } + + protected $id = ''; + 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; + } + 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}#id{$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("<div>{$this->content} </div>")) { + $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 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; + } +} +
« previous php.webmaster (#28676) next »