4in1_ws_web/src/lib/ic/TZOPart.php
2025-05-17 04:18:59 +03:00

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