2017-10-03

This commit is contained in:
Visman 2017-10-03 21:13:21 +07:00
parent 58b807cb87
commit 495166e21c
53 changed files with 3390 additions and 225 deletions

2
.gitignore vendored
View file

@ -2,4 +2,4 @@
/app/config/main.php
/app/cache/**/*.php
/app/cache/**/*.lock
/public/avatar/*
/public/img/avatars/*

View file

@ -81,7 +81,7 @@ class Routing
$r->add('GET', '/forum/{id:[1-9]\d*}/new/topic', 'Post:newTopic', 'NewTopic');
$r->add('GET', '/forum/{id:[1-9]\d*}[/{name}][/page/{page:[1-9]\d*}]', 'Forum:view', 'Forum');
// темы
$r->add('GET', '/topic/{id:[1-9]\d*}[/{name}][/page/{page:[1-9]\d*}]', 'Topic:view', 'Topic');
$r->add('GET', '/topic/{id:[1-9]\d*}[/{name}][/page/{page:[1-9]\d*}]', 'Topic:viewTopic', 'Topic');
$r->add('GET', '/topic/{id:[1-9]\d*}/goto/new', 'Topic:goToNew', 'TopicGoToNew');
$r->add('GET', '/topic/{id:[1-9]\d*}/goto/unread', 'Topic:goToUnread', 'TopicGoToUnread');
$r->add('GET', '/topic/{id:[1-9]\d*}/goto/last', 'Topic:goToLast', 'TopicGoToLast');

View file

@ -0,0 +1,78 @@
<?php
namespace ForkBB\Models\Pages;
trait CrumbTrait
{
/**
* Возвращает массив хлебных крошек
* @param mixed $args
* @return array
*/
protected function getCrumbs(...$args)
{
$crumbs = [];
$active = true;
foreach ($args as $arg) {
if (is_array($arg)) {
$cur = array_shift($arg);
// массив разделов
if (is_array($cur)) {
$id = $arg[0];
while (true) {
$this->titles[] = $cur[$id]['forum_name'];
$crumbs[] = [
$this->c->Router->link('Forum', ['id' => $id, 'name' => $cur[$id]['forum_name']]),
$cur[$id]['forum_name'],
$active,
];
$active = null;
if (! isset($cur[$id][0])) {
break;
}
$id = $cur[$id][0];
}
// отдельная страница
} else {
// определение названия
if (isset($arg[1])) {
$vars = $arg[0];
$name = $arg[1];
} elseif (is_string($arg[0])) {
$vars = [];
$name = $arg[0];
} elseif (isset($arg[0]['name'])) {
$vars = $arg[0];
$name = $arg[0]['name'];
} else {
continue;
}
$this->titles[] = $name;
$crumbs[] = [
$this->c->Router->link($cur, $vars),
$name,
$active,
];
}
// предположительно идет только название, без ссылки
} else {
$this->titles[] = (string) $arg;
$crumbs[] = [
null,
(string) $arg,
$active,
];
}
$active = null;
}
// главная страница
$crumbs[] = [
$this->c->Router->link('Index'),
__('Index'),
$active,
];
return array_reverse($crumbs);
}
}

View file

@ -5,6 +5,7 @@ namespace ForkBB\Models\Pages;
class Forum extends Page
{
use ForumsTrait;
use CrumbTrait;
/**
* Имя шаблона
@ -189,33 +190,10 @@ class Forum extends Page
$this->onlinePos = 'forum-' . $args['id'];
$crumbs = [];
$id = $args['id'];
$activ = true;
while (true) {
$name = $fDesc[$id]['forum_name'];
$this->titles[] = $name;
$crumbs[] = [
$this->c->Router->link('Forum', ['id' => $id, 'name' => $name]),
$name,
$activ,
];
$activ = null;
if (! isset($fDesc[$id][0])) {
break;
}
$id = $fDesc[$id][0];
}
$crumbs[] = [
$this->c->Router->link('Index'),
__('Index'),
null,
];
$this->data = [
'forums' => $this->getForumsData($args['id']),
'topics' => $topics,
'crumbs' => array_reverse($crumbs),
'crumbs' => $this->getCrumbs([$fDesc, $args['id']]),
'forumName' => $fDesc[$args['id']]['forum_name'],
'newTopic' => $newOn ? $this->c->Router->link('NewTopic', ['id' => $args['id']]) : null,
'pages' => $this->c->Func->paginate($pages, $page, 'Forum', ['id' => $args['id'], 'name' => $fDesc[$args['id']]['forum_name']]),

View file

@ -5,6 +5,7 @@ namespace ForkBB\Models\Pages;
class Index extends Page
{
use ForumsTrait;
use OnlineTrait;
/**
* Имя шаблона
@ -60,53 +61,11 @@ class Index extends Page
$stats['newest_user'] = $stats['last_user']['username'];
}
$this->data['stats'] = $stats;
// вывод информации об онлайн посетителях
if ($this->config['o_users_online'] == '1') {
$this->data['online'] = [];
$this->data['online']['max'] = $this->number($this->config['st_max_users']);
$this->data['online']['max_time'] = $this->time($this->config['st_max_users_time']);
// данные онлайн посетителей
list($users, $guests, $bots) = $this->c->Online->handle($this);
$list = [];
if ($this->c->user->gViewUsers == '1') {
foreach ($users as $id => $cur) {
$list[] = [
$this->c->Router->link('User', [
'id' => $id,
'name' => $cur['name'],
]),
$cur['name'],
];
}
} else {
foreach ($users as $cur) {
$list[] = $cur['name'];
}
}
$this->data['online']['number_of_users'] = $this->number(count($users));
$s = 0;
foreach ($bots as $name => $cur) {
$count = count($cur);
$s += $count;
if ($count > 1) {
$list[] = '[Bot] ' . $name . ' (' . $count . ')';
} else {
$list[] = '[Bot] ' . $name;
}
}
$s += count($guests);
$this->data['online']['number_of_guests'] = $this->number($s);
$this->data['online']['list'] = $list;
} else {
$this->onlineType = false;
$this->c->Online->handle($this);
$this->data['online'] = null;
}
$this->data['online'] = $this->getUsersOnlineInfo();
$this->data['forums'] = $this->getForumsData();
$this->canonical = $this->c->Router->link('Index');
return $this;
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace ForkBB\Models\Pages;
trait OnlineTrait
{
/**
* Получение информации об онлайн посетителях
* @return null|array
*/
protected function getUsersOnlineInfo()
{
if ($this->config['o_users_online'] == '1') {
$data = [
'max' => $this->number($this->config['st_max_users']),
'max_time' => $this->time($this->config['st_max_users_time']),
];
// данные онлайн посетителей
list($users, $guests, $bots) = $this->c->Online->handle($this);
$list = [];
if ($this->c->user->gViewUsers == '1') {
foreach ($users as $id => $cur) {
$list[] = [
$this->c->Router->link('User', [
'id' => $id,
'name' => $cur['name'],
]),
$cur['name'],
];
}
} else {
foreach ($users as $cur) {
$list[] = $cur['name'];
}
}
$data['number_of_users'] = $this->number(count($users));
$s = 0;
foreach ($bots as $name => $cur) {
$count = count($cur);
$s += $count;
if ($count > 1) {
$list[] = '[Bot] ' . $name . ' (' . $count . ')';
} else {
$list[] = '[Bot] ' . $name;
}
}
$s += count($guests);
$data['number_of_guests'] = $this->number($s);
$data['list'] = $list;
return $data;
} else {
$this->onlineType = false;
return null;
}
}
}

View file

@ -34,6 +34,9 @@ class Rules extends Page
'rules' => $this->config['o_rules_message'],
'formAction' => null,
];
$this->canonical = $this->c->Router->link('Rules');
return $this;
}

View file

@ -5,6 +5,8 @@ namespace ForkBB\Models\Pages;
class Topic extends Page
{
use UsersTrait;
use OnlineTrait;
use CrumbTrait;
/**
* Имя шаблона
@ -18,6 +20,22 @@ class Topic extends Page
*/
protected $onlinePos = 'topic';
/**
* Тип обработки пользователей онлайн
* Если false, то идет обновление данных
* Если true, то идет возврат данных (смотрите $onlineFilter)
* @var bool
*/
protected $onlineType = true;
/**
* Тип возврата данных при onlineType === true
* Если true, то из online должны вернутся только пользователи находящиеся на этой же странице
* Если false, то все пользователи online
* @var bool
*/
protected $onlineFilter = true;
/**
* Подготовка данных для шаблона
* @param array $args
@ -69,44 +87,37 @@ class Topic extends Page
$vars = [
':pid' => $args['id'],
];
$sql = 'SELECT topic_id FROM ::posts WHERE id=?i:pid';
$tid = $this->c->DB->query($sql, $vars)->fetchColumn();
// сообшение не найдено в базе
if (empty($tid)) {
return $this->c->Message->message('Bad request');
if ($this->c->user->isGuest) {
$sql = 'SELECT t.*, f.moderators, 0 AS is_subscribed
FROM ::topics AS t
INNER JOIN ::forums AS f ON f.id=t.forum_id
INNER JOIN ::posts AS p ON t.id=p.topic_id
WHERE p.id=?i:pid AND t.moved_to IS NULL';
} else {
$sql = 'SELECT t.*, f.moderators, s.user_id AS is_subscribed
FROM ::topics AS t
INNER JOIN ::forums AS f ON f.id=t.forum_id
INNER JOIN ::posts AS p ON t.id=p.topic_id
LEFT JOIN ::topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id=?i:uid)
WHERE p.id=?i:pid AND t.moved_to IS NULL';
}
$vars = [
':pid' => $args['id'],
':tid' => $tid,
];
$sql = 'SELECT COUNT(id) FROM ::posts WHERE topic_id=?i:tid AND id<?i:pid';
$num = 1 + $this->c->DB->query($sql, $vars)->fetchColumn();
return $this->view([
'id' => $tid,
'page' => ceil($num / $this->c->user->dispPosts),
]);
return $this->view($sql, $vars, null);
}
/**
* Подготовка данных для шаблона
* Просмотр темы по ее номеру
* @param array $args
* @return Page
*/
public function view(array $args)
public function viewTopic(array $args)
{
$this->c->Lang->load('topic');
$user = $this->c->user;
$vars = [
':tid' => $args['id'],
':uid' => $user->id,
];
if ($user->isGuest) {
if ($this->c->user->isGuest) {
$sql = 'SELECT t.*, f.moderators, 0 AS is_subscribed
FROM ::topics AS t
INNER JOIN ::forums AS f ON f.id=t.forum_id
@ -118,6 +129,25 @@ class Topic extends Page
LEFT JOIN ::topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id=?i:uid)
WHERE t.id=?i:tid AND t.moved_to IS NULL';
}
$page = isset($args['page']) ? (int) $args['page'] : 1;
return $this->view($sql, $vars, $page);
}
/**
* Подготовка данных для шаблона
* @param string $sql
* @param array $vars
* @param int|null $page
* @return Page
*/
protected function view($sql, array $vars, $page)
{
$user = $this->c->user;
$vars[':uid'] = $user->id;
$topic = $this->c->DB->query($sql, $vars)->fetch();
// тема отсутствует или недоступна
@ -132,18 +162,26 @@ class Topic extends Page
return $this->c->Message->message('Bad request');
}
$page = isset($args['page']) ? (int) $args['page'] : 1;
$pages = ceil(( $topic['num_replies'] + 1) / $user->dispPosts);
if (null === $page) {
$vars[':tid'] = $topic['id'];
$sql = 'SELECT COUNT(id) FROM ::posts WHERE topic_id=?i:tid AND id<?i:pid';
$num = 1 + $this->c->DB->query($sql, $vars)->fetchColumn();
$page = ceil($num / $user->dispPosts);
}
$this->c->Lang->load('topic');
$pages = ceil(($topic['num_replies'] + 1) / $user->dispPosts);
// попытка открыть страницу которой нет
if ($page < 1 || $page > $pages) {
return $this->c->Message->message('Bad request');
}
$offset = ($page - 1) * $user->dispPosts;
$vars = [
':tid' => $args['id'],
':tid' => $topic['id'],
':offset' => $offset,
':rows' => $user->dispPosts,
];
@ -151,29 +189,28 @@ class Topic extends Page
FROM ::posts
WHERE topic_id=?i:tid
ORDER BY id LIMIT ?i:offset, ?i:rows';
$ids = $this->c->DB->query($sql, $vars)->fetchAll(\PDO::FETCH_COLUMN);
// нарушена синхронизация количества сообщений в темах
if (empty($ids)) {
return $this->goToLast($args); //????
return $this->goToLast($topic['id']);
}
$moders = empty($topic['moderators']) ? [] : array_flip(unserialize($topic['moderators']));
$parent = isset($fDesc[$topic['forum_id']][0]) ? $fDesc[$topic['forum_id']][0] : 0;
$perm = $fTree[$parent][$topic['forum_id']];
$newOn = null;
if ($user->isAdmin) {
$newPost = $this->c->Router->link('NewPost', ['id' => $args['id']]);
$newOn = true;
} elseif ($topic['closed'] == '1') {
$newPost = false;
$newOn = false;
} elseif ($perm['post_replies'] === 1
|| (null === $perm['post_replies'] && $user->gPostReplies == '1')
|| ($user->isAdmMod && isset($moders[$user->id]))
) {
$newPost = $this->c->Router->link('NewPost', ['id' => $args['id']]);
} else {
$newPost = null;
$newOn = true;
}
// приклейка первого сообщения темы
@ -188,6 +225,7 @@ class Topic extends Page
$sql = 'SELECT id, message, poster, posted
FROM ::warnings
WHERE id IN (?ai:ids)';
$warnings = $this->c->DB->query($sql, $vars)->fetchAll(\PDO::FETCH_GROUP);
$vars = [
@ -202,11 +240,33 @@ class Topic extends Page
INNER JOIN ::users AS u ON u.id=p.poster_id
INNER JOIN ::groups AS g ON g.g_id=u.group_id
WHERE p.id IN (?ai:ids) ORDER BY p.id';
$stmt = $this->c->DB->query($sql, $vars);
// парсер и его настройка для сообщений
$bbcodes = include $this->c->DIR_CONFIG . '/defaultBBCode.php';
$smilies = $this->c->smilies;
foreach ($smilies as &$cur) {
$cur = $this->c->PUBLIC_URL . '/img/sm/' . $cur;
}
unset($cur);
$bbInfo = $this->c->BBCODE_INFO;
$bbWList = $this->config['p_message_bbcode'] == '1' ? null : [];
$bbBList = $this->config['p_message_img_tag'] == '1' ? [] : ['img'];
$parser = $this->c->Parser;
$parser->setBBCodes($bbcodes)
->setAttr('isSign', false)
->setWhiteList($bbWList)
->setBlackList($bbBList);
if ($user->showSmilies == '1') {
$parser->setSmilies($smilies)
->setSmTpl($bbInfo['smTpl'], $bbInfo['smTplTag'], $bbInfo['smTplBl']);
}
$genders = [1 => ' f-user-male', 2 => ' f-user-female'];
$postCount = 0;
$posts = [];
$signs = [];
$posters = [];
while ($cur = $stmt->fetch()) {
// данные по автору сообшения
@ -215,6 +275,7 @@ class Topic extends Page
} else {
$post = [
'poster' => $cur['username'],
'poster_id' => $cur['poster_id'],
'poster_title' => $this->censor($this->userGetTitle($cur)),
'poster_avatar' => null,
'poster_registered' => null,
@ -251,6 +312,14 @@ class Topic extends Page
$post['poster_online'] = ' f-user-online'; //????
$posters[$cur['poster_id']] = $post;
if ($this->config['o_signatures'] == '1'
&& $cur['signature'] != ''
&& $user->showSig == '1'
&& ! isset($signs[$cur['poster_id']])
) {
$signs[$cur['poster_id']] = $cur['signature'];
}
}
}
@ -260,6 +329,12 @@ class Topic extends Page
$post['posted'] = $this->time($cur['posted']);
$post['posted_utc'] = gmdate('Y-m-d\TH:i:s\Z', $cur['posted']);
$parser->parse($this->censor($cur['message']));
if ($this->config['o_smilies'] == '1' && $user->showSmilies == '1' && $cur['hide_smilies'] == '0') {
$parser->detectSmilies();
}
$post['message'] = $parser->getHtml();
// номер сообшения в теме
if ($stickFP && $offset > 0 && $cur['id'] == $topic['first_post_id']) {
$post['post_number'] = 1;
@ -274,12 +349,22 @@ class Topic extends Page
$controls['report'] = ['#', 'Report'];
}
if ($user->isAdmin
|| ($user->isAdmMod && isset($moders[$user->id]))
|| ($cur['poster_id'] == $user->id) //????
|| ($user->isAdmMod && isset($moders[$user->id]) && ! in_array($cur['poster_id'], $this->c->admins))
) {
$controls['delete'] = ['#', 'Delete'];
$controls['edit'] = ['#', 'Edit'];
} elseif ($topic['closed'] != '1'
&& $cur['poster_id'] == $user->id
&& ($user->gDeleditInterval == '0' || $cur['edit_post'] == '1' || time() - $cur['posted'] < $user->gDeleditInterval)
) {
if (($cur['id'] == $topic['first_post_id'] && $user->gDeleteTopics == '1') || ($cur['id'] != $topic['first_post_id'] && $user->gDeletePosts == '1')) {
$controls['delete'] = ['#', 'Delete'];
}
if ($user->gEditPosts == '1') {
$controls['edit'] = ['#', 'Edit'];
}
if ($newPost) {
}
if ($newOn) {
$controls['quote'] = ['#', 'Reply'];
}
@ -288,50 +373,54 @@ class Topic extends Page
$posts[] = $post;
}
if ($signs) {
// настройка парсера для подписей
$bbWList = $this->config['p_sig_bbcode'] == '1' ? $bbInfo['forSign'] : [];
$bbBList = $this->config['p_sig_img_tag'] == '1' ? [] : ['img'];
$parser->setAttr('isSign', true)
->setWhiteList($bbWList)
->setBlackList($bbBList);
foreach ($signs as &$cur) {
$parser->parse($this->censor($cur));
if ($this->config['o_smilies_sig'] == '1' && $user->showSmilies == '1') {
$parser->detectSmilies();
}
$cur = $parser->getHtml();
}
unset($cur);
}
$topic['subject'] = $this->censor($topic['subject']);
$crumbs = [];
$crumbs[] = [
$this->c->Router->link('Topic', ['id' => $args['id'], 'name' => $topic['subject']]),
$topic['subject'],
true,
];
$this->titles[] = $topic['subject'];
$id = $topic['forum_id'];
$activ = null;
while (true) {
$name = $fDesc[$id]['forum_name'];
$this->titles[] = $name;
$crumbs[] = [
$this->c->Router->link('Forum', ['id' => $id, 'name' => $name]),
$name,
$activ,
];
$activ = null;
if (! isset($fDesc[$id][0])) {
break;
}
$id = $fDesc[$id][0];
}
$crumbs[] = [
$this->c->Router->link('Index'),
__('Index'),
null,
];
$this->onlinePos = 'topic-' . $topic['id'];
$this->data = [
'topic' => $topic,
'posts' => $posts,
'signs' => $signs,
'warnings' => $warnings,
'crumbs' => array_reverse($crumbs),
'topicName' => $topic['subject'],
'newPost' => $newPost,
'crumbs' => $this->getCrumbs(
['Topic', ['id' => $topic['id'], 'name' => $topic['subject']]],
[$fDesc, $topic['forum_id']]
),
'newPost' => $newOn ? $this->c->Router->link('NewPost', ['id' => $topic['id']]) : $newOn,
'stickFP' => $stickFP,
'pages' => $this->c->Func->paginate($pages, $page, 'Topic', ['id' => $args['id'], 'name' => $topic['subject']]),
'pages' => $this->c->Func->paginate($pages, $page, 'Topic', ['id' => $topic['id'], 'name' => $topic['subject']]),
'online' => $this->getUsersOnlineInfo(),
'stats' => null,
];
$this->canonical = $this->c->Router->link('Topic', ['id' => $args['id'], 'name' => $topic['subject'], 'page' => $page]);
$this->canonical = $this->c->Router->link('Topic', ['id' => $topic['id'], 'name' => $topic['subject'], 'page' => $page]);
if ($this->config['o_topic_views'] == '1') {
$vars = [
':tid' => $topic['id'],
];
$sql = 'UPDATE ::topics SET num_views=num_views+1 WHERE id=?i:tid';
$this->c->DB->query($sql, $vars);
}
return $this;
}

View file

@ -47,10 +47,10 @@ trait UsersTrait
$filetypes = array('jpg', 'gif', 'png');
foreach ($filetypes as $type) {
$path = $this->c->DIR_PUBLIC . "/avatar/{$id}.{$type}";
$path = $this->c->DIR_PUBLIC . "/{$this->config['o_avatars_dir']}/{$id}.{$type}";
if (file_exists($path) && getimagesize($path)) {
return $this->c->PUBLIC_URL . "/avatar/{$id}.{$type}";
return $this->c->PUBLIC_URL . "/{$this->config['o_avatars_dir']}/{$id}.{$type}";
}
}

View file

@ -0,0 +1,510 @@
<?php
return [
['tag' => 'ROOT',
'type' => 'block',
'handler' => function($body) {
$body = '<p>' . $body . '</p>';
// Replace any breaks next to paragraphs so our replace below catches them
$body = preg_replace('%(</?p>)(?:\s*?<br>){1,2}%', '$1', $body);
$body = preg_replace('%(?:<br>\s*?){1,2}(</?p>)%', '$1', $body);
// Remove any empty paragraph tags (inserted via quotes/lists/code/etc) which should be stripped
$body = str_replace('<p></p>', '', $body);
$body = preg_replace('%<br>\s*?<br>%', '</p><p>', $body);
$body = str_replace('<p><br>', '<br><p>', $body);
$body = str_replace('<br></p>', '</p><br>', $body);
$body = str_replace('<p></p>', '<br><br>', $body);
return $body;
},
],
['tag' => 'code',
'type' => 'block',
'recursive' => true,
'text only' => true,
'pre' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs) {
$body = trim($body, "\n\r");
$class = substr_count($body, "\n") > 28 ? ' class="vscroll"' : '';
return '</p><div class="codebox"><pre' . $class . '><code>' . $body . '</code></pre></div><p>';
},
],
['tag' => 'b',
'handler' => function($body) {
return '<strong>' . $body . '</strong>';
},
],
['tag' => 'i',
'handler' => function($body) {
return '<em>' . $body . '</em>';
},
],
['tag' => 'em',
'handler' => function($body) {
return '<em>' . $body . '</em>';
},
],
['tag' => 'u',
'handler' => function($body) {
return '<span class="bbu">' . $body . '</span>';
},
],
['tag' => 's',
'handler' => function($body) {
return '<span class="bbs">' . $body . '</span>';
},
],
['tag' => 'del',
'handler' => function($body) {
return '<del>' . $body . '</del>';
},
],
['tag' => 'ins',
'handler' => function($body) {
return '<ins>' . $body . '</ins>';
},
],
['tag' => 'h',
'type' => 'h',
'handler' => function($body) {
return '</p><h5>' . $body . '</h5><p>';
},
],
['tag' => 'hr',
'type' => 'block',
'single' => true,
'handler' => function() {
return '</p><hr><p>';
},
],
['tag' => 'color',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^(?:\#(?:[\dA-Fa-f]{3}){1,2}|(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|yellow|white))$%',
],
],
'handler' => function($body, $attrs) {
return '<span style="color:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'colour',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^(?:\#(?:[\dA-Fa-f]{3}){1,2}|(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|yellow|white))$%',
],
],
'handler' => function($body, $attrs) {
return '<span style="color:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'background',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^(?:\#(?:[\dA-Fa-f]{3}){1,2}|(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|yellow|white))$%',
],
],
'handler' => function($body, $attrs) {
return '<span style="background-color:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'size',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^[1-9]\d*(?:em|ex|pt|px|\%)?$%',
],
],
'handler' => function($body, $attrs) {
if (is_numeric($attrs['Def'])) {
$attrs['Def'] .= 'px';
}
return '<span style="font-size:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'right',
'type' => 'block',
'handler' => function($body) {
return '</p><p style="text-align: right;">' . $body . '</p><p>';
},
],
['tag' => 'center',
'type' => 'block',
'handler' => function($body) {
return '</p><p style="text-align: center;">' . $body . '</p><p>';
},
],
['tag' => 'justify',
'type' => 'block',
'handler' => function($body) {
return '</p><p style="text-align: justify;">' . $body . '</p><p>';
},
],
['tag' => 'mono',
'handler' => function($body) {
return '<code>' . $body . '</code>';
},
],
['tag' => 'font',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^[a-z\d, -]+$%i',
],
],
'handler' => function($body, $attrs) {
return '<span style="font-family:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'email',
'type' => 'email',
'attrs' => [
'Def' => [
'format' => '%^[^\x00-\x1f\s]+?@[^\x00-\x1f\s]+$%',
],
'no attr' => [
'body format' => '%^[^\x00-\x1f\s]+?@[^\x00-\x1f\s]+$%D',
'text only' => true,
],
],
'handler' => function($body, $attrs) {
if (empty($attrs['Def'])) {
return '<a href="mailto:' . $body . '">' . $body . '</a>';
} else {
return '<a href="mailto:' . $attrs['Def'] . '">' . $body . '</a>';
}
},
],
['tag' => '*',
'type' => 'block',
'self nesting' => true,
'parents' => ['list'],
'auto' => true,
'handler' => function($body) {
return '<li><p>' . $body . '</p></li>';
},
],
['tag' => 'list',
'type' => 'list',
'self nesting' => true,
'tags only' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs) {
if (!isset($attrs['Def'])) {
$attrs['Def'] = '*';
}
switch ($attrs['Def'][0]) {
case 'a':
return '</p><ol class="alpha">' . $body . '</ol><p>';
case '1':
return '</p><ol class="decimal">' . $body . '</ol><p>';
default:
return '</p><ul>' . $body . '</ul><p>';
}
},
],
['tag' => 'after',
'type' => 'block',
'single' => true,
'attrs' => [
'Def' => [
'format' => '%^\d+$%',
],
],
'handler' => function($body, $attrs) {
$arr = array();
$sec = $attrs['Def'] % 60;
$min = ($attrs['Def'] / 60) % 60;
$hours = ($attrs['Def'] / 3600) % 24;
$days = (int) ($attrs['Def'] / 86400);
if ($days > 0) {
$arr[] = $days . __('After time d');
}
if ($hours > 0) {
$arr[] = $hours . __('After time H');
}
if ($min > 0) {
$arr[] = (($min < 10) ? '0' . $min : $min) . __('After time i');
}
if ($sec > 0) {
$arr[] = (($sec < 10) ? '0' . $sec : $sec) . __('After time s');
}
$attr = __('After time') . ' ' . implode(' ', $arr);
return '<span style="color: #808080"><em>' . $attr . ':</em></span><br />';
},
],
['tag' => 'quote',
'type' => 'block',
'self nesting' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs) {
if (isset($attrs['Def'])) {
$st = '</p><div class="quotebox"><cite>' . $attrs['Def'] . ' ' . __('wrote') . '</cite><blockquote><div><p>';
} else {
$st = '</p><div class="quotebox"><blockquote><div><p>';
}
return $st . $body . '</p></div></blockquote></div><p>';
},
],
['tag' => 'spoiler',
'type' => 'block',
'self nesting' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs) {
if (isset($attrs['Def'])) {
$st = '</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName(\'div\')[1],b=this.getElementsByTagName(\'span\')[0];if(a.style.display!=\'\'){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)[\'backgroundColor\'];if(d==\'transparent\'||d==\'rgba(0, 0, 0, 0)\')d=e;c=c.parentNode;}a.style.display=\'\';a.style.backgroundColor=d;b.innerHTML=\'&#9650;\';}else{a.style.display=\'none\';b.innerHTML=\'&#9660;\';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>' . $attrs['Def'] . '</div><div style="padding: 6px; margin: 0; display: none;"><p>';
} else {
$st = '</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName(\'div\')[1],b=this.getElementsByTagName(\'span\')[0];if(a.style.display!=\'\'){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)[\'backgroundColor\'];if(d==\'transparent\'||d==\'rgba(0, 0, 0, 0)\')d=e;c=c.parentNode;}a.style.display=\'\';a.style.backgroundColor=d;b.innerHTML=\'&#9650;\';}else{a.style.display=\'none\';b.innerHTML=\'&#9660;\';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>' . __('Hidden text') . '</div><div style="padding: 6px; margin: 0; display: none;"><p>';
}
return $st . $body . '</p></div></div><p>';
},
],
['tag' => 'img',
'type' => 'img',
'parents' => ['inline', 'block', 'url'],
'text only' => true,
'attrs' => [
'Def' => [
'body format' => '%^(?:(?:ht|f)tps?://[^\x00-\x1f\s<"]+|data:image/[a-z]+;base64,(?:[a-zA-Z\d/\+\=]+))$%D'
],
'no attr' => [
'body format' => '%^(?:(?:ht|f)tps?://[^\x00-\x1f\s<"]+|data:image/[a-z]+;base64,(?:[a-zA-Z\d/\+\=]+))$%D'
],
],
'handler' => function($body, $attrs, $parser) {
if (! isset($attrs['Def'])) {
$attrs['Def'] = (substr($body, 0, 11) === 'data:image/') ? 'base64' : basename($body);
}
// тег в подписи
if ($parser->attr('isSign')) {
if ($parser->attr('showImgSign')) {
return '<img src="' . $body . '" alt="' . $attrs['Def'] . '" class="sigimage" />';
}
} else {
// тег в теле сообщения
if ($parser->attr('showImg')) {
return '<span class="postimg"><img src="' . $body . '" alt="' . $attrs['Def'] . '" /></span>';
}
}
return '<a href="' . $body . '" rel="nofollow">&lt;' . __('Image link') . ' - ' . $attrs['Def'] . '&gt;</a>';
},
],
['tag' => 'url',
'type' => 'url',
'parents' => ['inline', 'block'],
'attrs' => [
'Def' => [
'format' => '%^[^\x00-\x1f]+$%',
],
'no attr' => [
'body format' => '%^[^\x00-\x1f]+$%D',
],
],
'handler' => function($body, $attrs, $parser) {
if (isset($attrs['Def'])) {
$url = $attrs['Def'];
} else {
$url = $body;
// возможно внутри была картинка, которая отображается как ссылка
if (preg_match('%^<a href=".++(?<=</a>)$%D', $url)) {
return $url;
}
// возможно внутри картинка
if (preg_match('%<img src="([^"]+)"%', $url, $match)) {
$url = $match[1];
}
}
$fUrl = str_replace(array(' ', '\'', '`', '"'), array('%20', '', '', ''), $url);
if (strpos($url, 'www.') === 0) {
$fUrl = 'http://'.$fUrl;
} else if (strpos($url, 'ftp.') === 0) {
$fUrl = 'ftp://'.$fUrl;
} else if (strpos($url, '/') === 0) {
$fUrl = $parser->attr('baseUrl') . $fUrl;
} else if (!preg_match('%^([a-z0-9]{3,6})://%', $url)) {
$fUrl = 'http://'.$fUrl;
}
if ($url === $body) {
$url = htmlspecialchars_decode($url, ENT_QUOTES);
$url = mb_strlen($url, 'UTF-8') > 55 ? mb_substr($url, 0, 39, 'UTF-8') . ' … ' . mb_substr($url, -10, null, 'UTF-8') : $url;
$body = $parser->e($url);
}
return '<a href="' . $fUrl . '" rel="nofollow">' . $body . '</a>';
},
],
['tag' => 'table',
'type' => 'table',
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'align' => true,
'background' => true,
'bgcolor' => true,
'border' => true,
'bordercolor' => true,
'cellpadding' => true,
'cellspacing' => true,
'frame' => true,
'rules' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '</p><table' . $attr . '>' . $body . '</table><p>';
},
],
['tag' => 'caption',
'type' => 'block',
'parents' => ['table'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<caption' . $attr . '><p>' . $body . '</p></caption>';
},
],
['tag' => 'thead',
'type' => 't',
'parents' => ['table'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<thead' . $attr . '>' . $body . '</thead>';
},
],
['tag' => 'tbody',
'type' => 't',
'parents' => ['table'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<tbody' . $attr . '>' . $body . '</tbody>';
},
],
['tag' => 'tfoot',
'type' => 't',
'parents' => ['table'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<tfoot' . $attr . '>' . $body . '</tfoot>';
},
],
['tag' => 'tr',
'type' => 'tr',
'parents' => ['table', 't'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<tr' . $attr . '>' . $body . '</tr>';
},
],
['tag' => 'th',
'type' => 'block',
'parents' => ['tr'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'colspan' => true,
'rowspan' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<th' . $attr . '><p>' . $body . '</p></th>';
},
],
['tag' => 'td',
'type' => 'block',
'parents' => ['tr'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'colspan' => true,
'rowspan' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<td' . $attr . '><p>' . $body . '</p></td>';
},
],
];

View file

@ -24,12 +24,6 @@ msgstr "Board is empty."
msgid "Newest user"
msgstr "Newest registered user:"
msgid "Users online"
msgstr "Registered users online:"
msgid "Guests online"
msgstr "guests:"
msgid "No of users"
msgstr "Total number of registered users:"
@ -39,10 +33,10 @@ msgstr "Total number of topics:"
msgid "No of posts"
msgstr "Total number of posts:"
msgid "Online"
msgid "Online users"
msgstr "Online:"
msgid "Board info"
msgid "Stats info"
msgstr "Board information"
msgid "Board stats"
@ -53,3 +47,6 @@ msgstr "Maximum online users (%1$s) was be there %2$s"
msgid "User info"
msgstr "User information"
msgid "Visitors online"
msgstr "Registered users online: <strong>%1$s</strong>, guests: <strong>%2$s</strong>."

View file

@ -95,8 +95,14 @@ msgstr "Warnings:"
msgid "Reply"
msgstr "Reply"
msgid "Users online"
msgstr "Registered users online in this topic: %s"
msgid "User info"
msgstr "User information"
msgid "Guests online"
msgstr "guests: %s"
msgid "Visitors online"
msgstr "Registered users online in this topic: <strong>%1$s</strong>, guests: <strong>%2$s</strong>."
msgid "Online users"
msgstr "Online:"
msgid "Stats info"
msgstr "Topic information"

View file

@ -24,12 +24,6 @@ msgstr "Форум пуст."
msgid "Newest user"
msgstr "Новичок:"
msgid "Users online"
msgstr "Сейчас пользователей:"
msgid "Guests online"
msgstr "гостей:"
msgid "No of users"
msgstr "Всего пользователей:"
@ -39,10 +33,10 @@ msgstr "Всего тем:"
msgid "No of posts"
msgstr "Всего сообщений:"
msgid "Online"
msgid "Online users"
msgstr "Активны:"
msgid "Board info"
msgid "Stats info"
msgstr "Информация о форуме"
msgid "Board stats"
@ -53,3 +47,6 @@ msgstr "Больше всего онлайн посетителей (%1$s) зд
msgid "User info"
msgstr "Информация о пользователях"
msgid "Visitors online"
msgstr "Сейчас пользователей: <strong>%1$s</strong>, гостей: <strong>%2$s</strong>."

View file

@ -96,8 +96,14 @@ msgstr "Предупреждений:"
msgid "Reply"
msgstr "Ответить"
msgid "Users online"
msgstr "Сейчас в этой теме пользователей: %s"
msgid "User info"
msgstr "Информация о пользователях"
msgid "Guests online"
msgstr "гостей: %s"
msgid "Visitors online"
msgstr "Сейчас в этой теме пользователей: <strong>%1$s</strong>, гостей: <strong>%2$s</strong>."
msgid "Online users"
msgstr "Активны:"
msgid "Stats info"
msgstr "Информация о теме"

View file

@ -22,38 +22,4 @@
<h2>{!! __('Empty board') !!}</h2>
</section>
@endif
<section class="f-stats">
<h2>{!! __('Board info') !!}</h2>
<div class="clearfix">
<dl class="right">
<dt>{!! __('Board stats') !!}</dt>
<dd>{!! __('No of users') !!} <strong>{!! $stats['total_users'] !!}</strong></dd>
<dd>{!! __('No of topics') !!} <strong>{!! $stats['total_topics'] !!}</strong></dd>
<dd>{!! __('No of posts') !!} <strong>{!! $stats['total_posts'] !!}</strong></dd>
</dl>
<dl class="left">
<dt>{!! __('User info') !!}</dt>
@if(is_string($stats['newest_user']))
<dd>{!! __('Newest user') !!} {{ $stats['newest_user'] }}</dd>
@else
<dd>{!! __('Newest user') !!} <a href="{!! $stats['newest_user'][0] !!}">{{ $stats['newest_user'][1] }}</a></dd>
@endif
@if($online)
<dd>{!! __('Users online') !!} <strong>{!! $online['number_of_users'] !!}</strong>, {!! __('Guests online') !!} <strong>{!! $online['number_of_guests'] !!}</strong>.</dd>
<dd>{!! __('Most online', $online['max'], $online['max_time']) !!}</dd>
@endif
</dl>
@if($online && $online['list'])
<dl class="f-inline f-onlinelist"><!-- inline -->
<dt>{!! __('Online') !!}</dt>
@foreach($online['list'] as $cur)
@if(is_string($cur))
<dd>{{ $cur }}</dd>
@else
<dd><a href="{!! $cur[0] !!}">{{ $cur[1] }}</a></dd>
@endif
@endforeach
</dl><!-- endinline -->
@endif
</div>
</section>
@include('layouts/stats')

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{!! __('lang_identifier') !!} dir="{!! __('lang_direction') !!}">
<html lang="{!! __('lang_identifier') !!}" dir="{!! __('lang_direction') !!}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -0,0 +1,39 @@
<section class="f-stats">
<h2>{!! __('Stats info') !!}</h2>
<div class="clearfix">
@if($stats)
<dl class="right">
<dt>{!! __('Board stats') !!}</dt>
<dd>{!! __('No of users') !!} <strong>{!! $stats['total_users'] !!}</strong></dd>
<dd>{!! __('No of topics') !!} <strong>{!! $stats['total_topics'] !!}</strong></dd>
<dd>{!! __('No of posts') !!} <strong>{!! $stats['total_posts'] !!}</strong></dd>
</dl>
@endif
<dl class="left">
<dt>{!! __('User info') !!}</dt>
@if($stats && is_string($stats['newest_user']))
<dd>{!! __('Newest user') !!} {{ $stats['newest_user'] }}</dd>
@elseif($stats)
<dd>{!! __('Newest user') !!} <a href="{!! $stats['newest_user'][0] !!}">{{ $stats['newest_user'][1] }}</a></dd>
@endif
@if($online)
<dd>{!! __('Visitors online', $online['number_of_users'], $online['number_of_guests']) !!}</dd>
@endif
@if($stats)
<dd>{!! __('Most online', $online['max'], $online['max_time']) !!}</dd>
@endif
</dl>
@if($online && $online['list'])
<dl class="f-inline f-onlinelist"><!-- inline -->
<dt>{!! __('Online users') !!}</dt>
@foreach($online['list'] as $cur)
@if(is_string($cur))
<dd>{{ $cur }}</dd>
@else
<dd><a href="{!! $cur[0] !!}">{{ $cur[1] }}</a></dd>
@endif
@endforeach
</dl><!-- endinline -->
@endif
</div>
</section>

View file

@ -48,14 +48,14 @@
@endif
</div>
<section class="f-main f-topic">
<h2>{{ $topicName }}</h2>
<h2>{{ $topic['subject'] }}</h2>
@foreach($posts as $post)
<article id="p{!! $post['id'] !!}" class="f-post{!! $post['poster_gender'].$post['poster_online'] !!} clearfix">
<div class="f-post-header clearfix">
<h3>{{ $topicName }} - #{!! $post['post_number'] !!}</h3>
<header class="f-post-header clearfix">
<h3>{{ $topic['subject'] }} - #{!! $post['post_number'] !!}</h3>
<span class="left"><time datetime="{{ $post['posted_utc'] }}">{{ $post['posted'] }}</time></span>
<span class="right"><a href="{!! $post['link'] !!}" rel="bookmark">#{!! $post['post_number'] !!}</a></span>
</div>
</header>
<div class="f-post-body clearfix">
<address class="f-post-left clearfix">
<ul class="f-user-info">
@ -84,9 +84,17 @@
</ul>
@endif
</address>
<div class="f-post-right f-post-main">
{!! $post['message'] !!}
</div>
<div class="f-post-footer clearfix">
@if(isset($signs[$post['poster_id']]))
<div class="f-post-right f-post-signature">
<hr>
{!! $signs[$post['poster_id']] !!}
</div>
@endif
</div>
<footer class="f-post-footer clearfix">
<div class="f-post-left">
<span></span>
</div>
@ -99,7 +107,7 @@
</ul>
</div>
@endif
</div>
</footer>
</article>
@endforeach
</section>
@ -112,3 +120,6 @@
@endif
@yield('crumbs')
</div>
@if($online)
@include('layouts/stats')
@endif

View file

@ -18,6 +18,7 @@
},
"require": {
"php": ">=5.6.0",
"artoodetoo/dirk": "dev-master"
"artoodetoo/dirk": "dev-master",
"MioVisman/Parserus": "^0.9.1"
}
}

46
composer.lock generated
View file

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "ad22a23d7b225fa300553d8d0dcdaeb9",
"content-hash": "2eea8744cdbc34c8408e6d137176a8df",
"hash": "f222f403c8d48f00cb5e3e37f680ebcb",
"content-hash": "b8c1ffae094a89502ad8a3c60385d4b8",
"packages": [
{
"name": "artoodetoo/dirk",
@ -52,6 +52,48 @@
"views"
],
"time": "2017-01-10 21:38:22"
},
{
"name": "miovisman/parserus",
"version": "0.9.1",
"source": {
"type": "git",
"url": "https://github.com/MioVisman/Parserus.git",
"reference": "d039178d46c989ebb8eb89970b52eb6ce927c817"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MioVisman/Parserus/zipball/d039178d46c989ebb8eb89970b52eb6ce927c817",
"reference": "d039178d46c989ebb8eb89970b52eb6ce927c817",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Parserus": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Visman",
"email": "mio.visman@yandex.ru",
"homepage": "https://github.com/MioVisman"
}
],
"description": "BBCode parser.",
"homepage": "https://github.com/MioVisman/Parserus",
"keywords": [
"bbcode",
"parser"
],
"time": "2016-11-26 06:56:51"
}
],
"packages-dev": [],

View file

@ -4,7 +4,7 @@ AddDefaultCharset UTF-8
RewriteEngine On
#RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
#RewriteCond %{REQUEST_FILENAME} !-d
#RewriteCond %{REQUEST_URI} !/public/.*\.\w+$
RewriteCond %{REQUEST_URI} !favicon\.ico
RewriteRule . index.php [L]

BIN
public/img/sm/big_smile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

BIN
public/img/sm/cool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

BIN
public/img/sm/hmm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

BIN
public/img/sm/lol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

BIN
public/img/sm/mad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

BIN
public/img/sm/neutral.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

BIN
public/img/sm/roll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

BIN
public/img/sm/sad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

BIN
public/img/sm/smile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

BIN
public/img/sm/tongue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

BIN
public/img/sm/wink.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

BIN
public/img/sm/yikes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View file

@ -1215,6 +1215,11 @@ li + li .f-btn {
clear: left;
}
.f-post-signature {
font-size: 0.875rem;
opacity: 0.5;
}
@media screen and (min-width: 50rem) {
.f-post {
background-color: #F8F4E3;

View file

@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Parserus' => array($vendorDir . '/miovisman/parserus'),
);

View file

@ -28,11 +28,22 @@ class ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7
),
);
public static $prefixesPsr0 = array (
'P' =>
array (
'Parserus' =>
array (
0 => __DIR__ . '/..' . '/miovisman/parserus',
),
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7::$prefixesPsr0;
}, null, ClassLoader::class);
}

View file

@ -46,5 +46,49 @@
"templating",
"views"
]
},
{
"name": "miovisman/parserus",
"version": "0.9.1",
"version_normalized": "0.9.1.0",
"source": {
"type": "git",
"url": "https://github.com/MioVisman/Parserus.git",
"reference": "d039178d46c989ebb8eb89970b52eb6ce927c817"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MioVisman/Parserus/zipball/d039178d46c989ebb8eb89970b52eb6ce927c817",
"reference": "d039178d46c989ebb8eb89970b52eb6ce927c817",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"time": "2016-11-26 06:56:51",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Parserus": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Visman",
"email": "mio.visman@yandex.ru",
"homepage": "https://github.com/MioVisman"
}
],
"description": "BBCode parser.",
"homepage": "https://github.com/MioVisman/Parserus",
"keywords": [
"bbcode",
"parser"
]
}
]

21
vendor/miovisman/parserus/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Visman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1222
vendor/miovisman/parserus/Parserus.php vendored Normal file

File diff suppressed because it is too large Load diff

40
vendor/miovisman/parserus/README.md vendored Normal file
View file

@ -0,0 +1,40 @@
# Parserus
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
BBCode parser.
## Requirements
* PHP 5.4.0
## Installation
Include `Parserus.php` or install [the composer package](https://packagist.org/packages/MioVisman/Parserus).
## Example
``` php
$parser = new Parserus();
echo $parser->addBBCode([
'tag' => 'b',
'handler' => function($body) {
return '<b>' . $body . '</b>';
}
])->addBBcode([
'tag' => 'i',
'handler' => function($body) {
return '<i>' . $body . '</i>';
},
])->parse("[i]Hello\n[b]World[/b]![/i]")
->getHTML();
#output: <i>Hello<br><b>World</b>!</i>
```
More examples in [the wiki](https://github.com/MioVisman/Parserus/wiki).
## License
This project is under MIT license. Please see the [license file](LICENSE) for details.

21
vendor/miovisman/parserus/composer.json vendored Normal file
View file

@ -0,0 +1,21 @@
{
"name": "miovisman/parserus",
"description": "BBCode parser.",
"keywords": ["bbcode", "parser"],
"homepage": "https://github.com/MioVisman/Parserus",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Visman",
"email": "mio.visman@yandex.ru",
"homepage": "https://github.com/MioVisman"
}
],
"require": {
"php": ">=5.4.0"
},
"autoload": {
"psr-0": {"Parserus": ""}
}
}

View file

@ -0,0 +1,20 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->addBBCode([
'tag' => 'b',
'handler' => function($body) {
return '<b>' . $body . '</b>';
}
])->addBBcode([
'tag' => 'i',
'handler' => function($body) {
return '<i>' . $body . '</i>';
},
])->parse("[i]Hello\n[b]World[/b]![/i]")
->getHTML();
#output: <i>Hello<br><b>World</b>!</i>

View file

@ -0,0 +1,20 @@
<?php
include '../Parserus.php';
$parser = new Parserus(ENT_XHTML);
echo $parser->addBBCode([
'tag' => 'b',
'handler' => function($body) {
return '<b>' . $body . '</b>';
}
])->addBBcode([
'tag' => 'i',
'handler' => function($body) {
return '<i>' . $body . '</i>';
},
])->parse("[i]Hello\n[b]World[/b]![/i]")
->getHTML();
#output: <i>Hello<br /><b>World</b>!</i>

View file

@ -0,0 +1,20 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->addBBCode([
'tag' => 'b',
'handler' => function($body) {
return '<b>' . $body . '</b>';
}
])->addBBcode([
'tag' => 'i',
'handler' => function($body) {
return '<i>' . $body . '</i>';
},
])->parse("[i]\nHello\n[b]\nWorld!")
->getHTML();
#output: <i>Hello<br><b>World!</b></i>

View file

@ -0,0 +1,50 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->addBBCode([
'tag' => 'after',
'type' => 'block',
'single' => true,
'attrs' => [
'Def' => [
'format' => '%^\d+$%',
],
],
'handler' => function($body, $attrs, $parser) {
$lang = $parser->attr('lang');
$arr = array();
$sec = $attrs['Def'] % 60;
$min = ($attrs['Def'] / 60) % 60;
$hours = ($attrs['Def'] / 3600) % 24;
$days = (int) ($attrs['Def'] / 86400);
if ($days > 0) {
$arr[] = $days . $lang['After time d'];
}
if ($hours > 0) {
$arr[] = $hours . $lang['After time H'];
}
if ($min > 0) {
$arr[] = (($min < 10) ? '0' . $min : $min) . $lang['After time i'];
}
if ($sec > 0) {
$arr[] = (($sec < 10) ? '0' . $sec : $sec) . $lang['After time s'];
}
$attr = $lang['After time'] . ' ' . implode(' ', $arr);
return '<span style="color: #808080"><em>' . $attr . ':</em></span><br>';
},
])->setAttr('lang', [
'After time' => 'Added later',
'After time s' => ' s',
'After time i' => ' min',
'After time H' => ' h',
'After time d' => ' d',
])->parse('[after=10123]')
->getHTML();
#output: <span style="color: #808080"><em>Added later 2 h 48 min 43 s:</em></span><br>

View file

@ -0,0 +1,588 @@
<?php
include '../Parserus.php';
# example on the basis of (partially) bb-codes from FluxBB/PunBB parsers
# пример на основании (частично) bb-кодов из парсеров FluxBB/PunBB
$bbcodes = [
['tag' => 'ROOT',
'type' => 'block',
'handler' => function($body) {
$body = '<p>' . $body . '</p>';
// Replace any breaks next to paragraphs so our replace below catches them
$body = preg_replace('%(</?p>)(?:\s*?<br />){1,2}%', '$1', $body);
$body = preg_replace('%(?:<br />\s*?){1,2}(</?p>)%', '$1', $body);
// Remove any empty paragraph tags (inserted via quotes/lists/code/etc) which should be stripped
$body = str_replace('<p></p>', '', $body);
$body = preg_replace('%<br />\s*?<br />%', '</p><p>', $body);
$body = str_replace('<p><br />', '<br /><p>', $body);
$body = str_replace('<br /></p>', '</p><br />', $body);
$body = str_replace('<p></p>', '<br /><br />', $body);
return $body;
},
],
['tag' => 'code',
'type' => 'block',
'recursive' => true,
'text only' => true,
'pre' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs) {
$body = trim($body, "\n\r");
$class = substr_count($body, "\n") > 28 ? ' class="vscroll"' : '';
return '</p><div class="codebox"><pre' . $class . '><code>' . $body . '</code></pre></div><p>';
},
],
['tag' => 'b',
'handler' => function($body) {
return '<strong>' . $body . '</strong>';
},
],
['tag' => 'i',
'handler' => function($body) {
return '<em>' . $body . '</em>';
},
],
['tag' => 'em',
'handler' => function($body) {
return '<em>' . $body . '</em>';
},
],
['tag' => 'u',
'handler' => function($body) {
return '<span class="bbu">' . $body . '</span>';
},
],
['tag' => 's',
'handler' => function($body) {
return '<span class="bbs">' . $body . '</span>';
},
],
['tag' => 'del',
'handler' => function($body) {
return '<del>' . $body . '</del>';
},
],
['tag' => 'ins',
'handler' => function($body) {
return '<ins>' . $body . '</ins>';
},
],
['tag' => 'h',
'type' => 'h',
'handler' => function($body) {
return '</p><h5>' . $body . '</h5><p>';
},
],
['tag' => 'hr',
'type' => 'block',
'single' => true,
'handler' => function() {
return '</p><hr /><p>';
},
],
['tag' => 'color',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^(?:\#(?:[\dA-Fa-f]{3}){1,2}|(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|yellow|white))$%',
],
],
'handler' => function($body, $attrs) {
return '<span style="color:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'colour',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^(?:\#(?:[\dA-Fa-f]{3}){1,2}|(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|yellow|white))$%',
],
],
'handler' => function($body, $attrs) {
return '<span style="color:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'background',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^(?:\#(?:[\dA-Fa-f]{3}){1,2}|(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|yellow|white))$%',
],
],
'handler' => function($body, $attrs) {
return '<span style="background-color:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'size',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^[1-9]\d*(?:em|ex|pt|px|\%)?$%',
],
],
'handler' => function($body, $attrs) {
if (is_numeric($attrs['Def'])) {
$attrs['Def'] .= 'px';
}
return '<span style="font-size:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'right',
'type' => 'block',
'handler' => function($body) {
return '</p><p style="text-align: right;">' . $body . '</p><p>';
},
],
['tag' => 'center',
'type' => 'block',
'handler' => function($body) {
return '</p><p style="text-align: center;">' . $body . '</p><p>';
},
],
['tag' => 'justify',
'type' => 'block',
'handler' => function($body) {
return '</p><p style="text-align: justify;">' . $body . '</p><p>';
},
],
['tag' => 'mono',
'handler' => function($body) {
return '<code>' . $body . '</code>';
},
],
['tag' => 'font',
'self nesting' => true,
'attrs' => [
'Def' => [
'format' => '%^[a-z\d, -]+$%i',
],
],
'handler' => function($body, $attrs) {
return '<span style="font-family:' . $attrs['Def'] . ';">' . $body . '</span>';
},
],
['tag' => 'email',
'type' => 'email',
'attrs' => [
'Def' => [
'format' => '%^[^\x00-\x1f\s]+?@[^\x00-\x1f\s]+$%',
],
'no attr' => [
'body format' => '%^[^\x00-\x1f\s]+?@[^\x00-\x1f\s]+$%D',
'text only' => true,
],
],
'handler' => function($body, $attrs) {
if (empty($attrs['Def'])) {
return '<a href="mailto:' . $body . '">' . $body . '</a>';
} else {
return '<a href="mailto:' . $attrs['Def'] . '">' . $body . '</a>';
}
},
],
['tag' => '*',
'type' => 'block',
'self nesting' => true,
'parents' => ['list'],
'auto' => true,
'handler' => function($body) {
return '<li><p>' . $body . '</p></li>';
},
],
['tag' => 'list',
'type' => 'list',
'self nesting' => true,
'tags only' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs) {
if (!isset($attrs['Def'])) {
$attrs['Def'] = '*';
}
switch ($attrs['Def'][0]) {
case 'a':
return '</p><ol class="alpha">' . $body . '</ol><p>';
case '1':
return '</p><ol class="decimal">' . $body . '</ol><p>';
default:
return '</p><ul>' . $body . '</ul><p>';
}
},
],
['tag' => 'after',
'type' => 'block',
'single' => true,
'attrs' => [
'Def' => [
'format' => '%^\d+$%',
],
],
'handler' => function($body, $attrs, $parser) {
$lang = $parser->attr('lang');
$arr = array();
$sec = $attrs['Def'] % 60;
$min = ($attrs['Def'] / 60) % 60;
$hours = ($attrs['Def'] / 3600) % 24;
$days = (int) ($attrs['Def'] / 86400);
if ($days > 0) {
$arr[] = $days . $lang['After time d'];
}
if ($hours > 0) {
$arr[] = $hours . $lang['After time H'];
}
if ($min > 0) {
$arr[] = (($min < 10) ? '0' . $min : $min) . $lang['After time i'];
}
if ($sec > 0) {
$arr[] = (($sec < 10) ? '0' . $sec : $sec) . $lang['After time s'];
}
$attr = $lang['After time'] . ' ' . implode(' ', $arr);
return '<span style="color: #808080"><em>' . $attr . ':</em></span><br />';
},
],
['tag' => 'quote',
'type' => 'block',
'self nesting' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs, $parser) {
if (isset($attrs['Def'])) {
$lang = $parser->attr('lang');
$st = '</p><div class="quotebox"><cite>' . $attrs['Def'] . ' ' . $lang['wrote'] . '</cite><blockquote><div><p>';
} else {
$st = '</p><div class="quotebox"><blockquote><div><p>';
}
return $st . $body . '</p></div></blockquote></div><p>';
},
],
['tag' => 'spoiler',
'type' => 'block',
'self nesting' => true,
'attrs' => [
'Def' => true,
'no attr' => true,
],
'handler' => function($body, $attrs, $parser) {
if (isset($attrs['Def'])) {
$st = '</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName(\'div\')[1],b=this.getElementsByTagName(\'span\')[0];if(a.style.display!=\'\'){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)[\'backgroundColor\'];if(d==\'transparent\'||d==\'rgba(0, 0, 0, 0)\')d=e;c=c.parentNode;}a.style.display=\'\';a.style.backgroundColor=d;b.innerHTML=\'&#9650;\';}else{a.style.display=\'none\';b.innerHTML=\'&#9660;\';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>' . $attrs['Def'] . '</div><div style="padding: 6px; margin: 0; display: none;"><p>';
} else {
$lang = $parser->attr('lang');
$st = '</p><div class="quotebox" style="padding: 0px;"><div onclick="var e,d,c=this.parentNode,a=c.getElementsByTagName(\'div\')[1],b=this.getElementsByTagName(\'span\')[0];if(a.style.display!=\'\'){while(c.parentNode&&(!d||!e||d==e)){e=d;d=(window.getComputedStyle?getComputedStyle(c, null):c.currentStyle)[\'backgroundColor\'];if(d==\'transparent\'||d==\'rgba(0, 0, 0, 0)\')d=e;c=c.parentNode;}a.style.display=\'\';a.style.backgroundColor=d;b.innerHTML=\'&#9650;\';}else{a.style.display=\'none\';b.innerHTML=\'&#9660;\';}" style="font-weight: bold; cursor: pointer; font-size: 0.9em;"><span style="padding: 0 5px;">&#9660;</span>' . $lang['Hidden text'] . '</div><div style="padding: 6px; margin: 0; display: none;"><p>';
}
return $st . $body . '</p></div></div><p>';
},
],
['tag' => 'img',
'type' => 'img',
'parents' => ['inline', 'block', 'url'],
'text only' => true,
'attrs' => [
'Def' => [
'body format' => '%^(?:(?:ht|f)tps?://[^\x00-\x1f\s<"]+|data:image/[a-z]+;base64,(?:[a-zA-Z\d/\+\=]+))$%D'
],
'no attr' => [
'body format' => '%^(?:(?:ht|f)tps?://[^\x00-\x1f\s<"]+|data:image/[a-z]+;base64,(?:[a-zA-Z\d/\+\=]+))$%D'
],
],
'handler' => function($body, $attrs, $parser) {
if (! isset($attrs['Def'])) {
$attrs['Def'] = (substr($body, 0, 11) === 'data:image/') ? 'base64' : basename($body);
}
// тег в подписи
if ($parser->attr('isSign')) {
if ($parser->attr('showImgSign')) {
return '<img src="' . $body . '" alt="' . $attrs['Def'] . '" class="sigimage" />';
}
} else {
// тег в теле сообщения
if ($parser->attr('showImg')) {
return '<span class="postimg"><img src="' . $body . '" alt="' . $attrs['Def'] . '" /></span>';
}
}
$lang = $parser->attr('lang');
return '<a href="' . $body . '" rel="nofollow">&lt;' . $lang['Image link']. ' - ' . $attrs['Def'] . '&gt;</a>';
},
],
['tag' => 'url',
'type' => 'url',
'parents' => ['inline', 'block'],
'attrs' => [
'Def' => [
'format' => '%^[^\x00-\x1f]+$%',
],
'no attr' => [
'body format' => '%^[^\x00-\x1f]+$%D',
],
],
'handler' => function($body, $attrs, $parser) {
if (isset($attrs['Def'])) {
$url = $attrs['Def'];
} else {
$url = $body;
// возможно внутри была картинка, которая отображается как ссылка
if (preg_match('%^<a href=".++(?<=</a>)$%D', $url)) {
return $url;
}
// возможно внутри картинка
if (preg_match('%<img src="([^"]+)"%', $url, $match)) {
$url = $match[1];
}
}
$fUrl = str_replace(array(' ', '\'', '`', '"'), array('%20', '', '', ''), $url);
if (strpos($url, 'www.') === 0) {
$fUrl = 'http://'.$fUrl;
} else if (strpos($url, 'ftp.') === 0) {
$fUrl = 'ftp://'.$fUrl;
} else if (strpos($url, '/') === 0) {
$fUrl = $parser->attr('baseUrl') . $fUrl;
} else if (!preg_match('%^([a-z0-9]{3,6})://%', $url)) {
$fUrl = 'http://'.$fUrl;
}
if ($url === $body) {
$url = htmlspecialchars_decode($url, ENT_QUOTES);
$url = mb_strlen($url, 'UTF-8') > 55 ? mb_substr($url, 0, 39, 'UTF-8') . ' … ' . mb_substr($url, -10, null, 'UTF-8') : $url;
$body = $parser->e($url);
}
return '<a href="' . $fUrl . '" rel="nofollow">' . $body . '</a>';
},
],
['tag' => 'table',
'type' => 'table',
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'align' => true,
'background' => true,
'bgcolor' => true,
'border' => true,
'bordercolor' => true,
'cellpadding' => true,
'cellspacing' => true,
'frame' => true,
'rules' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '</p><table' . $attr . '>' . $body . '</table><p>';
},
],
['tag' => 'caption',
'type' => 'block',
'parents' => ['table'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<caption' . $attr . '><p>' . $body . '</p></caption>';
},
],
['tag' => 'thead',
'type' => 't',
'parents' => ['table'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<thead' . $attr . '>' . $body . '</thead>';
},
],
['tag' => 'tbody',
'type' => 't',
'parents' => ['table'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<tbody' . $attr . '>' . $body . '</tbody>';
},
],
['tag' => 'tfoot',
'type' => 't',
'parents' => ['table'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<tfoot' . $attr . '>' . $body . '</tfoot>';
},
],
['tag' => 'tr',
'type' => 'tr',
'parents' => ['table', 't'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<tr' . $attr . '>' . $body . '</tr>';
},
],
['tag' => 'th',
'type' => 'block',
'parents' => ['tr'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'colspan' => true,
'rowspan' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<th' . $attr . '><p>' . $body . '</p></th>';
},
],
['tag' => 'td',
'type' => 'block',
'parents' => ['tr'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'colspan' => true,
'rowspan' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<td' . $attr . '><p>' . $body . '</p></td>';
},
],
];
$lang = [
'Hidden text' => 'Hidden text',
'wrote' => 'wrote:', // For [quote]'s
'After time' => 'Added later',
'After time s' => ' s',
'After time i' => ' min',
'After time H' => ' h',
'After time d' => ' d',
'Image link' => 'image', // This is displayed (i.e. <image>) instead of images when "Show images" is disabled in the profile
];
$text = '[table align="center" border="1" bordercolor="#ccc" cellpadding="5" cellspacing="0" style="border-collapse:collapse; width:500px"]
[caption][b]Table[/b][/caption]
[thead]
[tr]
[th style=width:50%]Column 1[/th]
[th style=width:50%]Column 2[/th]
[/tr]
[/thead]
[tbody]
[tr]
[td]1.1[/td]
[td]
[list]
[*]1.2
[*]1.3
[*]1.4
[/list]
[/td]
[/tr]
[/tbody]
[/table]
[size=36]Hello World![/size]
[img]data:image/gif;base64,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[/img]
[code]
$parser->setBBCodes($bbcodes)
->setAttr(\'baseUrl\', \'http://localhost\')
->setAttr(\'lang\', $lang)
->setAttr(\'showImg\', true)
->setAttr(\'showImgSign\', true)
->setAttr(\'isSign\', false)
->parse($text);
[/code]
[center][background=#00CCCC][url=https://github.com/MioVisman/Parserus]Parserus[/url] BBCode parser[/background][/center]
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
';
$parser = new Parserus(ENT_XHTML);
$parser->setBBCodes($bbcodes)
->setAttr('baseUrl', 'http://localhost')
->setAttr('lang', $lang)
->setAttr('showImg', true)
->setAttr('showImgSign', true)
->setAttr('isSign', false)
->parse($text);
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>' .
$parser->getHtml() .
'</body>
</html>';

View file

@ -0,0 +1,27 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->setBBCodes([
['tag' => 'url',
'type' => 'url',
'parents' => ['inline', 'block'],
'attrs' => [
'Def' => [
'format' => '%^[^\x00-\x1f]+$%',
],
'no attr' => [
'body format' => '%^[^\x00-\x1f]+$%D',
],
],
'handler' => function($body, $attrs, $parser) {
#...
},
],
])->parse('Hello www.example.com World!')
->detectUrls()
->getCode();
#output: Hello [url]www.example.com[/url] World!

View file

@ -0,0 +1,63 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->setBBCodes([
['tag' => 'url',
'type' => 'url',
'parents' => ['inline', 'block'],
'attrs' => [
'Def' => [
'format' => '%^[^\x00-\x1f]+$%',
],
'no attr' => [
'body format' => '%^[^\x00-\x1f]+$%D',
],
],
'handler' => function($body, $attrs, $parser) {
#...
},
],
['tag' => 'h',
'type' => 'h',
'handler' => function($body, $attrs, $parser) {
#...
},
],
])->parse('www.example.com/link1[h]Hello www.example.com/link2 World![/h]www.example.com/link3')
->detectUrls()
->getCode();
#output: [url]www.example.com/link1[/url][h]Hello www.example.com/link2 World![/h][url]www.example.com/link3[/url]
echo "\n\n";
echo $parser->setBlackList(['url'])
->setWhiteList()
->parse('www.example.com/link1[h]Hello www.example.com/link2 World![/h]www.example.com/link3')
->detectUrls()
->getCode();
#output: www.example.com/link1[h]Hello www.example.com/link2 World![/h]www.example.com/link3
var_dump($parser->getErrors());
#output: array (size=1)
#output: 0 => string 'Тег [url] находится в черном списке' (length=60)
echo "\n\n";
echo $parser->setBlackList()
->setWhiteList(['h'])
->parse('www.example.com/link1[h]Hello www.example.com/link2 World![/h]www.example.com/link3')
->detectUrls()
->getCode();
#output: www.example.com/link1[h]Hello www.example.com/link2 World![/h]www.example.com/link3
var_dump($parser->getErrors());
#output: array (size=1)
#output: 0 => string 'Тег [url] отсутствует в белом списке' (length=62)

View file

@ -0,0 +1,9 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->e("<'abcde'>");
#output: &lt;&apos;abcde&apos;&gt;

View file

@ -0,0 +1,126 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->setBBCodes([
['tag' => 'table',
'type' => 'table',
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'align' => true,
'background' => true,
'bgcolor' => true,
'border' => true,
'bordercolor' => true,
'cellpadding' => true,
'cellspacing' => true,
'frame' => true,
'rules' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<table' . $attr . '>' . $body . '</table>';
},
],
['tag' => 'tr',
'type' => 'tr',
'parents' => ['table', 't'],
'tags only' => true,
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<tr' . $attr . '>' . $body . '</tr>';
},
],
['tag' => 'th',
'type' => 'block',
'parents' => ['tr'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'colspan' => true,
'rowspan' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<th' . $attr . '>' . $body . '</th>';
},
],
['tag' => 'td',
'type' => 'block',
'parents' => ['tr'],
'self nesting' => true,
'attrs' => [
'no attr' => true,
'style' => true,
'colspan' => true,
'rowspan' => true,
],
'handler' => function($body, $attrs) {
$attr = '';
foreach ($attrs as $key => $val) {
$attr .= ' ' . $key . '="' . $val . '"';
}
return '<td' . $attr . '>' . $body . '</td>';
},
],
])->parse('
[table align=right border=1 bordercolor=#ccc cellpadding=5 cellspacing=0 style="border-collapse:collapse; width:500px"]
[tr]
[th style="width:50%"]Position[/th]
[th style=width:50%]Astronaut[/th]
[/tr]
[tr]
[td]Commander[/td]
[td]Neil A. Armstrong[/td]
[/tr]
[tr]
[td]Command Module Pilot[/td]
[td]Michael Collins[/td]
[/tr]
[tr]
[td]Lunar Module Pilot[/td]
[td]Edwin "Buzz" E. Aldrin, Jr.[/td]
[/tr]
[/table]
')->getCode();
#output:
#[table align="right" border="1" bordercolor="#ccc" cellpadding="5" cellspacing="0" style="border-collapse:collapse; width:500px"]
# [tr]
# [th style=width:50%]Position[/th]
# [th style=width:50%]Astronaut[/th]
# [/tr]
# [tr]
# [td]Commander[/td]
# [td]Neil A. Armstrong[/td]
# [/tr]
# [tr]
# [td]Command Module Pilot[/td]
# [td]Michael Collins[/td]
# [/tr]
# [tr]
# [td]Lunar Module Pilot[/td]
# [td]Edwin "Buzz" E. Aldrin, Jr.[/td]
# [/tr]
#[/table]
#

View file

@ -0,0 +1,41 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
$parser->addBBCode([
'tag' => 'b',
'handler' => function($body) {
return '<b>' . $body . '</b>';
}
])->addBBcode([
'tag' => 'i',
'handler' => function($body) {
return '<i>' . $body . '</i>';
},
]);
$parser->parse("[i][b] [/b][/i]")->stripEmptyTags(" \n", true);
$err = [
1 => '[%1$s] is in the black list',
2 => '[%1$s] is absent in the white list',
3 => '[%1$s] can\'t be opened in the [%2$s]',
4 => '[/%1$s] was found without a matching [%1$s]',
5 => '[/%1$s] is found for single [%1$s]',
6 => 'There are no attributes in [%1$s]',
7 => 'Primary attribute is forbidden in [%1$s=...]',
8 => 'Secondary attributes are forbidden in [%1$s ...]',
9 => 'The attribute \'%2$s\' doesn\'t correspond to a template in the [%1$s]',
10 => '[%1$s ...] contains unknown secondary attribute \'%2$s\'',
11 => 'The body of [%1$s] doesn\'t correspond to a template',
12 => '[%1$s] was opened within itself, this is not allowed',
13 => 'In the [%1$s] is absent mandatory attribute \'%2$s\'',
14 => 'All tags are empty'
];
var_dump($parser->getErrors($err));
#output: array (size=1)
# 0 => string 'All tags are empty' (length=18)

View file

@ -0,0 +1,34 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
echo $parser->setSmilies([
':)' => 'http://example.com/smilies/smile.png',
';)' => 'http://example.com/smilies/wink.png',
])->addBBCode([
'tag' => 'img',
'type' => 'img',
'parents' => ['inline', 'block', 'url'],
'text only' => true,
'attrs' => [
'Def' => [
'body format' => '%^(?:(?:ht|f)tps?://[^\x00-\x1f\s<"]+|data:image/[a-z]+;base64,(?:[a-zA-Z\d/\+\=]+))$%D'
],
'no attr' => [
'body format' => '%^(?:(?:ht|f)tps?://[^\x00-\x1f\s<"]+|data:image/[a-z]+;base64,(?:[a-zA-Z\d/\+\=]+))$%D'
],
],
'handler' => function($body, $attrs, $parser) {
if (! isset($attrs['Def'])) {
$attrs['Def'] = (substr($body, 0, 11) === 'data:image/') ? 'base64' : basename($body);
}
return '<img src="' . $body . '" alt="' . $attrs['Def'] . '">';
},
])->setSmTpl('<img src="{url}" alt="{alt}">')
->parse(":)\n;)")
->detectSmilies()
->getHTML();
#output: <img src="http://example.com/smilies/smile.png" alt=":)"><br><img src="http://example.com/smilies/wink.png" alt=";)">

View file

@ -0,0 +1,61 @@
<?php
include '../Parserus.php';
$parser = new Parserus();
$parser->addBBCode([
'tag' => 'b',
'handler' => function($body) {
return '<b>' . $body . '</b>';
}
])->addBBcode([
'tag' => 'i',
'handler' => function($body) {
return '<i>' . $body . '</i>';
},
]);
# №1
var_dump($parser->parse("[i][b] [/b][/i]")->stripEmptyTags());
#output: boolean false
echo $parser->getCode();
#output: [i][b] [/b][/i]
echo "\n\n";
# №2
var_dump($parser->parse("[i][b] [/b][/i]")->stripEmptyTags(" \n", true));
#output: boolean true
echo $parser->getCode();
#output: [i][b] [/b][/i]
var_dump($parser->getErrors());
#output: array (size=1)
# 0 => string 'Все теги пустые' (length=28)
echo "\n\n";
# №3
var_dump($parser->parse("[i][b] [/b][/i]")->stripEmptyTags(" \n"));
#output: boolean true
echo $parser->getCode();
#output:
var_dump($parser->getErrors());
#output: array (size=1)
# empty