From 1ae61e6a23ded5fc4d814a75902a446fbc400e36 Mon Sep 17 00:00:00 2001 From: flashwave Date: Thu, 23 Aug 2018 20:13:37 +0200 Subject: [PATCH] Checkpoint! Perms work on the index. --- composer.json | 3 +- composer.lock | 110 +++++++- ...018_08_18_014408_add_forum_permissions.php | 31 +++ misuzu.php | 15 +- src/Application.php | 21 +- src/Forum/forum.php | 251 ++++++++++-------- src/Forum/perms.php | 45 ++-- 7 files changed, 326 insertions(+), 150 deletions(-) diff --git a/composer.json b/composer.json index e5e60d73..5c2a1056 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "swiftmailer/swiftmailer": "~6.0", "erusev/parsedown": "~1.6", "geoip2/geoip2": "~2.0", - "twig/extensions": "^1.5" + "twig/extensions": "^1.5", + "filp/whoops": "^2.2" }, "require-dev": { "phpunit/phpunit": "~6.0" diff --git a/composer.lock b/composer.lock index e6396d94..c55ef6e3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b859e162d3882d5c8dbbfbb02b1eb0b7", + "content-hash": "1e2dfc77fb54aea063a4063f00da713e", "packages": [ { "name": "composer/ca-bundle", @@ -219,6 +219,67 @@ ], "time": "2018-03-08T01:11:30+00:00" }, + { + "name": "filp/whoops", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/181c4502d8f34db7aed7bfe88d4f87875b8e947a", + "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0", + "psr/log": "^1.0.1" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "time": "2018-03-03T17:56:25+00:00" + }, { "name": "geoip2/geoip2", "version": "v2.9.0", @@ -428,6 +489,53 @@ ], "time": "2018-07-05T06:59:26+00:00" }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v6.1.2", diff --git a/database/2018_08_18_014408_add_forum_permissions.php b/database/2018_08_18_014408_add_forum_permissions.php index 328f7cf2..3a2e0f41 100644 --- a/database/2018_08_18_014408_add_forum_permissions.php +++ b/database/2018_08_18_014408_add_forum_permissions.php @@ -34,9 +34,40 @@ function migrate_up(PDO $conn): void ON DELETE CASCADE ) "); + + $conn->exec(' + CREATE VIEW `msz_forum_permissions_view` AS + WITH RECURSIVE permissions(user_id, role_id, forum_id, forum_perms_allow, forum_perms_deny) as ( + SELECT + pp.`user_id`, pp.`role_id`, + pc.`forum_id`, + IFNULL(pp.`forum_perms_allow`, 0), IFNULL(pp.`forum_perms_deny`, 0) + FROM `msz_forum_categories` as pc + LEFT JOIN `msz_forum_permissions` as pp + ON pp.`forum_id` = pc.`forum_id` + GROUP BY `user_id`, `role_id`, `forum_id` + UNION ALL + SELECT + permissions.`user_id`, permissions.`role_id`, + cc.`forum_id`, + IFNULL(cp.`forum_perms_allow`, 0) | permissions.`forum_perms_allow`, + IFNULL(cp.`forum_perms_deny`, 0) | permissions.`forum_perms_deny` + FROM `msz_forum_categories` as cc + LEFT JOIN `msz_forum_permissions` as cp + ON cp.`forum_id` = cc.`forum_id` + INNER JOIN permissions + ON cc.`forum_parent` = permissions.`forum_id` + ) + SELECT + `user_id`, `role_id`, `forum_id`, + (BIT_OR(`forum_perms_allow`) &~ BIT_OR(`forum_perms_deny`)) as `forum_perms` + FROM permissions + GROUP BY `user_id`, `role_id`, `forum_id` + '); } function migrate_down(PDO $conn): void { + $conn->exec('DROP VIEW `msz_forum_permissions_view`'); $conn->exec('DROP TABLE `msz_forum_permissions`'); } diff --git a/misuzu.php b/misuzu.php index 79fa2721..ca415a8b 100644 --- a/misuzu.php +++ b/misuzu.php @@ -3,7 +3,20 @@ namespace Misuzu; date_default_timezone_set('UTC'); +define('MSZ_DEBUG', file_exists(__DIR__ . '/vendor/phpunit/phpunit/composer.json')); + require_once __DIR__ . '/vendor/autoload.php'; + +if (MSZ_DEBUG) { + $errorHandler = new \Whoops\Run; + $errorHandler->pushHandler( + PHP_SAPI === 'cli' + ? new \Whoops\Handler\PlainTextHandler + : new \Whoops\Handler\PrettyPageHandler + ); + $errorHandler->register(); +} + require_once __DIR__ . '/src/audit_log.php'; require_once __DIR__ . '/src/changelog.php'; require_once __DIR__ . '/src/colour.php'; @@ -29,7 +42,7 @@ require_once __DIR__ . '/src/Users/validation.php'; $app = new Application( __DIR__ . '/config/config.ini', - IO\Directory::exists(__DIR__ . '/vendor/phpunit/phpunit') + MSZ_DEBUG ); $app->startDatabase(); diff --git a/src/Application.php b/src/Application.php index 55a5de45..5cf6018d 100644 --- a/src/Application.php +++ b/src/Application.php @@ -78,11 +78,14 @@ class Application extends ApplicationBase $this->debugMode = $debug; $this->configInstance = new ConfigManager($configFile); - ExceptionHandler::register( - $debug, - $this->configInstance->get('Exceptions', 'report_url', 'string', null), - $this->configInstance->get('Exceptions', 'hash_key', 'string', null) - ); + // only use this error handler in prod mode, dev uses Whoops now + if (!$debug) { + ExceptionHandler::register( + false, + $this->configInstance->get('Exceptions', 'report_url', 'string', null), + $this->configInstance->get('Exceptions', 'hash_key', 'string', null) + ); + } } public function getTimeSinceStart(): float @@ -105,14 +108,6 @@ class Application extends ApplicationBase return $this->configInstance; } - /** - * Shuts the application down. - */ - public function __destruct() - { - ExceptionHandler::unregister(); - } - /** * Gets whether we're in debug mode or not. * @return bool diff --git a/src/Forum/forum.php b/src/Forum/forum.php index a2720ac1..3cfb38d1 100644 --- a/src/Forum/forum.php +++ b/src/Forum/forum.php @@ -1,6 +1,8 @@ 0 ORDER BY f.`forum_order` "); - $getCategories->bindValue('perm_user_id_1', $userId); - $getCategories->bindValue('perm_user_id_2', $userId); + $getCategories->bindValue('perm_user_id_user', $userId); + $getCategories->bindValue('perm_user_id_role', $userId); $categories = $getCategories->execute() ? $getCategories->fetchAll(PDO::FETCH_ASSOC) : []; $categories = array_merge([MSZ_FORUM_ROOT_DATA], $categories); @@ -118,8 +121,8 @@ function forum_get_root_categories(int $userId): array AND `forum_type` != 1 AND {$forumPermSql} > 0 ", MSZ_FORUM_ROOT)); - $getRootForumCount->bindValue('perm_user_id_1', $userId); - $getRootForumCount->bindValue('perm_user_id_2', $userId); + $getRootForumCount->bindValue('perm_user_id_user', $userId); + $getRootForumCount->bindValue('perm_user_id_role', $userId); $getRootForumCount->execute(); $categories[0]['forum_children'] = (int)$getRootForumCount->fetchColumn(); @@ -133,22 +136,28 @@ function forum_get_breadcrumbs( array $indexLink = ['Forums' => '/forum/'] ): array { $breadcrumbs = []; - $getBreadcrumb = Database::prepare(' - SELECT `forum_id`, `forum_name`, `forum_parent` - FROM `msz_forum_categories` - WHERE `forum_id` = :forum_id + $getBreadcrumbs = Database::prepare(' + WITH RECURSIVE breadcrumbs(forum_id, forum_name, forum_parent) as ( + SELECT c.`forum_id`, c.`forum_name`, c.`forum_parent` + FROM `msz_forum_categories` as c + WHERE `forum_id` = :forum_id + UNION ALL + SELECT p.`forum_id`, p.`forum_name`, p.`forum_parent` + FROM `msz_forum_categories` as p + INNER JOIN breadcrumbs + ON p.`forum_id` = breadcrumbs.forum_parent + ) + SELECT * FROM breadcrumbs '); + $getBreadcrumbs->bindValue('forum_id', $forumId); + $breadcrumbsDb = $getBreadcrumbs->execute() ? $getBreadcrumbs->fetchAll(PDO::FETCH_ASSOC) : []; - while ($forumId > MSZ_FORUM_ROOT) { - $getBreadcrumb->bindValue('forum_id', $forumId); - $breadcrumb = $getBreadcrumb->execute() ? $getBreadcrumb->fetch() : []; - - if (!$breadcrumb) { - break; - } + if (!$breadcrumbsDb) { + return [$indexLink]; + } + foreach ($breadcrumbsDb as $breadcrumb) { $breadcrumbs[$breadcrumb['forum_name']] = sprintf($linkFormat, $breadcrumb['forum_id']); - $forumId = $breadcrumb['forum_parent']; } return array_reverse($breadcrumbs + $indexLink); @@ -156,132 +165,146 @@ function forum_get_breadcrumbs( function forum_increment_clicks(int $forumId): void { - $incrementLinkClicks = Database::prepare(' + $incrementLinkClicks = Database::prepare(sprintf(' UPDATE `msz_forum_categories` SET `forum_link_clicks` = `forum_link_clicks` + 1 WHERE `forum_id` = :forum_id - AND `forum_type` = ' . MSZ_FORUM_TYPE_LINK . ' + AND `forum_type` = %d AND `forum_link_clicks` IS NOT NULL - '); + ', MSZ_FORUM_TYPE_LINK)); $incrementLinkClicks->bindValue('forum_id', $forumId); $incrementLinkClicks->execute(); } -define('MSZ_FORUM_GET_CHILDREN_QUERY_SMALL', ' - SELECT - :user_id as `target_user_id`, - f.`forum_id`, f.`forum_name`, - ( +function forum_read_status_sql( + string $topic_id_param, + string $topic_bumped_param, + string $forum_id_param = 'f.`forum_id`', + string $user_param = '`target_user_id`' +): string { + return sprintf( + ' SELECT - `target_user_id` > 0 + %1$s > 0 AND - t.`topic_id` IS NOT NULL + %2$s IS NOT NULL AND - t.`topic_bumped` >= NOW() - INTERVAL 1 MONTH + %3$s >= NOW() - INTERVAL 1 MONTH AND ( SELECT COUNT(ti.`topic_id`) < ( SELECT COUNT(`topic_id`) FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` + WHERE `forum_id` = %4$s AND `topic_bumped` >= NOW() - INTERVAL 1 MONTH AND `topic_deleted` IS NULL ) FROM `msz_forum_topics_track` as tt RIGHT JOIN `msz_forum_topics` as ti ON ti.`topic_id` = tt.`topic_id` - WHERE ti.`forum_id` = f.`forum_id` - AND tt.`user_id` = `target_user_id` + WHERE ti.`forum_id` = %4$s + AND tt.`user_id` = %1$s AND `track_last_read` >= `topic_bumped` ) - ) as `forum_unread` - FROM `msz_forum_categories` as f - LEFT JOIN `msz_forum_topics` as t - ON t.`topic_id` = ( - SELECT `topic_id` - FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` - AND `topic_deleted` IS NULL - ORDER BY `topic_bumped` DESC - LIMIT 1 - ) - WHERE `forum_parent` = :parent_id - AND `forum_hidden` = false - ORDER BY f.`forum_order` -'); -define('MSZ_FORUM_GET_CHILDREN_QUERY_STANDARD', ' - SELECT - :user_id as `target_user_id`, - f.`forum_id`, f.`forum_name`, f.`forum_description`, f.`forum_type`, - f.`forum_link`, f.`forum_link_clicks`, f.`forum_archived`, - t.`topic_id` as `recent_topic_id`, p.`post_id` as `recent_post_id`, - t.`topic_title` as `recent_topic_title`, t.`topic_bumped` as `recent_topic_bumped`, - p.`post_created` as `recent_post_created`, - u.`user_id` as `recent_post_user_id`, - u.`username` as `recent_post_username`, - COALESCE(u.`user_colour`, r.`role_colour`) as `recent_post_user_colour`, - ( - SELECT COUNT(`topic_id`) - FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` - ) as `forum_topic_count`, - ( - SELECT COUNT(`post_id`) - FROM `msz_forum_posts` - WHERE `forum_id` = f.`forum_id` - ) as `forum_post_count`, - ( + ', + $user_param, + $topic_id_param, + $topic_bumped_param, + $forum_id_param + ); +} + +define( + 'MSZ_FORUM_GET_CHILDREN_QUERY_SMALL', + sprintf( + ' SELECT - `target_user_id` > 0 - AND - `recent_topic_id` IS NOT NULL - AND - `recent_topic_bumped` >= NOW() - INTERVAL 1 MONTH - AND ( - SELECT COUNT(ti.`topic_id`) < ( + :user_id as `target_user_id`, + f.`forum_id`, f.`forum_name`, + (%s) as `forum_unread` + FROM `msz_forum_categories` as f + LEFT JOIN `msz_forum_topics` as t + ON t.`topic_id` = ( + SELECT `topic_id` + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + AND `topic_deleted` IS NULL + ORDER BY `topic_bumped` DESC + LIMIT 1 + ) + WHERE `forum_parent` = :parent_id + AND `forum_hidden` = false + AND (%s & %d) > 0 + ORDER BY f.`forum_order` + ', + forum_read_status_sql('t.`topic_id`', 't.`topic_bumped`'), + forum_perms_get_user_sql('forum', 'f.`forum_id`'), + MSZ_FORUM_PERM_CAN_LIST_FORUM + ) +); + +define( + 'MSZ_FORUM_GET_CHILDREN_QUERY_STANDARD', + sprintf( + ' + SELECT + :user_id as `target_user_id`, + f.`forum_id`, f.`forum_name`, f.`forum_description`, f.`forum_type`, + f.`forum_link`, f.`forum_link_clicks`, f.`forum_archived`, + t.`topic_id` as `recent_topic_id`, p.`post_id` as `recent_post_id`, + t.`topic_title` as `recent_topic_title`, t.`topic_bumped` as `recent_topic_bumped`, + p.`post_created` as `recent_post_created`, + u.`user_id` as `recent_post_user_id`, + u.`username` as `recent_post_username`, + COALESCE(u.`user_colour`, r.`role_colour`) as `recent_post_user_colour`, + ( SELECT COUNT(`topic_id`) FROM `msz_forum_topics` WHERE `forum_id` = f.`forum_id` - AND `topic_bumped` >= NOW() - INTERVAL 1 MONTH - AND `topic_deleted` IS NULL - ) - FROM `msz_forum_topics_track` as tt - RIGHT JOIN `msz_forum_topics` as ti - ON ti.`topic_id` = tt.`topic_id` - WHERE ti.`forum_id` = f.`forum_id` - AND tt.`user_id` = `target_user_id` - AND `track_last_read` >= `topic_bumped` + ) as `forum_topic_count`, + ( + SELECT COUNT(`post_id`) + FROM `msz_forum_posts` + WHERE `forum_id` = f.`forum_id` + ) as `forum_post_count`, + (%s) as `forum_unread` + FROM `msz_forum_categories` as f + LEFT JOIN `msz_forum_topics` as t + ON t.`topic_id` = ( + SELECT `topic_id` + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + AND `topic_deleted` IS NULL + ORDER BY `topic_bumped` DESC + LIMIT 1 ) - ) as `forum_unread` - FROM `msz_forum_categories` as f - LEFT JOIN `msz_forum_topics` as t - ON t.`topic_id` = ( - SELECT `topic_id` - FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` - AND `topic_deleted` IS NULL - ORDER BY `topic_bumped` DESC - LIMIT 1 + LEFT JOIN `msz_forum_posts` as p + ON p.`post_id` = ( + SELECT `post_id` + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + ORDER BY `post_id` DESC + LIMIT 1 + ) + LEFT JOIN `msz_users` as u + ON u.`user_id` = p.`user_id` + LEFT JOIN `msz_roles` as r + ON r.`role_id` = u.`display_role` + WHERE f.`forum_parent` = :parent_id + AND f.`forum_hidden` = false + AND (%4$s & %5$d) > 0 + AND ( + (f.`forum_parent` = %2$d AND f.`forum_type` != %3$d) + OR f.`forum_parent` != %2$d + ) + ORDER BY f.`forum_order` + ', + forum_read_status_sql('`recent_topic_id`', '`recent_topic_bumped`'), + MSZ_FORUM_ROOT, + MSZ_FORUM_TYPE_CATEGORY, + forum_perms_get_user_sql('forum', 'f.`forum_id`'), + MSZ_FORUM_PERM_CAN_LIST_FORUM ) - LEFT JOIN `msz_forum_posts` as p - ON p.`post_id` = ( - SELECT `post_id` - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - ORDER BY `post_id` DESC - LIMIT 1 - ) - LEFT JOIN `msz_users` as u - ON u.`user_id` = p.`user_id` - LEFT JOIN `msz_roles` as r - ON r.`role_id` = u.`display_role` - WHERE f.`forum_parent` = :parent_id - AND f.`forum_hidden` = false - AND ( - (f.`forum_parent` = ' . MSZ_FORUM_ROOT . ' AND f.`forum_type` != ' . MSZ_FORUM_TYPE_CATEGORY . ') - OR f.`forum_parent` != ' . MSZ_FORUM_ROOT . ' - ) - ORDER BY f.`forum_order` -'); +); function forum_get_children(int $parentId, int $userId, bool $small = false): array { @@ -291,6 +314,8 @@ function forum_get_children(int $parentId, int $userId, bool $small = false): ar : MSZ_FORUM_GET_CHILDREN_QUERY_STANDARD ); $getListing->bindValue('user_id', $userId); + $getListing->bindValue('perm_user_id_user', $userId); + $getListing->bindValue('perm_user_id_role', $userId); $getListing->bindValue('parent_id', $parentId); return $getListing->execute() ? $getListing->fetchAll() : []; diff --git a/src/Forum/perms.php b/src/Forum/perms.php index 265d1fdb..4f88505f 100644 --- a/src/Forum/perms.php +++ b/src/Forum/perms.php @@ -33,30 +33,33 @@ function forum_perms_create(): int function forum_perms_get_user_sql( string $prefix, - string $forum = ':perm_forum_id', - string $user_for_user = ':perm_user_id_1', - string $user_for_role = ':perm_user_id_2' + string $forum_id_param = ':perm_forum_id', + string $user_id_user_param = ':perm_user_id_user', + string $user_id_role_param = ':perm_user_id_role' ): string { - return " - SELECT BIT_OR(`{$prefix}_perms_allow`) &~ BIT_OR(`{$prefix}_perms_deny`) - FROM `msz_forum_permissions` - WHERE ( - `forum_id` = {$forum} - OR `forum_id` IS NULL - ) - AND ( - (`user_id` IS NULL AND `role_id` IS NULL) - OR (`user_id` = {$user_for_user} AND `role_id` IS NULL) - OR ( - `user_id` IS NULL - AND `role_id` IN ( - SELECT `role_id` - FROM `msz_user_roles` - WHERE `user_id` = {$user_for_role} + return sprintf( + ' + SELECT BIT_OR(`%1$s_perms`) + FROM `msz_forum_permissions_view` + WHERE `forum_id` = %2$s + AND ( + (`user_id` IS NULL AND `role_id` IS NULL) + OR (`user_id` = %3$s AND `role_id` IS NULL) + OR ( + `user_id` IS NULL + AND `role_id` IN ( + SELECT `role_id` + FROM `msz_user_roles` + WHERE `user_id` = %4$s + ) ) ) - ) - "; + ', + $prefix, + $forum_id_param, + $user_id_user_param, + $user_id_role_param + ); } function forum_perms_get_user(string $prefix, int $forum, int $user): int