178 lines
6.0 KiB
PHP
178 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace app\ic;
|
|
|
|
use app\MarkupUtil;
|
|
|
|
class TZOPart
|
|
{
|
|
protected ?string $html = null;
|
|
|
|
public function __construct(
|
|
public readonly ?int $part = null,
|
|
public readonly bool $appendix = false
|
|
) {
|
|
if (!$this->appendix) {
|
|
if (!$this->part)
|
|
throw new \ValueError("must be either appendix or valid part");
|
|
if (!isset(TZO::getStrings()['part_'.$this->part]))
|
|
throw new \ValueError("invalid part");
|
|
}
|
|
}
|
|
|
|
public function getNumberWithDots(): string {
|
|
return $this->appendix ? 'null.error' : self::getPartNumberWithDots($this->part);
|
|
}
|
|
|
|
protected static function getPartNumberWithDots(int $part): string {
|
|
return implode('.', str_split((string)$part));
|
|
}
|
|
|
|
public function getLabel(): string {
|
|
return TZO::getStrings()[$this->getStringsKey()]['label'];
|
|
}
|
|
|
|
protected function getStringsKey(): string {
|
|
return self::getPartStringsKey($this->part, $this->appendix);
|
|
}
|
|
|
|
protected static function getPartStringsKey(?int $part, bool $appendix = false): string {
|
|
return !$appendix ? 'part_'.$part : 'appendix';
|
|
}
|
|
|
|
protected static function getPartLabel(int $part): string {
|
|
return TZO::getStrings()[self::getPartStringsKey($part)]['label'];
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getTableOfContents(): array {
|
|
return TZO::getStrings()[$this->getStringsKey()]['toc'];
|
|
}
|
|
|
|
public function getFullLabel(): string {
|
|
return TZO::getStrings()[$this->getStringsKey()]['full_title'] ?? (!$this->appendix ? $this->getNumberWithDots().'_' : '').$this->getLabel();
|
|
}
|
|
|
|
public function getUrl(): string {
|
|
return 'https://'.$_SERVER['HTTP_HOST'].'/tbc/'.($this->appendix ? 'appendix' : $this->part).'/';
|
|
}
|
|
|
|
public function getHtml(): string {
|
|
if ($this->html !== null)
|
|
return $this->html;
|
|
|
|
$markdown_file = APP_ROOT.'/data/tzo/'.($this->appendix ? 'appendix' : $this->part).'.md';
|
|
$md = file_get_contents($markdown_file);
|
|
|
|
// images
|
|
$md = preg_replace_callback(
|
|
'#!\[]\('
|
|
// 1 = filename
|
|
. '(/images/tzo/[\w\d\-.]+)'
|
|
// 2 = caption
|
|
. '(?:\s+"((?:\\\\.|[^"\\\\])*)")?'
|
|
. '\)#',
|
|
function($m) {
|
|
$file = $m[1];
|
|
$name = basename($file);
|
|
$caption = isset($m[2]) ? stripcslashes($m[2]) : '';
|
|
$path = APP_ROOT.'/public/ic'.$file;
|
|
list($w, $h) = getimagesize($path); // TODO cache image size
|
|
return (
|
|
'<figure>'
|
|
.'<div class="img-wrapper"><img src="'.htmlescape($file).'" width="'.$w.'" height="'.$h.'" alt="'.substr($name, 0, strrpos($name, '.')).'"></div>'
|
|
.($caption != '' ? '<figcaption>'.htmlescape($caption).'</figcaption>' : '')
|
|
.'</figure>'
|
|
);
|
|
},
|
|
$md
|
|
);
|
|
|
|
$html = MarkupUtil::markdownToHtml($md,
|
|
use_image_previews: false);
|
|
|
|
// links
|
|
$html = preg_replace_callback(
|
|
'/<center>\s*\(\[([^]]+)]\(([^)]+)\)\)\s*<\/center>/',
|
|
function ($matches) {
|
|
$linkText = $matches[1];
|
|
$url = $matches[2];
|
|
return '<p style="text-align: center">(<a href="'.htmlescape($url).'">'.htmlescape($linkText).'</a>)</p>';
|
|
},
|
|
$html
|
|
);
|
|
|
|
// chapter markers
|
|
$html = preg_replace_callback(
|
|
'/<center>(?:# )?(\(\d+\)|([\w\s?]+))<\/center>/',
|
|
function ($matches) {
|
|
$number = $matches[1];
|
|
return '<p class="tzo-chapter" id="chapter'.$number.'">'.$number.'</p>';
|
|
},
|
|
$html
|
|
);
|
|
|
|
$this->html = $html;
|
|
return $html;
|
|
}
|
|
|
|
public function getDescriptionPreview(int $len): string {
|
|
$text = trim(MarkupUtil::htmlToText($this->getHtml()));
|
|
if (mb_strlen($text) >= $len)
|
|
return mb_substr($text, 0, $len - 3).'...';
|
|
return $text;
|
|
}
|
|
|
|
public function getFirstImageUrl(): ?string {
|
|
if (!preg_match('/<img[^>]+src=[\'"]([^\'"]+)[\'"]/i', $this->getHtml(), $m))
|
|
return null;
|
|
$url = $m[1];
|
|
if (!str_starts_with($url, 'https://'))
|
|
$url = 'https://'.$_SERVER['HTTP_HOST'].$url;
|
|
return $url;
|
|
}
|
|
|
|
public function getNavigationTreeHtml(): string {
|
|
if ($this->part == 1 || $this->appendix)
|
|
return '';
|
|
|
|
$buf_last_line = [];
|
|
$buf_all_lines = [];
|
|
|
|
$part_row = intval($this->part > 10 ? floor($this->part / 10) : $this->part);
|
|
$part_column = ($this->part < 10 ? $this->part*10 : $this->part) % 10;
|
|
if ($part_column == 0)
|
|
$part_column = 1;
|
|
|
|
for ($cur_row = 1;
|
|
$cur_row <= $part_row;
|
|
$cur_row++)
|
|
{
|
|
$columns = 4 - abs($cur_row - 4);
|
|
for ($cur_column = 1; $cur_column <= $columns; $cur_column++) {
|
|
$cur_part = $cur_row;
|
|
if ($columns > 1) {
|
|
$cur_part *= 10;
|
|
$cur_part += $cur_column;
|
|
}
|
|
$label = self::getPartLabel($cur_part);
|
|
if ($cur_row == $part_row && $cur_column > $part_column) {
|
|
$label_len = strlen($label);
|
|
$label = $label[0].str_repeat('_', $label_len-1);
|
|
}
|
|
$cur_part_label = self::getPartNumberWithDots($cur_part).'_'.$label;
|
|
if ($cur_row < $part_row || $cur_column < $part_column) {
|
|
$buf_last_line[] = '<a href="/tbc/'.$cur_part.'/">'.htmlescape($cur_part_label).'</a>';
|
|
} else {
|
|
$buf_last_line[] = $cur_part_label;
|
|
}
|
|
}
|
|
$buf_all_lines[] = implode(' <span>|</span> ', $buf_last_line);
|
|
$buf_last_line = [];
|
|
}
|
|
|
|
return implode('<br>', $buf_all_lines);
|
|
}
|
|
} |