From 495166e21c276024fe64557335f4fea28623955d Mon Sep 17 00:00:00 2001 From: Visman Date: Tue, 3 Oct 2017 21:13:21 +0700 Subject: [PATCH] 2017-10-03 --- .gitignore | 2 +- app/Controllers/Routing.php | 2 +- app/Models/Pages/CrumbTrait.php | 78 ++ app/Models/Pages/Forum.php | 26 +- app/Models/Pages/Index.php | 51 +- app/Models/Pages/OnlineTrait.php | 59 + app/Models/Pages/Rules.php | 3 + app/Models/Pages/Topic.php | 249 ++-- app/Models/Pages/UsersTrait.php | 4 +- app/config/defaultBBCode.php | 510 +++++++ app/lang/English/index.po | 13 +- app/lang/English/topic.po | 14 +- app/lang/Russian/index.po | 13 +- app/lang/Russian/topic.po | 14 +- app/templates/index.tpl | 36 +- app/templates/layouts/redirect.tpl | 2 +- app/templates/layouts/stats.tpl | 39 + app/templates/topic.tpl | 25 +- composer.json | 3 +- composer.lock | 46 +- public/.htaccess | 2 +- public/img/sm/big_smile.png | Bin 0 -> 373 bytes public/img/sm/cool.png | Bin 0 -> 380 bytes public/img/sm/hmm.png | Bin 0 -> 422 bytes public/img/sm/lol.png | Bin 0 -> 364 bytes public/img/sm/mad.png | Bin 0 -> 409 bytes public/img/sm/neutral.png | Bin 0 -> 415 bytes public/img/sm/roll.png | Bin 0 -> 386 bytes public/img/sm/sad.png | Bin 0 -> 420 bytes public/img/sm/smile.png | Bin 0 -> 426 bytes public/img/sm/tongue.png | Bin 0 -> 416 bytes public/img/sm/wink.png | Bin 0 -> 428 bytes public/img/sm/yikes.png | Bin 0 -> 406 bytes public/style/ForkBB/style.css | 5 + vendor/composer/autoload_namespaces.php | 1 + vendor/composer/autoload_static.php | 11 + vendor/composer/installed.json | 44 + vendor/miovisman/parserus/LICENSE | 21 + vendor/miovisman/parserus/Parserus.php | 1222 +++++++++++++++++ vendor/miovisman/parserus/README.md | 40 + vendor/miovisman/parserus/composer.json | 21 + vendor/miovisman/parserus/examples/_first.php | 20 + .../miovisman/parserus/examples/_second.php | 20 + vendor/miovisman/parserus/examples/_third.php | 20 + vendor/miovisman/parserus/examples/attr.php | 50 + .../parserus/examples/bbcodes_test.php | 588 ++++++++ .../parserus/examples/detectUrls.php | 27 + .../parserus/examples/detectUrls2.php | 63 + vendor/miovisman/parserus/examples/e.php | 9 + .../miovisman/parserus/examples/getCode.php | 126 ++ .../miovisman/parserus/examples/getErrors.php | 41 + .../miovisman/parserus/examples/smilies.php | 34 + .../parserus/examples/stripEmptyTags.php | 61 + 53 files changed, 3390 insertions(+), 225 deletions(-) create mode 100644 app/Models/Pages/CrumbTrait.php create mode 100644 app/Models/Pages/OnlineTrait.php create mode 100644 app/config/defaultBBCode.php create mode 100644 app/templates/layouts/stats.tpl create mode 100644 public/img/sm/big_smile.png create mode 100644 public/img/sm/cool.png create mode 100644 public/img/sm/hmm.png create mode 100644 public/img/sm/lol.png create mode 100644 public/img/sm/mad.png create mode 100644 public/img/sm/neutral.png create mode 100644 public/img/sm/roll.png create mode 100644 public/img/sm/sad.png create mode 100644 public/img/sm/smile.png create mode 100644 public/img/sm/tongue.png create mode 100644 public/img/sm/wink.png create mode 100644 public/img/sm/yikes.png create mode 100644 vendor/miovisman/parserus/LICENSE create mode 100644 vendor/miovisman/parserus/Parserus.php create mode 100644 vendor/miovisman/parserus/README.md create mode 100644 vendor/miovisman/parserus/composer.json create mode 100644 vendor/miovisman/parserus/examples/_first.php create mode 100644 vendor/miovisman/parserus/examples/_second.php create mode 100644 vendor/miovisman/parserus/examples/_third.php create mode 100644 vendor/miovisman/parserus/examples/attr.php create mode 100644 vendor/miovisman/parserus/examples/bbcodes_test.php create mode 100644 vendor/miovisman/parserus/examples/detectUrls.php create mode 100644 vendor/miovisman/parserus/examples/detectUrls2.php create mode 100644 vendor/miovisman/parserus/examples/e.php create mode 100644 vendor/miovisman/parserus/examples/getCode.php create mode 100644 vendor/miovisman/parserus/examples/getErrors.php create mode 100644 vendor/miovisman/parserus/examples/smilies.php create mode 100644 vendor/miovisman/parserus/examples/stripEmptyTags.php diff --git a/.gitignore b/.gitignore index 3d0b188d..32af164c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ /app/config/main.php /app/cache/**/*.php /app/cache/**/*.lock -/public/avatar/* +/public/img/avatars/* diff --git a/app/Controllers/Routing.php b/app/Controllers/Routing.php index 4d766b67..dd26066e 100644 --- a/app/Controllers/Routing.php +++ b/app/Controllers/Routing.php @@ -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'); diff --git a/app/Models/Pages/CrumbTrait.php b/app/Models/Pages/CrumbTrait.php new file mode 100644 index 00000000..b3edf1f9 --- /dev/null +++ b/app/Models/Pages/CrumbTrait.php @@ -0,0 +1,78 @@ +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); + } +} diff --git a/app/Models/Pages/Forum.php b/app/Models/Pages/Forum.php index b852419a..4c5e8bf7 100644 --- a/app/Models/Pages/Forum.php +++ b/app/Models/Pages/Forum.php @@ -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']]), diff --git a/app/Models/Pages/Index.php b/app/Models/Pages/Index.php index b59807f4..1db4dfcb 100644 --- a/app/Models/Pages/Index.php +++ b/app/Models/Pages/Index.php @@ -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; } } diff --git a/app/Models/Pages/OnlineTrait.php b/app/Models/Pages/OnlineTrait.php new file mode 100644 index 00000000..7b864ec6 --- /dev/null +++ b/app/Models/Pages/OnlineTrait.php @@ -0,0 +1,59 @@ +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; + } + } +} diff --git a/app/Models/Pages/Rules.php b/app/Models/Pages/Rules.php index 2a7e8b50..1acfb1ff 100644 --- a/app/Models/Pages/Rules.php +++ b/app/Models/Pages/Rules.php @@ -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; } diff --git a/app/Models/Pages/Topic.php b/app/Models/Pages/Topic.php index 45a83df6..bea4febc 100644 --- a/app/Models/Pages/Topic.php +++ b/app/Models/Pages/Topic.php @@ -5,7 +5,9 @@ namespace ForkBB\Models\Pages; class Topic extends Page { use UsersTrait; - + use OnlineTrait; + use CrumbTrait; + /** * Имя шаблона * @var string @@ -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 idc->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,62 +129,88 @@ 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'; } - $topic = $this->c->DB->query($sql, $vars)->fetch(); + $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(); + // тема отсутствует или недоступна if (empty($topic)) { return $this->c->Message->message('Bad request'); } - + list($fTree, $fDesc, $fAsc) = $this->c->forums; - + // раздел отсутствует в доступных if (empty($fDesc[$topic['forum_id']])) { return $this->c->Message->message('Bad request'); } + + if (null === $page) { + $vars[':tid'] = $topic['id']; + $sql = 'SELECT COUNT(id) FROM ::posts WHERE topic_id=?i:tid AND iddispPosts); + $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, + ':rows' => $user->dispPosts, ]; $sql = 'SELECT id 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, + 'topic' => $topic, + 'posts' => $posts, + 'signs' => $signs, 'warnings' => $warnings, - 'crumbs' => array_reverse($crumbs), - 'topicName' => $topic['subject'], - 'newPost' => $newPost, - 'stickFP' => $stickFP, - 'pages' => $this->c->Func->paginate($pages, $page, 'Topic', ['id' => $args['id'], 'name' => $topic['subject']]), + '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' => $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; } diff --git a/app/Models/Pages/UsersTrait.php b/app/Models/Pages/UsersTrait.php index e586e41f..7aa23487 100644 --- a/app/Models/Pages/UsersTrait.php +++ b/app/Models/Pages/UsersTrait.php @@ -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}"; } } diff --git a/app/config/defaultBBCode.php b/app/config/defaultBBCode.php new file mode 100644 index 00000000..5b7d472c --- /dev/null +++ b/app/config/defaultBBCode.php @@ -0,0 +1,510 @@ + 'ROOT', + 'type' => 'block', + 'handler' => function($body) { + $body = '

' . $body . '

'; + + // Replace any breaks next to paragraphs so our replace below catches them + $body = preg_replace('%()(?:\s*?
){1,2}%', '$1', $body); + $body = preg_replace('%(?:
\s*?){1,2}()%', '$1', $body); + + // Remove any empty paragraph tags (inserted via quotes/lists/code/etc) which should be stripped + $body = str_replace('

', '', $body); + + $body = preg_replace('%
\s*?
%', '

', $body); + + $body = str_replace('


', '

', $body); + $body = str_replace('

', '


', $body); + $body = str_replace('

', '

', $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 '

' . $body . '

'; + }, + ], + ['tag' => 'b', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'em', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'u', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 's', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'del', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'ins', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'h', + 'type' => 'h', + 'handler' => function($body) { + return '

' . $body . '

'; + }, + ], + ['tag' => 'hr', + 'type' => 'block', + 'single' => true, + 'handler' => function() { + return '


'; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['tag' => 'right', + 'type' => 'block', + 'handler' => function($body) { + return '

' . $body . '

'; + }, + ], + ['tag' => 'center', + 'type' => 'block', + 'handler' => function($body) { + return '

' . $body . '

'; + }, + ], + ['tag' => 'justify', + 'type' => 'block', + 'handler' => function($body) { + return '

' . $body . '

'; + }, + ], + ['tag' => 'mono', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'font', + 'self nesting' => true, + 'attrs' => [ + 'Def' => [ + 'format' => '%^[a-z\d, -]+$%i', + ], + ], + 'handler' => function($body, $attrs) { + return '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + } else { + return '' . $body . ''; + } + }, + ], + ['tag' => '*', + 'type' => 'block', + 'self nesting' => true, + 'parents' => ['list'], + 'auto' => true, + 'handler' => function($body) { + return '

  • ' . $body . '

  • '; + }, + ], + ['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 '

      ' . $body . '

    '; + case '1': + return '

      ' . $body . '

    '; + default: + return '

    '; + } + }, + ], + ['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 '' . $attr . ':
    '; + }, + ], + ['tag' => 'quote', + 'type' => 'block', + 'self nesting' => true, + 'attrs' => [ + 'Def' => true, + 'no attr' => true, + ], + 'handler' => function($body, $attrs) { + if (isset($attrs['Def'])) { + $st = '

    ' . $attrs['Def'] . ' ' . __('wrote') . '

    '; + } else { + $st = '

    '; + } + + return $st . $body . '

    '; + }, + ], + ['tag' => 'spoiler', + 'type' => 'block', + 'self nesting' => true, + 'attrs' => [ + 'Def' => true, + 'no attr' => true, + ], + 'handler' => function($body, $attrs) { + if (isset($attrs['Def'])) { + $st = '

    ' . $attrs['Def'] . '

    '; + } else { + $st = '

    ' . __('Hidden text') . '

    '; + } + + return $st . $body . '

    '; + }, + ], + ['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 '' . $attrs['Def'] . ''; + } + } else { + // тег в теле сообщения + if ($parser->attr('showImg')) { + return '' . $attrs['Def'] . ''; + } + } + + return '<' . __('Image link') . ' - ' . $attrs['Def'] . '>'; + }, + ], + ['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('%^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 '' . $body . ''; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], +]; diff --git a/app/lang/English/index.po b/app/lang/English/index.po index b3352a8d..2794798e 100644 --- a/app/lang/English/index.po +++ b/app/lang/English/index.po @@ -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: %1$s, guests: %2$s." diff --git a/app/lang/English/topic.po b/app/lang/English/topic.po index e6ab3f65..9190e84d 100644 --- a/app/lang/English/topic.po +++ b/app/lang/English/topic.po @@ -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: %1$s, guests: %2$s." + +msgid "Online users" +msgstr "Online:" + +msgid "Stats info" +msgstr "Topic information" diff --git a/app/lang/Russian/index.po b/app/lang/Russian/index.po index 5757295f..58d777f7 100644 --- a/app/lang/Russian/index.po +++ b/app/lang/Russian/index.po @@ -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 "Сейчас пользователей: %1$s, гостей: %2$s." diff --git a/app/lang/Russian/topic.po b/app/lang/Russian/topic.po index 71429e81..7e94e5f1 100644 --- a/app/lang/Russian/topic.po +++ b/app/lang/Russian/topic.po @@ -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 "Сейчас в этой теме пользователей: %1$s, гостей: %2$s." + +msgid "Online users" +msgstr "Активны:" + +msgid "Stats info" +msgstr "Информация о теме" diff --git a/app/templates/index.tpl b/app/templates/index.tpl index e664d8a8..f4c03584 100644 --- a/app/templates/index.tpl +++ b/app/templates/index.tpl @@ -22,38 +22,4 @@

    {!! __('Empty board') !!}

    @endif -
    -

    {!! __('Board info') !!}

    -
    -
    -
    {!! __('Board stats') !!}
    -
    {!! __('No of users') !!} {!! $stats['total_users'] !!}
    -
    {!! __('No of topics') !!} {!! $stats['total_topics'] !!}
    -
    {!! __('No of posts') !!} {!! $stats['total_posts'] !!}
    -
    -
    -
    {!! __('User info') !!}
    -@if(is_string($stats['newest_user'])) -
    {!! __('Newest user') !!} {{ $stats['newest_user'] }}
    -@else -
    {!! __('Newest user') !!} {{ $stats['newest_user'][1] }}
    -@endif -@if($online) -
    {!! __('Users online') !!} {!! $online['number_of_users'] !!}, {!! __('Guests online') !!} {!! $online['number_of_guests'] !!}.
    -
    {!! __('Most online', $online['max'], $online['max_time']) !!}
    -@endif -
    -@if($online && $online['list']) -
    -
    {!! __('Online') !!}
    -@foreach($online['list'] as $cur) -@if(is_string($cur)) -
    {{ $cur }}
    -@else -
    {{ $cur[1] }}
    -@endif -@endforeach -
    -@endif -
    -
    +@include('layouts/stats') diff --git a/app/templates/layouts/redirect.tpl b/app/templates/layouts/redirect.tpl index 61e5ab66..6b7e319f 100644 --- a/app/templates/layouts/redirect.tpl +++ b/app/templates/layouts/redirect.tpl @@ -1,5 +1,5 @@ - + diff --git a/app/templates/layouts/stats.tpl b/app/templates/layouts/stats.tpl new file mode 100644 index 00000000..20cb9e8e --- /dev/null +++ b/app/templates/layouts/stats.tpl @@ -0,0 +1,39 @@ +
    +

    {!! __('Stats info') !!}

    +
    +@if($stats) +
    +
    {!! __('Board stats') !!}
    +
    {!! __('No of users') !!} {!! $stats['total_users'] !!}
    +
    {!! __('No of topics') !!} {!! $stats['total_topics'] !!}
    +
    {!! __('No of posts') !!} {!! $stats['total_posts'] !!}
    +
    +@endif +
    +
    {!! __('User info') !!}
    +@if($stats && is_string($stats['newest_user'])) +
    {!! __('Newest user') !!} {{ $stats['newest_user'] }}
    +@elseif($stats) +
    {!! __('Newest user') !!} {{ $stats['newest_user'][1] }}
    +@endif +@if($online) +
    {!! __('Visitors online', $online['number_of_users'], $online['number_of_guests']) !!}
    +@endif +@if($stats) +
    {!! __('Most online', $online['max'], $online['max_time']) !!}
    +@endif +
    +@if($online && $online['list']) +
    +
    {!! __('Online users') !!}
    +@foreach($online['list'] as $cur) +@if(is_string($cur)) +
    {{ $cur }}
    +@else +
    {{ $cur[1] }}
    +@endif +@endforeach +
    +@endif +
    +
    diff --git a/app/templates/topic.tpl b/app/templates/topic.tpl index 39f6b4d4..062b3816 100644 --- a/app/templates/topic.tpl +++ b/app/templates/topic.tpl @@ -48,14 +48,14 @@ @endif
    -

    {{ $topicName }}

    +

    {{ $topic['subject'] }}

    @foreach($posts as $post)
    -
    -

    {{ $topicName }} - #{!! $post['post_number'] !!}

    +
    +

    {{ $topic['subject'] }} - #{!! $post['post_number'] !!}

    #{!! $post['post_number'] !!} -
    +
    @endif
    - +
    + {!! $post['message'] !!} +
    +@if(isset($signs[$post['poster_id']])) +
    +
    + {!! $signs[$post['poster_id']] !!} +
    +@endif
    - @endif -
    + @endforeach @@ -112,3 +120,6 @@ @endif @yield('crumbs')
    +@if($online) +@include('layouts/stats') +@endif \ No newline at end of file diff --git a/composer.json b/composer.json index eecb01e5..a1e1cb56 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ }, "require": { "php": ">=5.6.0", - "artoodetoo/dirk": "dev-master" + "artoodetoo/dirk": "dev-master", + "MioVisman/Parserus": "^0.9.1" } } diff --git a/composer.lock b/composer.lock index 5f7dc7f3..ce407c03 100644 --- a/composer.lock +++ b/composer.lock @@ -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": [], diff --git a/public/.htaccess b/public/.htaccess index 5639360b..ad1d367a 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -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] diff --git a/public/img/sm/big_smile.png b/public/img/sm/big_smile.png new file mode 100644 index 0000000000000000000000000000000000000000..1c4ea81f3a54f6ac7961f9bbbf96db62dd7018cd GIT binary patch literal 373 zcmV-*0gC>KP)z!o&f*YV&%;z|Jp|XU;m;KL6EL|Jy3no-Y3DJO9Nh^|?jpoB%K;5cshw=H_tm z00001bW%=J06^y0W&i*HheGTAz=HOIp-TyxCi8PrGCqW4nkVV|eDtjo8KA_l@CMrq4I82YIHo&hpju z@fi`j`;T}?LFbo%LbxiyvV!}~y{Dw8spS62mkfw*L18=f(*( TqA@7w00000NkvXXu0mjf&M~#W literal 0 HcmV?d00001 diff --git a/public/img/sm/cool.png b/public/img/sm/cool.png new file mode 100644 index 0000000000000000000000000000000000000000..54ec46f6e9381190f6b3fdbcb47d2e72760d538a GIT binary patch literal 380 zcmV-?0fYXDP)i`iE5&iK1;+p_sVq!){MgRZ+DJd!c-vIvQ0RPwk|FZxj8UX*g0P~;#_@e;*`2hdz zd;iP;|J4AusT0PB0RF51=$!!Kr3ZI%TnPyYiG*@BE*SI6JpbZc{qzyFv=RTm0RH3- zJ3BirE-t>dD&C_pr+OJCAP};95&zL=|Ltf0;d}q#K0`er|J7FPyLkKn0004WQchC< zK<3zH0001&NklQ?!(EDS&i4n>h)|FZ-rOqL*z~E3tQMB7q-dl9dMV{w^ z>(Y5io^T-qPjr!Hr86@-JNb1tnj(g0ZRvqoCd a`F}t4p9zOyzihq$0000Hz=O00993;+p{d@c{p`0ROrG_@e+SDk?@sM*r?(|LuGI`2hdp z692&f{QMFB%mDw?0P~;#{q-t0E*1XrM*i_){_GLPhXDQb5#pr>{;UA$odEyUW&h?q z|Iu9k<758qD*xLm<;^Gm$wt*L_t(|+Fj2_5`!=d1yGtB z1{``z=%n|a`(G@w!zzC?<9X7;lBPmr;GOj9-{bJf$;5}4L7v^6D6U0WmO_l(u$7Ix zFAC3tI&AhijDuyCxh~Xdce@hsyo#czng_?rxdt1E`M%GPk#L8CEgdn&kZ#5HNg7)^ zAwV29Em$+LWf+9u&`zl-|F7c&Cvj*ybn#n=a?b50Ra6branhEWlK=O80jw1aPv>*_ Q^#A|>07*qoM6N<$g1`981^@s6 literal 0 HcmV?d00001 diff --git a/public/img/sm/lol.png b/public/img/sm/lol.png new file mode 100644 index 0000000000000000000000000000000000000000..bad071878c6540ba172c144a9e0907212ddb0080 GIT binary patch literal 364 zcmV-y0h9iTP)`f|Nq8^0RPwkL@OxkWH05dQ2t z|HwxFxd8v#MngRz|HVH4=0^YFKL6lj|Ltf0)mH!GR{!C9=$ruZpa3T7T8RJv00DGT zPE!Ct=GbNc004wZL_t(|+Fj4r4udcZh2b>ow532Po9?~W`@bAE5sJij_bZVtiw>CR z;rjSI-?t1d)8aKE-!e}*QB*Zh0d<*!Iy*|Gl$7_&X@8*S`Pf^4ILf66s{=4D*%M~n7!)yvcpwA#K8BEjsf7);k?`r{_)(GWo&Xz;~0000< KMNUMnLSTZ8ex&ID literal 0 HcmV?d00001 diff --git a/public/img/sm/mad.png b/public/img/sm/mad.png new file mode 100644 index 0000000000000000000000000000000000000000..d8ffcc61530be5dab6679949500dd815d6d5bc4e GIT binary patch literal 409 zcmV;K0cQS*P)<00000;hF#u5fLUC06H%i|GNPF^D6(>0R8k4{^(wVq*W^LjT-i|LkPqqzC@x07gbe#)km^%mC<|0ROZA z{;UAA0088q8TGkE_^~SG%_slKM*i(8=g>L%suBOiD*xbn{_GJ$Jt6<&R{zC5|J7Fi z+baLS5&zl|{{R3OD9%Cv0004WQchC00000NkvXXu0mjf De?-0w literal 0 HcmV?d00001 diff --git a/public/img/sm/neutral.png b/public/img/sm/neutral.png new file mode 100644 index 0000000000000000000000000000000000000000..a2e4cb8c9caf3de9dac165a49c28b2b3beb036f6 GIT binary patch literal 415 zcmV;Q0bu@#P)>|{_kUAVq*X8 zd;aAB|G@zN%mDw?0RQ6>{rCX<{1G=U75()p#)km@tN`hq0R8L{{qzyqp)dc{W&hqn z;Hd!lsuAR*8RMW1|G*LWu`2%UD*xIM|HUfxxkdlUM*rI?<;^Gl%wp%zIsf5%|KMZ) z<5vI0K0`er|J7Cr$BFa+0004WQchC6M6E^oynZtha?jdt^?<&_wamtUL_xS3#O2JKj(&*AP7Tfe>H>BbzR?w z4yR)Y>ugt5o(DZ2wkr;9yP_z%HrsEOYp^6V&vQy-ij@Fc5uucl$VzNSo5_f9ow#nm z6T~bjrNv@cBYpoONm-i2wi2`7g9OIdt{p=&KuS4{K!$(+`vQzW3;M_U#x(!{002ov JPDHLkV1f(zzjOcq literal 0 HcmV?d00001 diff --git a/public/img/sm/roll.png b/public/img/sm/roll.png new file mode 100644 index 0000000000000000000000000000000000000000..1c3bc7e50b3a99a1714ee9c13a73fd1718a91720 GIT binary patch literal 386 zcmV-|0e$|7P)%w9RKcR2?+_>p)mjB6E!Oo{;UAtsQ~}X0RPwk z|KUFW)mH!GR{!8*|HVG#%_ry3IsegT|KWSt&qmCU0NBVo-?n1XqDADS8RMW1PPOgE z00001bW%=J06^y0W&i*Hlu1NERCwB5kJl1|AP@kRc4#cb5@L&{_x}I?mOJ8;JIpf6 zJpi5q2;9f2cZcKt)Q>0arI{hOYuCEzxs2noLA^S_uAfPoCU?@i$MGuA0WPNxU@tWC z*B6@?k=9W~$^^E|3xGe?R);^0R8v?{^S$<{1N}m z0RPnh|G@zNvjFm-0R5~6{q-t0E*1XsM*r?){_$e{>=FI+5ypoA>74-Dp)dc{W&h?q z|KnresQ~`%D&@^5|H(%G+$#0CMgQ7H|KAY*#VY>HV)(HttaJaA^+7@|HVH4<5p+-tK0wp00DGTPE!Ct=GbNc005*(L_t(|+Fj2_62dSH1yB-q zC#3gANJ8%&?td|4lNEn7<9YHz(nN=;z&q;Me}>mPXCrUr6msw8OmkIMRaHKQ?x-7i zUlg_twOem<`1Y1rW?4|1!|BYxb>sVfQ+xYMdIb|KT-OztgpzWw>0luQcFZ77lnvrw zrV^zsc!CU*5n>tYk^-}eQwa11W?Z7jI} O0000Hs7f0QjQ-|J?xp-~j*F0{{R3;+p{d@&Nt#0RHhqDk>^QMn+;{V*l-X|FZ!9x&Z#= z0RPkg^Pm9#eKc8yG>x^Bb{Jg%^e0_(OV literal 0 HcmV?d00001 diff --git a/public/img/sm/tongue.png b/public/img/sm/tongue.png new file mode 100644 index 0000000000000000000000000000000000000000..397b98ae4f0ded90ef2e07a8130195fe05642e32 GIT binary patch literal 416 zcmV;R0bl-!P)gZ>=FOt5C80C|HnfA<751*3IEIh z>74+^hXA!2E#=K8{^J?{+D88ED(BES|Jy3{xkdlQD)_M~yV)Z0h^~sX;z8AK&0iu9D**$yJkIE&J?#nrFwU=bj^+HkB_87RCW$8N9 zwmP~n-s}uxHiJ5x&KIJQB2P`zO!MM-YZ7pViQ_l{W=KR7-1jgE0_^!M36&!T!9df2 zYaIEEF&W1)q+IP!T7*#li6vc)2T>u!x0mlxA>dIIc9bjq|L+IgI}8<>5n^}%0000< KMNUMnLSTYhjl)L( literal 0 HcmV?d00001 diff --git a/public/img/sm/wink.png b/public/img/sm/wink.png new file mode 100644 index 0000000000000000000000000000000000000000..fbc40af40303418a4ff7bcf26af55f6af52a64d8 GIT binary patch literal 428 zcmV;d0aN~oP)T0ROrG|I`5d{1N}} zbvG^*{^|h#$^gcP0RHO$+MzG~^eF%15B}>B{qzz4tN{M;M*i_)|J7w;Vq*Wl2LIMq z|K>gEodEcx0RO}#{x@B1}FCKf|?ZYjl8KtZKJqHP*IB^^&iKBD%;Z~-huIrkw*nNkX zWdn3g^9@-A%&jU02t(R3OGK3w;F%Z^{{JGdRXeB{C%=J^Wm#q_C<4-9+#Jf(k0000Y00000;+gk43{HqE5^by8~0Myjf>74+XPfz0}Cc0i;)}Ai)xkcs8C*Gqm z{>)pTC+M&zU!{^L-#VY^dd;j4+|HVH4)mGrC0RP%X|G*Lb z@&NzaD*wy?;GF>f+YvqkgS!9#00DGTPE!Ct=GbNc005RrL_t(|+Fj4r5`!QVMNy5$ z3)s8Hmfm~M|NmGHlBevs!?1=2K)CmhGl&hu;=^E_-;1m~si`(^HY?!^|4)(=uy0fXa+s)4McP&$bTH4T;;) array($vendorDir . '/miovisman/parserus'), ); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index e108cb5f..a271f3ab 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -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); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 87567c11..b70afd12 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -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" + ] } ] diff --git a/vendor/miovisman/parserus/LICENSE b/vendor/miovisman/parserus/LICENSE new file mode 100644 index 00000000..2896d351 --- /dev/null +++ b/vendor/miovisman/parserus/LICENSE @@ -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. diff --git a/vendor/miovisman/parserus/Parserus.php b/vendor/miovisman/parserus/Parserus.php new file mode 100644 index 00000000..5dba4d80 --- /dev/null +++ b/vendor/miovisman/parserus/Parserus.php @@ -0,0 +1,1222 @@ + + * @link https://github.com/MioVisman/Parserus + * @license https://opensource.org/licenses/MIT The MIT License (MIT) + */ + +class Parserus +{ + /** + * Массив дерева тегов построенный методом parse() + * @var array + */ + protected $data; + + /** + * Индекс последнего элемента из массива data + * @var int + */ + protected $dataId; + + /** + * Индекс текущего элемента дерева из массива data + * @var int + */ + protected $curId; + + /** + * Битовая маска флагов для функции htmlspecialchars() + * @var int + */ + protected $eFlags; + + /** + * Массив искомых значений для замены при преобразовании текста в HTML + * @var array + */ + protected $tSearch = ["\n", "\t", ' ', ' ']; + + /** + * Массив значений замены при преобразовании текста в HTML + * @var array + */ + protected $tRepl; + + /** + * Массив разрешенных тегов. Если null, то все теги из bbcodes разрешены + * @var array|null + */ + protected $whiteList = null; + + /** + * Массив запрещенных тегов. Если null, то все теги из bbcodes разрешены + * @var array|null + */ + protected $blackList = null; + + /** + * Ассоциативный массив bb-кодов + * @var array + */ + protected $bbcodes = []; + + /** + * Ассоциативный массив переменных, которые можно использовать в bb-кодах + * @var array + */ + protected $attrs = []; + + /** + * Ассоциативный массив смайлов + * @var array + */ + protected $smilies = []; + + /** + * Паттерн для поиска смайлов в тексте при получении HTML + * @var string|null + */ + protected $smPattern = null; + + /** + * Флаг необходимости обработки смайлов при получении HTML + * @var bool + */ + protected $smOn = false; + + /** + * Шаблон подстановки при обработке смайлов + * Например: {alt} + * @var string + */ + protected $smTpl = ''; + + /** + * Имя тега под которым идет отображение смайлов + * @var string + */ + protected $smTag = ''; + + /** + * Список тегов в которых не нужно отображать смайлы + * @var array + */ + protected $smBL = []; + + /** + * Массив ошибок полученных при отработке метода parse() + * @var array + */ + protected $errors = []; + + /** + * Флаг строгого режима поиска ошибок + * Нужен, например, для проверки атрибутов тегов при получении текста от пользователя + * @var bool + */ + protected $strict = false; + + /** + * Конструктор + * + * @param int $flag Один из флагов ENT_HTML401, ENT_XML1, ENT_XHTML, ENT_HTML5 + */ + public function __construct($flag = ENT_HTML5) + { + if (! in_array($flag, [ENT_HTML401, ENT_XML1, ENT_XHTML, ENT_HTML5])) { + $flag = ENT_HTML5; + } + $this->eFlags = $flag | ENT_QUOTES | ENT_SUBSTITUTE; + $this->tRepl = in_array($flag, [ENT_HTML5, ENT_HTML401]) + ? ['
    ', '    ', '  ', '  '] + : ['
    ', '    ', '  ', '  ']; + } + + /** + * Метод добавляет один bb-код + * + * @param array $bb Массив описания bb-кода + * @return Parserus $this + */ + public function addBBCode(array $bb) + { + $res = [ + 'type' => 'inline', + 'parents' => ['inline' => 1, 'block' => 2], + 'auto' => true, + 'self nesting' => false, + ]; + + if ($bb['tag'] === 'ROOT') { + $tag = 'ROOT'; + } else { + $tag = strtolower($bb['tag']); + } + + if (isset($bb['type'])) { + $res['type'] = $bb['type']; + + if ($bb['type'] !== 'inline') { + $res['parents'] = ['block' => 1]; + $res['auto'] = false; + } + } + + if (isset($bb['parents'])) { + $res['parents'] = array_flip($bb['parents']); + } + + if (isset($bb['auto'])) { + $res['auto'] = (bool) $bb['auto']; + } + + if (isset($bb['self nesting'])) { + $res['self nesting'] = (bool) $bb['self nesting']; + } + + if (isset($bb['recursive'])) { + $res['recursive'] = true; + } + + if (isset($bb['text only'])) { + $res['text only'] = true; + } + + if (isset($bb['tags only'])) { + $res['tags only'] = true; + } + + if (isset($bb['single'])) { + $res['single'] = true; + } + + if (isset($bb['pre'])) { + $res['pre'] = true; + } + + $res['handler'] = isset($bb['handler']) ? $bb['handler'] : null; + + $required = []; + $attrs = []; + $other = false; + + if (! isset($bb['attrs'])) { + $cur = []; + + if (isset($bb['body format'])) { + $cur['body format'] = $bb['body format']; + } + if (isset($bb['text only'])) { + $cur['text only'] = true; + } + + $attrs['no attr'] = $cur; + } else { + foreach ($bb['attrs'] as $attr => $cur) { + if (! is_array($cur)) { + $cur = []; + } + + if (isset($bb['text only'])) { + $cur['text only'] = true; + } + + $attrs[$attr] = $cur; + + if (isset($cur['required'])) { + $required[] = $attr; + } + + if ($attr !== 'Def' && $attr !== 'no attr') { + $other = true; + } + } + } + + $res['attrs'] = $attrs; + $res['required'] = $required; + $res['other'] = $other; + + $this->bbcodes[$tag] = $res; + return $this; + } + + /** + * Метод задает массив bb-кодов + * + * @param array $bbcodes Массив описаний bb-кодов + * @return Parserus $this + */ + public function setBBCodes(array $bbcodes) + { + $this->bbcodes = []; + + foreach ($bbcodes as $bb) { + $this->addBBCode($bb); + } + + $this->defaultROOT(); + return $this; + } + + /** + * Метод устанавливает тег ROOT при его отсутствии + */ + protected function defaultROOT() + { + if (! isset($this->bbcodes['ROOT'])) { + $this->addBBCode(['tag' => 'ROOT', 'type' => 'block']); + } + } + + /** + * Метод задает массив смайлов + * + * @param array $smilies Ассоциативный массив смайлов + * @return Parserus $this + */ + public function setSmilies(array $smilies) + { + $this->smilies = $smilies; + $this->createSmPattern(); + return $this; + } + + /** + * Метод генерирует паттерн для поиска смайлов в тексте + */ + protected function createSmPattern() + { + if (empty($this->smilies)) { + $this->smPattern = null; + return; + } + + $arr = array_keys($this->smilies); + sort($arr); + $arr[] = ' '; + + $symbol = ''; + $pattern = ''; + $quote = ''; + $sub = []; + + foreach ($arr as $val) { + if (preg_match('%^(.)(.+)%u', $val, $match)) { + if ($symbol === $match[1]) { + $sub[] = preg_quote($match[2], '%'); + } else { + if (count($sub) > 1) { + $pattern .= $quote . preg_quote($symbol, '%') . '(?:' . implode('|', $sub) . ')'; + $quote = '|'; + } else if (count($sub) == 1) { + $pattern .= $quote . preg_quote($symbol, '%') . $sub[0]; + $quote = '|'; + } + $symbol = $match[1]; + $sub = [preg_quote($match[2], '%')]; + } + } + } + + $this->smPattern = '%(?<=\s|^)(?:' . $pattern . ')(?![\p{L}\p{N}])%u'; + } + + /** + * Метод устанавливает шаблон для отображения смайлов + * + * @param string $tpl Строка шаблона, например: {alt} + * @param string $tag Имя тега под которым идет отображение смайлов + * @param array $bl Список тегов в которых не нужно отображать смайлы + * @return Parserus $this + */ + public function setSmTpl($tpl, $tag = 'img', array $bl = ['url']) + { + $this->smTpl = $tpl; + $this->smTag = $tag; + $this->smBL = array_flip($bl); + return $this; + } + + /** + * Метод включает (если есть возможность) отображение смайлов на текущем дереве тегов + * + * @return Parserus $this + */ + public function detectSmilies() + { + $this->smOn = null !== $this->smPattern && isset($this->bbcodes[$this->smTag]); + return $this; + } + + /** + * Метод устанавливает список разрешенных bb-кодов + * + * @param mixed $list Массив bb-кодов, null и т.д. + * @return Parserus $this + */ + public function setWhiteList($list = null) + { + $this->whiteList = is_array($list) ? $list : null; + return $this; + } + + /** + * Метод устанавливает список запрещенных bb-кодов + * + * @param mixed $list Массив bb-кодов, null и т.д. + * @return Parserus $this + */ + public function setBlackList($list = null) + { + $this->blackList = ! empty($list) && is_array($list) ? $list : null; + return $this; + } + + /** + * Метод задает значение переменной для возможного использования в bb-кодах + * + * @param string $name Имя переменной + * @param mixed $val Значение переменной + * @return Parserus $this + */ + public function setAttr($name, $val) + { + $this->attrs[$name] = $val; + return $this; + } + + /** + * Метод для получения значения переменной + * + * @param string $name Имя переменной + * @return mixed|null Значение переменной или null, если переменная не была задана ранее + */ + public function attr($name) + { + return isset($this->attrs[$name]) ? $this->attrs[$name] : null; + } + + /** + * Метод добавляет новый тег в дерево тегов + * + * @param string $tag Имя тега + * @param int $parentId Указатель на родителя + * @param array $attrs Массив атрибутов тега + * @param bool $textOnly Флаг. Если true, то в теле только текст + * @return int Указатель на данный тег + */ + protected function addTagNode($tag, $parentId = null, $attrs = [], $textOnly = false) + { + $this->data[++$this->dataId] = [ + 'tag' => $tag, + 'parent' => $parentId, + 'children' => [], + 'attrs' => $attrs, + ]; + + if ($textOnly) { + $this->data[$this->dataId]['text only'] = true; + } + + if (null !== $parentId) { + $this->data[$parentId]['children'][] = $this->dataId; + } + + return $this->dataId; + } + + /** + * Метод добавляет текстовый узел в дерево тегов + * + * @param string $text Текст + * @param int $parentId Указатель на родителя + * @return string Пустая строка + */ + protected function addTextNode($text, $parentId) + { + if (isset($text[0])) { + + $this->data[++$this->dataId] = [ + 'text' => $text, + 'parent' => $parentId, + ]; + + $this->data[$parentId]['children'][] = $this->dataId; + } + + return ''; + } + + /** + * Метод нормализует содержимое атрибута + * + * @param string $attr Содержимое атрибута полученное из регулярного выражения + * @return string + */ + protected function getNormAttr($attr) + { + // удаление крайних кавычек + if (isset($attr[1]) + && $attr[0] === $attr[strlen($attr) - 1] + && ($attr[0] === '"' || $attr[0] === '\'') + ) { + return substr($attr, 1, -1); + } + + return $attr; + } + + /** + * Метод выделяет все атрибуты с их содержимым для обрабатываемого тега + * + * @param string $tag Имя обрабатываемого тега + * @param string $type "Тип атрибутов" = ' ', '=' или ']' + * @param string $text Текст из которого выделяются атрибуты + * @return null|array + */ + protected function parseAttrs($tag, $type, $text) + { + $attrs = []; + $tagText = ''; + + if ($type === '=') { + $pattern = '%^(?!\x20) + ("[^\x00-\x1f"]*(?:"+(?!\x20*+\]|\x20++[a-z-]{2,15}=)[^\x00-\x1f"]*)*" + |\'[^\x00-\x1f\']*(?:\'+(?!\x20*+\]|\x20++[a-z-]{2,15}=)[^\x00-\x1f\']*)*\' + |[^\x00-\x20\]]+(?:\x20++(?!\]|[a-z-]{2,15}=)[^\x00-\x20\]]+)*) + \x20* + (\]|\x20(?=[a-z-]{2,15}=))%x'; + + $match = preg_split($pattern, $text, 2, PREG_SPLIT_DELIM_CAPTURE); + + if (! isset($match[1])) { + return null; + } + + $type = $match[2]; + $tagText .= $match[1] . $match[2]; + $text = $match[3]; + + $tmp = $this->getNormAttr($match[1]); + if (isset($tmp[0])) { + $attrs['Def'] = $tmp; + + // в теге не может быть первичного атрибута + if ($this->strict + && ! isset($this->bbcodes[$tag]['attrs']['Def']) + ) { + $this->errors[] = [7, $tag]; + return null; + } + } + } + + if ($type !== ']') { + $pattern = '%^\x20*+([a-z-]{2,15}) + =(?!\x20) + ("[^\x00-\x1f"]*(?:"+(?!\x20*+\]|\x20++[a-z-]{2,15}=)[^\x00-\x1f"]*)*" + |\'[^\x00-\x1f\']*(?:\'+(?!\x20*+\]|\x20++[a-z-]{2,15}=)[^\x00-\x1f\']*)*\' + |[^\x00-\x20\]]+(?:\x20++(?!\]|[a-z-]{2,15}=)[^\x00-\x20\]]+)*) + \x20* + (\]|\x20(?=[a-z-]{2,15}=))%x'; + + do { + $match = preg_split($pattern, $text, 2, PREG_SPLIT_DELIM_CAPTURE); + + if (! isset($match[1])) { + return null; + } + + $tagText .= $match[1] . '=' . $match[2] . $match[3]; + $text = $match[4]; + + $tmp = $this->getNormAttr($match[2]); + if (isset($tmp[0])) { + $attrs[$match[1]] = $tmp; + + if ($this->strict) { + // в теге не может быть вторичных атрибутов + if (! $this->bbcodes[$tag]['other']) { + $this->errors[] = [8, $tag]; + return null; + } + // этот атрибут отсутвтует в описании тега + if (! isset($this->bbcodes[$tag]['attrs'][$match[1]])) { + $this->errors[] = [10, $tag, $match[1]]; + return null; + } + } + } + + } while ($match[3] !== ']'); + } + + if (empty($attrs)) { + // в теге должны быть атрибуты + if (! empty($this->bbcodes[$tag]['required']) + || ! isset($this->bbcodes[$tag]['attrs']['no attr']) + ) { + $this->errors[] = [6, $tag]; + return null; + } + } else { + foreach ($this->bbcodes[$tag]['required'] as $key) { + // нет обязательного атрибута + if (! isset($attrs[$key])) { + $this->errors[] = [13, $tag, $key]; + return null; + } + } + } + + return [ + 'attrs' => $attrs, + 'tag' => $tagText, + 'text' => $text, + ]; + } + + /** + * Метод определяет указатель на родительский тег для текущего + * + * @param string $tag Имя тега + * @return int|false false, если невозможно подобрать родителя + */ + protected function findParent($tag) + { + if (false === $this->bbcodes[$tag]['self nesting']) { + $curId = $this->curId; + + while (null !== $curId) { + // этот тег нельзя открыть внутри аналогичного + if ($this->data[$curId]['tag'] === $tag) { + $this->errors[] = [12, $tag]; + return false; + } + $curId = $this->data[$curId]['parent']; + } + } + + $curId = $this->curId; + $curTag = $this->data[$curId]['tag']; + + while (null !== $curId) { + if (isset($this->bbcodes[$tag]['parents'][$this->bbcodes[$curTag]['type']])) { + return $curId; + } else if ($this->bbcodes[$tag]['type'] === 'inline' + || false === $this->bbcodes[$curTag]['auto'] + ) { + // тег не может быть открыт на этой позиции + $this->errors[] = [3, $tag, $this->data[$this->curId]['tag']]; + return false; + } + + $curId = $this->data[$curId]['parent']; + $curTag = $this->data[$curId]['tag']; + } + + $this->errors[] = [3, $tag, $this->data[$this->curId]['tag']]; + return false; + } + + /** + * Метод проводит проверку значений атрибутов и(или) тела тега на соответствие правилам + * + * @param string $tag Имя тега + * @param array $attrs Массив атрибутов + * @param string $text Текст из которого выделяется тело тега + * @return array|false false в случае ошибки + */ + protected function validationTag($tag, $attrs, $text) + { + if (empty($attrs)) { + $attrs['no attr'] = null; + } + + $body = null; + $end = null; + $tested = []; + $flag = false; + $bb = $this->bbcodes[$tag]; + + foreach ($attrs as $key => $val) { + // проверка формата атрибута + if (isset($bb['attrs'][$key]['format']) + && ! preg_match($bb['attrs'][$key]['format'], $val) + ) { + $this->errors[] = [9, $tag, $key]; + return false; + } + + // для рекурсивного тега тело не проверяется даже если есть правила + if (isset($bb['recursive'])) { + continue; + } + + // тело тега + if (null === $body + && (isset($bb['attrs'][$key]['body format']) + || isset($bb['attrs'][$key]['text only'])) + ) { + $ptag = preg_quote($tag, '%'); + $match = preg_split('%^([^\[]*(?:\[(?!/' . $ptag . '\])[^\[]*)*)(?:\[/' . $ptag . '\])?%i', $text, 2, PREG_SPLIT_DELIM_CAPTURE); + + $body = $match[1]; + $end = $match[2]; + } + + // для тега с 'text only' устанавливается флаг для возврата тела + if (isset($bb['attrs'][$key]['text only'])) { + $flag = true; + } + + // проверка формата тела тега + if (isset($bb['attrs'][$key]['body format'])) { + if (isset($tested[$bb['attrs'][$key]['body format']])) { + continue; + } else if (! preg_match($bb['attrs'][$key]['body format'], $body)) { + $this->errors[] = [11, $tag]; + return false; + } + + $tested[$bb['attrs'][$key]['body format']] = true; + } + } + + unset($attrs['no attr']); + + return [ + 'attrs' => $attrs, + 'body' => $flag ? $body : null, + 'end' => $end, + ]; + } + + /** + * Метод закрывает текущий тег + * + * @param string $tag Имя обрабатываемого тега + * @param string $curText Текст до тега, который еще не был учтен + * @param string $tagText Текст самого тега - [/tag] + * @return string Пустая строка, если тег удалось закрыть + */ + protected function closeTag($tag, $curText, $tagText) { + // ошибка одиночного тега + if (isset($this->bbcodes[$tag]['single'])) { + $this->errors[] = [5, $tag]; + return $curText . $tagText; + } + + $curId = $this->curId; + $curTag = $this->data[$curId]['tag']; + + while ($curTag !== $tag && $curId > 0) { + if (false === $this->bbcodes[$curTag]['auto']) { + break; + } + + $curId = $this->data[$curId]['parent']; + $curTag = $this->data[$curId]['tag']; + } + + // ошибка закрытия тега + if ($curTag !== $tag) { + $this->errors[] = [4, $tag]; + return $curText . $tagText; + } + + $this->addTextNode($curText, $this->curId); + + $this->curId = $this->data[$curId]['parent']; + return ''; + } + + /** + * Сброс состояния + * + * @param array $opts Ассоциативный массив опций + */ + protected function reset(array $opts) + { + $this->defaultROOT(); + $this->data = []; + $this->dataId = -1; + $this->curId = $this->addTagNode( + isset($opts['root']) && isset($this->bbcodes[$opts['root']]) + ? $opts['root'] + : 'ROOT' + ); + $this->smOn = false; + $this->errors = []; + $this->strict = isset($opts['strict']) ? (bool) $opts['strict'] : false; + } + + /** + * Метод строит дерево тегов из текста содержащего bb-коды + * + * @param string $text Обрабатываемый текст + * @param array $opts Ассоциативный массив опций + * @return Parserus $this + */ + public function parse($text, $opts = []) + { + $this->reset($opts); + $curText = ''; + $recCount = 0; + + $text = str_replace("\r\n", "\n", $text); + $text = str_replace("\r", "\n", $text); + + while (($match = preg_split('%(\[(/)?(' . ($recCount ? $recTag : '[a-z\*][a-z\d-]{0,10}') . ')((?(1)\]|[=\]\x20])))%i', $text, 2, PREG_SPLIT_DELIM_CAPTURE)) + && isset($match[1]) + ) { + /* $match[0] - текст до тега + * $match[1] - [ + (|/) + имя тега + (]| |=) + * $match[2] - (|/) + * $match[3] - имя тега + * $match[4] - тип атрибутов --> (]| |=) + * $match[5] - остаток текста до конца + */ + $tagText = $match[1]; + $curText .= $match[0]; + $text = $match[5]; + $tag = strtolower($match[3]); + + if (! isset($this->bbcodes[$tag])) { + $curText .= $tagText; + continue; + } + + if (! empty($match[2])) { + if ($recCount && --$recCount) { + $curText .= $tagText; + } else { + $curText = $this->closeTag($tag, $curText, $tagText); + } + continue; + } + + $attrs = $this->parseAttrs($tag, $match[4], $text); + + if (null === $attrs) { + $curText .= $tagText; + continue; + } + + if (isset($attrs['tag'][0])) { + $tagText .= $attrs['tag']; + $text = $attrs['text']; + } + + if ($recCount) { + ++$recCount; + $curText .= $tagText; + continue; + } + + if (null !== $this->blackList && in_array($tag, $this->blackList)) { + $curText .= $tagText; + $this->errors[] = [1, $tag]; + continue; + } + + if (null !== $this->whiteList && ! in_array($tag, $this->whiteList)) { + $curText .= $tagText; + $this->errors[] = [2, $tag]; + continue; + } + + if (($parentId = $this->findParent($tag)) === false) { + $curText .= $tagText; + continue; + } + + if (($attrs = $this->validationTag($tag, $attrs['attrs'], $text)) === false) { + $curText .= $tagText; + continue; + } + + $curText = $this->addTextNode($curText, $this->curId); + + $id = $this->addTagNode( + $tag, + $parentId, + $attrs['attrs'], + isset($attrs['body']) || isset($this->bbcodes[$tag]['text only']) + ); + + if (isset($attrs['body'])) { + $this->addTextNode($attrs['body'], $id); + + $text = $attrs['end']; + $this->curId = $parentId; + + } else if (isset($this->bbcodes[$tag]['single'])) { + $this->curId = $parentId; + + } else { + $this->curId = $id; + + if (isset($this->bbcodes[$tag]['recursive'])) { + $recCount = 1; + $recTag = preg_quote($tag, '%'); + } + } + } + + $this->addTextNode($curText . $text, $this->curId); + return $this; + } + + /** + * Метод возвращает HTML построенный на основании дерева тегов + * + * @param int $id Указатель на текущий тег + * @return string + */ + public function getHtml($id = 0) + { + if (isset($this->data[$id]['tag'])) { + + $body = ''; + foreach ($this->data[$id]['children'] as $cid) { + $body .= $this->getHtml($cid); + } + + $bb = $this->bbcodes[$this->data[$id]['tag']]; + + if (null === $bb['handler']) { + return $body; + } + + $attrs = []; + foreach ($this->data[$id]['attrs'] as $key => $val) { + if (isset($bb['attrs'][$key])) { + $attrs[$key] = $this->e($val); + } + } + + return $bb['handler']($body, $attrs, $this); + } + + $pid = $this->data[$id]['parent']; + $bb = $this->bbcodes[$this->data[$pid]['tag']]; + + if (isset($bb['tags only'])) { + return ''; + } + + switch (2 * (end($this->data[$pid]['children']) === $id) + + ($this->data[$pid]['children'][0] === $id) + ) { + case 1: + $text = $this->e(preg_replace('%^\x20*\n%', '', $this->data[$id]['text'])); + break; + case 2: + $text = $this->e(preg_replace('%\n\x20*$%D', '', $this->data[$id]['text'])); + break; + case 3: + $text = $this->e(preg_replace('%^\x20*\n|\n\x20*$%D', '', $this->data[$id]['text'])); + break; + default: + $text = $this->e($this->data[$id]['text']); + break; + } + + if (empty($this->data[$pid]['text only']) + && $this->smOn + && isset($this->bbcodes[$this->smTag]['parents'][$bb['type']]) + && ! isset($this->smBL[$this->data[$pid]['tag']]) + ) { + $text = preg_replace_callback($this->smPattern, function($m) { + return str_replace( + ['{url}', '{alt}'], + [$this->e($this->smilies[$m[0]]), $this->e($m[0])], + $this->smTpl + ); + }, $text); + } + + if (! isset($bb['pre'])) { + $text = str_replace($this->tSearch, $this->tRepl, $text); + } + + return $text; + } + + /** + * Метод возвращает текст с bb-кодами построенный на основании дерева тегов + * + * @param int $id Указатель на текущий тег + * @return string + */ + public function getCode($id = 0) + { + if (isset($this->data[$id]['text'])) { + return $this->data[$id]['text']; + } + + $body = ''; + foreach ($this->data[$id]['children'] as $cid) { + $body .= $this->getCode($cid); + } + + if ($id === 0) { + return $body; + } + + $tag = $this->data[$id]['tag']; + $attrs = $this->data[$id]['attrs']; + + $def = ''; + $other = ''; + $count = count($attrs); + foreach ($attrs as $attr => $val) { + $quote = ''; + if ($count > 1 || strpbrk($val, ' \'"]')) { + $quote = '"'; + if (false !== strpos($val, '"') && false === strpos($val, '\'')) { + $quote = '\''; + } + } + if ($attr === 'Def') { + $def = '=' . $quote . $val . $quote; + } else { + $other .= ' ' . $attr . '=' . $quote . $val . $quote; + } + } + + return '[' . $tag . $def . $other . ']' . (isset($this->bbcodes[$tag]['single']) ? '' : $body . '[/' . $tag .']'); + } + + /** + * Метод ищет в текстовых узлах ссылки и создает на их месте узлы с bb-кодами url + * Для уменьшения нагрузки использовать при сохранении, а не при выводе + * + * @return Parserus $this + */ + public function detectUrls() + { + $pattern = '%\b(?<=\s|^) + (?>(?:ht|f)tps?://|www\.|ftp\.) + (?:[\p{L}\p{N}]+(?:[\p{L}\p{N}\-]*[\p{L}\p{N}])?\.)+ + \p{L}[\p{L}\p{N}\-]*[\p{L}\p{N}] + (?::\d{1,5})? + (?:/ + (?:[\p{L}\p{N};:@&=$_.+!*\'"(),\%/-]+)? + (?:\?[\p{L}\p{N};:@&=$_.+!*\'"(),\%-]+)? + (?:\#[\p{L}\p{N}-]+)? + )?%xu'; + + return $this->detect('url', $pattern, true); + } + + /** + * Метод ищет в текстовых узлах совпадения с $pattern и создает на их месте узлы с bb-кодами $tag + * + * @param string $tag Имя для создания bb-кода + * @param string $pattern Регулярное выражение для поиска + * @param bool $textOnly Флаг. true, если содержимое созданного тега текстовое + * @return Parserus $this + */ + protected function detect($tag, $pattern, $textOnly) + { + if (! isset($this->bbcodes[$tag])) { + return $this; + } + + $error = null; + if (null !== $this->blackList && in_array($tag, $this->blackList)) { + $error = 1; + } else if (null !== $this->whiteList && ! in_array($tag, $this->whiteList)) { + $error = 2; + } + + for ($id = $this->dataId; $id > 0; --$id) { + // не текстовый узел + if (! isset($this->data[$id]['text'])) { + continue; + } + + $pid = $this->data[$id]['parent']; + + // родитель может содержать только текст или не подходит по типу + if (isset($this->data[$pid]['text only']) || + ! isset($this->bbcodes[$tag]['parents'][$this->bbcodes[$this->data[$pid]['tag']]['type']]) + ) { + continue; + } + + if (! preg_match_all($pattern, $this->data[$id]['text'], $matches, PREG_OFFSET_CAPTURE)) { + continue; + } else if ($error) { + $this->errors[] = [$error, $tag]; + return $this; + } + + $idx = array_search($id, $this->data[$pid]['children']); + $arrEnd = array_slice($this->data[$pid]['children'], $idx + 1); + $this->data[$pid]['children'] = array_slice($this->data[$pid]['children'], 0, $idx); + + $pos = 0; + + foreach ($matches[0] as $match) { + $this->addTextNode(substr($this->data[$id]['text'], $pos, $match[1] - $pos), $pid); + + $new = $this->addTagNode($tag, $pid, [], $textOnly); + $this->addTextNode($match[0], $new); + + $pos = $match[1] + strlen($match[0]); + } + + $this->addTextNode($this->endStr($this->data[$id]['text'], $pos), $pid); + unset($this->data[$id]); + + $this->data[$pid]['children'] = array_merge($this->data[$pid]['children'], $arrEnd); + } + + return $this; + } + + /** + * Метод удаляет пустые теги из дерева + * + * @param string $mask Маска символов, которые не учитываются при определении пустоты текстовых узлов + * @param bool $flag Если true, то при пустом дереве оно не будет очищено, а останется без изменений, + * но будет оставлена ошибка, которая отобразится в getErrors() + * @return bool Если true, то дерево тегов пусто + */ + public function stripEmptyTags($mask = '', $flag = false) + { + if ($flag) { + $data = $this->data; + + if ($this->stripEmptyTags_($mask, 0)) { + $this->errors[] = [14]; + $this->data = $data; + return true; + } + return false; + + } else { + return $this->stripEmptyTags_($mask, 0); + } + } + + /** + * Метод рекурсивно удаляет пустые теги из дерева + * + * @param string $mask Маска символов, которые не учитываются при определении пустоты текстовых узлов + * @param int $id Указатель на текущий тег + * @return bool Если true, то тег/узел пустой + */ + protected function stripEmptyTags_($mask, $id) + { + // текстовый узел + if (isset($this->data[$id]['text'])) { + if (isset($mask[0])) { + return trim($this->data[$id]['text'], $mask) === ''; + } + return false; + } + + // одиночный тег + if (isset($this->bbcodes[$this->data[$id]['tag']]['single'])) { + return false; + } + + $res = true; + // перебор детей с удалением тегов + foreach ($this->data[$id]['children'] as $key => $cid) { + if ($this->stripEmptyTags_($mask, $cid)) { + if (isset($this->data[$cid]['tag'])) { + unset($this->data[$id]['children'][$key]); + unset($this->data[$cid]); + } + } else { + $res = false; + } + } + + if ($res) { + foreach ($this->data[$id]['children'] as $cid) { + unset($this->data[$cid]); + } + $this->data[$id]['children'] = []; + } + + return $res; + } + + /** + * Метод возвращает массив ошибок + * + * @param array $lang Массив строк шаблонов описания ошибок + * @return array + */ + public function getErrors(array $lang = []) + { + $defLang = [ + 1 => 'Тег [%1$s] находится в черном списке', + 2 => 'Тег [%1$s] отсутствует в белом списке', + 3 => 'Тег [%1$s] нельзя открыть внутри тега [%2$s]', + 4 => 'Не найден начальный тег для парного тега [/%1$s]', + 5 => 'Найден парный тег [/%1$s] для одиночного тега [%1$s]', + 6 => 'В теге [%1$s] отсутствуют атрибуты', + 7 => 'Тег [%1$s=...] не может содержать первичный атрибут', + 8 => 'Тег [%1$s ...] не может содержать вторичные атрибуты', + 9 => 'Атрибут \'%2$s\' тега [%1$s] не соответствует шаблону', + 10 => 'Тег [%1$s ...] содержит неизвестный вторичный атрибут \'%2$s\'', + 11 => 'Тело тега [%1$s] не соответствует шаблону', + 12 => 'Тег [%1$s] нельзя открыть внутри аналогичного тега', + 13 => 'В теге [%1$s] отсутствует обязательный атрибут \'%2$s\'', + 14 => 'Все теги пустые' + ]; + + $errors = []; + + foreach ($this->errors as $args) { + $err = array_shift($args); + + if (isset($lang[$err])) { + $text = $lang[$err]; + } else if (isset($defLang[$err])) { + $text = $defLang[$err]; + } else { + $text = 'Unknown error'; + } + + $errors[] = vsprintf($text, array_map([$this, 'e'], $args)); + } + + return $errors; + } + + /** + * Метод преобразует специальные символы в HTML-сущности + * + * @param string $text + * @return string + */ + public function e($text) + { + return htmlspecialchars($text, $this->eFlags, 'UTF-8'); + } + + /** + * Метод возвращает окончание строки + * + * @param string $str Текст + * @param int $pos Начальная позиция в байтах с которой идет возврат текста + * @return string + */ + protected function endStr($str, $pos) + { + $s = substr($str, $pos); + return false === $s ? '' : $s; + } +} diff --git a/vendor/miovisman/parserus/README.md b/vendor/miovisman/parserus/README.md new file mode 100644 index 00000000..eb5ab5ba --- /dev/null +++ b/vendor/miovisman/parserus/README.md @@ -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 '' . $body . ''; + } +])->addBBcode([ + 'tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, +])->parse("[i]Hello\n[b]World[/b]![/i]") +->getHTML(); + +#output: Hello
    World!
    +``` + +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. diff --git a/vendor/miovisman/parserus/composer.json b/vendor/miovisman/parserus/composer.json new file mode 100644 index 00000000..bbfb2f7e --- /dev/null +++ b/vendor/miovisman/parserus/composer.json @@ -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": ""} + } +} diff --git a/vendor/miovisman/parserus/examples/_first.php b/vendor/miovisman/parserus/examples/_first.php new file mode 100644 index 00000000..cd1b8981 --- /dev/null +++ b/vendor/miovisman/parserus/examples/_first.php @@ -0,0 +1,20 @@ +addBBCode([ + 'tag' => 'b', + 'handler' => function($body) { + return '' . $body . ''; + } +])->addBBcode([ + 'tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, +])->parse("[i]Hello\n[b]World[/b]![/i]") +->getHTML(); + +#output: Hello
    World!
    diff --git a/vendor/miovisman/parserus/examples/_second.php b/vendor/miovisman/parserus/examples/_second.php new file mode 100644 index 00000000..f7583d3d --- /dev/null +++ b/vendor/miovisman/parserus/examples/_second.php @@ -0,0 +1,20 @@ +addBBCode([ + 'tag' => 'b', + 'handler' => function($body) { + return '' . $body . ''; + } +])->addBBcode([ + 'tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, +])->parse("[i]Hello\n[b]World[/b]![/i]") +->getHTML(); + +#output: Hello
    World!
    diff --git a/vendor/miovisman/parserus/examples/_third.php b/vendor/miovisman/parserus/examples/_third.php new file mode 100644 index 00000000..bb6902f7 --- /dev/null +++ b/vendor/miovisman/parserus/examples/_third.php @@ -0,0 +1,20 @@ +addBBCode([ + 'tag' => 'b', + 'handler' => function($body) { + return '' . $body . ''; + } +])->addBBcode([ + 'tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, +])->parse("[i]\nHello\n[b]\nWorld!") +->getHTML(); + +#output: Hello
    World!
    diff --git a/vendor/miovisman/parserus/examples/attr.php b/vendor/miovisman/parserus/examples/attr.php new file mode 100644 index 00000000..f49f828c --- /dev/null +++ b/vendor/miovisman/parserus/examples/attr.php @@ -0,0 +1,50 @@ +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 '' . $attr . ':
    '; + }, +])->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: Added later 2 h 48 min 43 s:
    diff --git a/vendor/miovisman/parserus/examples/bbcodes_test.php b/vendor/miovisman/parserus/examples/bbcodes_test.php new file mode 100644 index 00000000..c86a43b7 --- /dev/null +++ b/vendor/miovisman/parserus/examples/bbcodes_test.php @@ -0,0 +1,588 @@ + 'ROOT', + 'type' => 'block', + 'handler' => function($body) { + $body = '

    ' . $body . '

    '; + + // Replace any breaks next to paragraphs so our replace below catches them + $body = preg_replace('%()(?:\s*?
    ){1,2}%', '$1', $body); + $body = preg_replace('%(?:
    \s*?){1,2}()%', '$1', $body); + + // Remove any empty paragraph tags (inserted via quotes/lists/code/etc) which should be stripped + $body = str_replace('

    ', '', $body); + + $body = preg_replace('%
    \s*?
    %', '

    ', $body); + + $body = str_replace('


    ', '

    ', $body); + $body = str_replace('

    ', '


    ', $body); + $body = str_replace('

    ', '

    ', $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 '

    ' . $body . '

    '; + }, + ], + ['tag' => 'b', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'em', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'u', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 's', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'del', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'ins', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'h', + 'type' => 'h', + 'handler' => function($body) { + return '

    ' . $body . '

    '; + }, + ], + ['tag' => 'hr', + 'type' => 'block', + 'single' => true, + 'handler' => function() { + return '


    '; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['tag' => 'right', + 'type' => 'block', + 'handler' => function($body) { + return '

    ' . $body . '

    '; + }, + ], + ['tag' => 'center', + 'type' => 'block', + 'handler' => function($body) { + return '

    ' . $body . '

    '; + }, + ], + ['tag' => 'justify', + 'type' => 'block', + 'handler' => function($body) { + return '

    ' . $body . '

    '; + }, + ], + ['tag' => 'mono', + 'handler' => function($body) { + return '' . $body . ''; + }, + ], + ['tag' => 'font', + 'self nesting' => true, + 'attrs' => [ + 'Def' => [ + 'format' => '%^[a-z\d, -]+$%i', + ], + ], + 'handler' => function($body, $attrs) { + return '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + } else { + return '' . $body . ''; + } + }, + ], + ['tag' => '*', + 'type' => 'block', + 'self nesting' => true, + 'parents' => ['list'], + 'auto' => true, + 'handler' => function($body) { + return '

  • ' . $body . '

  • '; + }, + ], + ['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 '

      ' . $body . '

    '; + case '1': + return '

      ' . $body . '

    '; + default: + return '

      ' . $body . '

    '; + } + }, + ], + ['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 '' . $attr . ':
    '; + }, + ], + ['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 = '

    ' . $attrs['Def'] . ' ' . $lang['wrote'] . '

    '; + } else { + $st = '

    '; + } + + return $st . $body . '

    '; + }, + ], + ['tag' => 'spoiler', + 'type' => 'block', + 'self nesting' => true, + 'attrs' => [ + 'Def' => true, + 'no attr' => true, + ], + 'handler' => function($body, $attrs, $parser) { + if (isset($attrs['Def'])) { + $st = '

    ' . $attrs['Def'] . '

    '; + } else { + $lang = $parser->attr('lang'); + $st = '

    ' . $lang['Hidden text'] . '

    '; + } + + return $st . $body . '

    '; + }, + ], + ['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 '' . $attrs['Def'] . ''; + } + } else { + // тег в теле сообщения + if ($parser->attr('showImg')) { + return '' . $attrs['Def'] . ''; + } + } + + $lang = $parser->attr('lang'); + + return '<' . $lang['Image link']. ' - ' . $attrs['Def'] . '>'; + }, + ], + ['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('%^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 '' . $body . ''; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], + ['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 '

    ' . $body . '

    '; + }, + ], +]; + +$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. ) 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] + + +'; + +$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 ' + + + + +' . +$parser->getHtml() . +' +'; diff --git a/vendor/miovisman/parserus/examples/detectUrls.php b/vendor/miovisman/parserus/examples/detectUrls.php new file mode 100644 index 00000000..a4c3a766 --- /dev/null +++ b/vendor/miovisman/parserus/examples/detectUrls.php @@ -0,0 +1,27 @@ +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! diff --git a/vendor/miovisman/parserus/examples/detectUrls2.php b/vendor/miovisman/parserus/examples/detectUrls2.php new file mode 100644 index 00000000..2ed193eb --- /dev/null +++ b/vendor/miovisman/parserus/examples/detectUrls2.php @@ -0,0 +1,63 @@ +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) diff --git a/vendor/miovisman/parserus/examples/e.php b/vendor/miovisman/parserus/examples/e.php new file mode 100644 index 00000000..10868fb0 --- /dev/null +++ b/vendor/miovisman/parserus/examples/e.php @@ -0,0 +1,9 @@ +e("<'abcde'>"); + +#output: <'abcde'> diff --git a/vendor/miovisman/parserus/examples/getCode.php b/vendor/miovisman/parserus/examples/getCode.php new file mode 100644 index 00000000..a3ffe44c --- /dev/null +++ b/vendor/miovisman/parserus/examples/getCode.php @@ -0,0 +1,126 @@ +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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], + ['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 '' . $body . ''; + }, + ], +])->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] +# diff --git a/vendor/miovisman/parserus/examples/getErrors.php b/vendor/miovisman/parserus/examples/getErrors.php new file mode 100644 index 00000000..6395d6bc --- /dev/null +++ b/vendor/miovisman/parserus/examples/getErrors.php @@ -0,0 +1,41 @@ +addBBCode([ + 'tag' => 'b', + 'handler' => function($body) { + return '' . $body . ''; + } +])->addBBcode([ + 'tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, +]); + +$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) diff --git a/vendor/miovisman/parserus/examples/smilies.php b/vendor/miovisman/parserus/examples/smilies.php new file mode 100644 index 00000000..98fcb492 --- /dev/null +++ b/vendor/miovisman/parserus/examples/smilies.php @@ -0,0 +1,34 @@ +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 '' . $attrs['Def'] . ''; + }, +])->setSmTpl('{alt}') + ->parse(":)\n;)") + ->detectSmilies() + ->getHTML(); + +#output: :)
    ;) diff --git a/vendor/miovisman/parserus/examples/stripEmptyTags.php b/vendor/miovisman/parserus/examples/stripEmptyTags.php new file mode 100644 index 00000000..47aed475 --- /dev/null +++ b/vendor/miovisman/parserus/examples/stripEmptyTags.php @@ -0,0 +1,61 @@ +addBBCode([ + 'tag' => 'b', + 'handler' => function($body) { + return '' . $body . ''; + } +])->addBBcode([ + 'tag' => 'i', + 'handler' => function($body) { + return '' . $body . ''; + }, +]); + +# №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