1
0
mirror of https://github.com/php/web-php.git synced 2026-03-23 23:02:13 +01:00

Refactor createNewsEntry for non-interactive use

Add bin/createReleaseEntry script for common release announcements
This commit is contained in:
Sara Golemon
2020-06-17 20:19:30 +00:00
parent 0df7236ff9
commit 7971068c37
3 changed files with 367 additions and 123 deletions

View File

@@ -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);
if ($_SERVER['argc'] > 1) {
// getopt based non-interactive mode
$entry = parseOptions();
} else {
// Classic interactive prompts
$entry = getEntry();
}
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");
$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");
// 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;
}
function createNewsEntry(string $id): void {
global $categories;
$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'])));
function getEntry(): Entry {
$entry = new Entry;
$entry->setTitle(getTitle());
$entry->setCategories(selectCategories());
if ($entry->isConference()) {
$entry->setConfTime(getConfTime());
}
foreach ($entry['categories'] as $cat) {
ce($dom, "category", null, ['term' => $cat, "label" => $categories[$cat]], $item);
}
$image = getImage();
$entry->setImage($image['path'] ?? '', $image['title'] ?? '', $image['link'] ?? '');
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");
}
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;
}
function getEntry(string $id): array {
$entry = [];
$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;
}

48
bin/createReleaseEntry Executable file
View File

@@ -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");

210
include/news_entry.inc Executable file
View File

@@ -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;
}
}