From fb96249e8243330e8260c809bbe9e305f2f9550d Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:09:06 +0100 Subject: [PATCH 01/12] Add files via upload --- admin/control_panel.php | 561 ++++++++++++++++++ admin/create_subcategory.php | 40 ++ admin/css/control_panel.css | 27 + admin/css/control_panel_categories.css | 53 ++ admin/css/control_panel_form.css | 11 + admin/css/control_panel_mod.css | 42 ++ admin/css/control_panel_themes.css | 74 +++ admin/css/control_panel_users.css | 84 +++ admin/css/setup-2.css | 28 + admin/css/setup_4.css | 72 +++ admin/css/setup_main.css | 149 +++++ admin/css/subcategory.css | 23 + admin/edit_subcategory.php | 29 + admin/form/control_panel.php | 418 +++++++++++++ admin/form/setup.php | 462 +++++++++++++++ admin/form/subcategory.php | 99 ++++ admin/includes/config.php | 21 + .../includes/control_panel_users_toolbar.php | 28 + admin/includes/file_upload.php | 52 ++ admin/includes/setup_inc.php | 26 + admin/includes/site_logo.php | 92 +++ admin/includes/subcategory_form.php | 15 + admin/includes/subcategory_init.php | 41 ++ admin/js/setup_4.js | 193 ++++++ admin/setup.php | 232 ++++++++ 25 files changed, 2872 insertions(+) create mode 100644 admin/control_panel.php create mode 100644 admin/create_subcategory.php create mode 100644 admin/css/control_panel.css create mode 100644 admin/css/control_panel_categories.css create mode 100644 admin/css/control_panel_form.css create mode 100644 admin/css/control_panel_mod.css create mode 100644 admin/css/control_panel_themes.css create mode 100644 admin/css/control_panel_users.css create mode 100644 admin/css/setup-2.css create mode 100644 admin/css/setup_4.css create mode 100644 admin/css/setup_main.css create mode 100644 admin/css/subcategory.css create mode 100644 admin/edit_subcategory.php create mode 100644 admin/form/control_panel.php create mode 100644 admin/form/setup.php create mode 100644 admin/form/subcategory.php create mode 100644 admin/includes/config.php create mode 100644 admin/includes/control_panel_users_toolbar.php create mode 100644 admin/includes/file_upload.php create mode 100644 admin/includes/setup_inc.php create mode 100644 admin/includes/site_logo.php create mode 100644 admin/includes/subcategory_form.php create mode 100644 admin/includes/subcategory_init.php create mode 100644 admin/js/setup_4.js create mode 100644 admin/setup.php diff --git a/admin/control_panel.php b/admin/control_panel.php new file mode 100644 index 0000000..3cb4a7c --- /dev/null +++ b/admin/control_panel.php @@ -0,0 +1,561 @@ +exists('control_panel', $key) ? $error->get_message('control_panel', $key) : ''; +} + +function get($key) +{ + return isset($_GET[$key]) ? strtolower(trim($_GET[$key])) : null; +} + +function post_clean($key) +{ + return isset($_POST[$key]) ? trim($_POST[$key]) : null; +} + +function get_sort_by() +{ + $sort_by = get('sort_by'); + if ($sort_by == null || !in_array($sort_by, array('az', 'za', 'time'))) + { + $sort_by = 'time'; + } + return $sort_by; +} + +if (isset($_GET['q']) && get('q') != '') +{ + $_POST['q'] = $_GET['q']; +} +?> +
+
+

Control Panel

+
+ $v) + { + $arr[] = $k.'='.urlencode($v); + } + $url .= '&'.implode('&', $arr); + } + return $url; + } + + $side_nav = array( + 'My Site' => array( + 'Change Site Name' => cp_link('change_site_name'), + 'Change Site Logo' => cp_link('change_site_logo'), + ), + 'Forum' => array( + 'Categories' => cp_link('categories'), + 'Moderators' => cp_link('moderators'), + 'Users' => cp_link('users'), + ), + 'Themes' => array( + 'Install Theme' => cp_link('install_theme'), + 'My Themes' => cp_link('my_themes'), + ), + ); + + $side_nav_active = ''; + switch ($current) { + case 'change_site_name': + $side_nav_active = 'Change Site Name'; + break; + case 'change_site_logo': + $side_nav_active = 'Change Site Logo'; + break; + case 'install_theme': + $side_nav_active = 'Install Theme'; + break; + case 'categories': + $side_nav_active = 'Categories'; + break; + case 'moderators': + $side_nav_active = 'Moderators'; + break; + case 'users': + $side_nav_active = 'Users'; + break; + case 'my_themes': + $side_nav_active = 'My Themes'; + break; + } + + $theme->side_nav($side_nav, $side_nav_active); + ?> +
+ +
+
+
+ +
+
Site name can only contain the following characters: a-z, A-Z, 0-9, -, _, :, (, ) or a whitespace.
+
+ +
+
+ +
+
+
+ +
+
+

Max resolution for site logo is $config[site_logo_max_width]x$config[site_logo_max_height] (pixels)"; ?>

+

Filesize should not exceed

+
+
+
+ +
+
exists('control_panel', 'categories')) + { + $msg = $error->get_message('control_panel', 'categories'); + $msg = str_replace(''; + } + ?>
+
+
+
Category
+
Description
+
+
+
+
Click on a row above to edit.
+
+

Add Category

+
+
+ +
+
+
+ +
+
+ + +
+
+ + +
+ get_user_meta($username); + if ($usermeta != null) + { + $form_action = ''; + $h4 = ''; + $submit_btn_value = ''; + if ($user_db->is_moderator($usermeta['user_id'])) + { + $form_action = 'edit_mod'; + $h4 = "Edit $username"; + $submit_btn_value = 'Edit Mod'; + } + else + { + $form_action = 'add_mod'; + $h4 = "Add $username to moderators"; + $submit_btn_value = 'Add Mod'; + } + ?> +
+ + +

+
+
+ '; + echo '
'; + echo '

'.$username.'

'; + echo '

Joined: '.date('d M, Y', $usermeta['joined']).'

'; + echo '

Last Online: 1 minute ago

'; + echo '
'; + ?> +
+
+

Select Categories

get_categories(CATEGORY_NO_PARENT); + echo '
    '; + $i = 1; + + $mod_cat_ids = $user_db->get_moderator_cat_ids($usermeta['user_id']); + foreach ($cats as $c) + { + echo '
  • '; + echo '
  • '; + } + echo '
'; + ?>
+
+
+ '.$username.' does not exists.
'; + } + } + else + { + $sort_by = get_sort_by(); + + include(ADMIN_DIR.'/includes/control_panel_users_toolbar.php'); + + // Show all moderators here. + $mod_rows = $user_db->get_moderators($sort_by, post_clean('q')); + if ($mod_rows != null) + { + $mods = array(); + foreach ($mod_rows as $m) + { + if (!array_key_exists($m->username, $mods)) + { + $mods[ $m->username ] = array( + 'joined' => $m->joined, + 'sections' => array($m->cat_id => $m->cat) + ); + } + else + { + $mods[ $m->username ]['sections'][ $m->cat_id ] = $m->cat; + } + } + + echo '
'; + foreach ($mods as $username => $mod_info) + { + $mod['username'] = $username; + $mod['picture_url'] = $config['http_host'].'/file.php?u='.$username; + $mod['profile_url'] = $config['http_host'].'/profile.php?u='.$username; + $mod['options'] = array( + 'Remove', + 'Edit moderator' + ); + $mod['info'] = array( + 'joined' => readable_time($mod_info['joined']), + 'sections' => array(), + ); + foreach ($mod_info['sections'] as $cat_id => $catname) + { + array_push($mod['info']['sections'], ''.$catname.''); + } + $mod['info']['sections'] = implode(',', $mod['info']['sections']); + + $theme->cp_user($mod); + } + echo '
'; + } + else + { + $q = post_clean('q'); + if ($q != null && $q != '') + { + echo '
There was no user found with the search criteria you entered.
'; + } + else + { + echo '
There are no moderators to show.
'; + } + } + } + } + elseif ($current == 'users') + { + $sort_by = get_sort_by(); + + include(ADMIN_DIR.'/includes/control_panel_users_toolbar.php'); + + if ($error->exists('control_panel', 'user')) + { + echo '
'; + echo $error->get_message('control_panel', 'user'); + echo '
'; + } + + $users = $user_db->get_all_users($sess_info['user_id'], $sort_by, post_clean('q')); + if ($users != null) + { + echo '
'; + foreach ($users as $u) + { + $u = (array) $u; + $u['profile_url'] = $config['http_host'].'/profile.php?u='.$u['username']; + $u['picture_url'] = $config['http_host'].'/file.php?u='.$u['username']; + + $u['info'] = array( + 'Joined' => readable_time($u['joined']), + 'Last Online' => readable_time($u['last_active']), + ); + + $u['options'] = array(); + $ban_url_ref = 'admin/control_panel.php?s=users'; + if (isset($_POST['q']) && post_clean('q') != '') + { + $ban_url_ref .= '&q='.post_clean('q'); + } + $ban_url = $config['http_host'].'/form/user_ban.php?u='.$u['username'].'&ref='.urlencode($ban_url_ref); + if ($u['is_banned']) + { + $u['options'][] = 'Unban User'; + } + else + { + $u['options'][] = 'Ban User'; + } + $u['options'][] = 'Delete User'; + if (!$user_db->is_moderator($u['user_id'])) + { + $u['options'][] = 'Add to Mod'; + } + + unset($u['user_id']); + + $theme->cp_user($u); + } + echo '
'; + } + else + { + $q = post_clean('q'); + if ($q != null && $q != '') + { + echo '
There was no user found with the search criteria you entered.
'; + } + else + { + echo '
There are no users.
'; + } + } + } + elseif ($current == 'install_theme') + { ?> +
+ exists('control_panel', 'site_theme')) + { + echo '
'.$error->get_message('control_panel', 'site_theme').'
'; + } + ?> +
+
+ +
+
+
+ +
+
+ exists('control_panel', 'site_theme')) + { + echo '
'; + echo $error->get_message('control_panel', 'site_theme'); + echo '
'; + } + + $themes = Theme_manager::get_themes(); + + $current_theme = $themes[$config['site_theme']]; + + echo '
'; + echo '
'.$current_theme->name.' (v'.$current_theme->version.') – Current Theme
'; + echo ''; + echo '
'; + + unset($themes[$config['site_theme']]); + + echo '
'; + echo '
Installed Themes
'; + echo '
'; + foreach ($themes as $thm => $meta) + { + $dir = $config['themes_path'].'/'.$thm; + + echo '
'; + + echo '
'.($meta !== null ? $meta->name : $thm).'
'; + + echo '
'; + if ($meta !== null && file_exists($meta->screenshot_path)) + { + echo '
'; + echo '
'; + echo '

(View screenshot fullsize)

'; + echo '
'; + } + else + { + echo '
No screenshot found
'; + } + + // Theme options + echo '
'; + echo '
    '; + + echo '
  • Version: '.$meta->version.'
  • '; + + $is_current_theme = $thm == $config['site_theme']; + if ( ! $is_current_theme) + { + echo '
  • Set as Site Theme
  • '; + } + + if (count($themes) > 1 && !$is_current_theme) + { + echo '
  • Delete Theme
  • '; + } + + echo '
'; + + echo '
'; + + echo '
'; + + echo '
'; + } + + echo '
'; + echo '
'; + echo '
'; + } + ?> +
+
+
+
+ +
+ + \ No newline at end of file diff --git a/admin/create_subcategory.php b/admin/create_subcategory.php new file mode 100644 index 0000000..74386d7 --- /dev/null +++ b/admin/create_subcategory.php @@ -0,0 +1,40 @@ + +
+
+ +

Create Subcategory

+ field_value_exists('subcat', 'name')) + { + $subcat_name = $error->get_field_value('subcat', 'name'); + } + + if ($error->field_value_exists('subcat', 'description')) + { + $subcat_description = $error->get_field_value('subcat', 'description'); + } + + include('includes/subcategory_form.php'); + ?> +
+ +
+
+ \ No newline at end of file diff --git a/admin/css/control_panel.css b/admin/css/control_panel.css new file mode 100644 index 0000000..8093726 --- /dev/null +++ b/admin/css/control_panel.css @@ -0,0 +1,27 @@ +#left #inner { + margin-top: 10px; +} + +#left #inner #side_nav, +#left #inner #main_content { + float: left; +} + +#left #inner #main_content { + width: 570px; +} + +#left #inner form .textfield, +#left #inner form .file_field { + margin-top: 3px; +} + +#left #inner form > div.note { + font-size: 12px; + line-height: 18px; +} + +#left #inner form .checkbox { + position: relative; + top: 2px; +} diff --git a/admin/css/control_panel_categories.css b/admin/css/control_panel_categories.css new file mode 100644 index 0000000..f392b3d --- /dev/null +++ b/admin/css/control_panel_categories.css @@ -0,0 +1,53 @@ +#table_wrapper { + width: 100%; +} + +#category_table .col { + float: left; +} + +#category_table .col1 { + width: 150px; + padding: 0 6px; +} + +#category_table .col2 { + width: 340px; + text-overflow: ellipsis; +} + +#category_table #theader, +#category_table .row { + padding: 6px 0; + line-height: 150%; + cursor: default; +} + +#category_table #theader { + font-weight: bold; +} + +#category_table #body { + height: 180px; + overflow-y: auto; + font-size: 12px; +} + +#category_table .row { + position: relative; +} + +#category_table .row .remove { + color: #e20; + font-size: 11px; + position: absolute; + right: 6px; +} + +#category_table .row .remove:hover { + text-decoration: underline; +} + +#cp_form #finish_btn { + float: right; +} diff --git a/admin/css/control_panel_form.css b/admin/css/control_panel_form.css new file mode 100644 index 0000000..1df2491 --- /dev/null +++ b/admin/css/control_panel_form.css @@ -0,0 +1,11 @@ +#cp_form > div { + margin-bottom: 10px; +} + +#cp_form > div .textfield { + width: 100%; +} + +#cp_form > div#error_div { + margin-bottom: 0; +} diff --git a/admin/css/control_panel_mod.css b/admin/css/control_panel_mod.css new file mode 100644 index 0000000..6117018 --- /dev/null +++ b/admin/css/control_panel_mod.css @@ -0,0 +1,42 @@ +#user_info img, +#user_info #beside_img { + float: left; +} + +#user_info img { + width: 70px; + height: 70px; +} + +#user_info #beside_img { + margin-left: 7px; +} + +#user_info #beside_img > div { + margin-top: 5px; +} + +#user_info #beside_img b { + font-size: 12px; +} + +#user_info #beside_img #username { + font-weight: bold; +} + +#categories ul { + margin-top: 1px; +} + +#categories ul li { + margin-top: 2px;; +} + +#categories ul li input { + cursor: pointer; +} + +#categories ul li label { + position: relative; + top: -1px; +} diff --git a/admin/css/control_panel_themes.css b/admin/css/control_panel_themes.css new file mode 100644 index 0000000..0e8289d --- /dev/null +++ b/admin/css/control_panel_themes.css @@ -0,0 +1,74 @@ +#theme_error { + margin-bottom: 10px; +} + +#themes #grid { + margin-top: 7px; +} + +#current_theme #current_theme_span { + font-size: 12px; +} + +#current_theme img { + width: 300px; + height: 195px; + margin-top: 5px; +} + +#themes { + margin-top: 30px; +} + +#themes #grid .theme { + width: 275px; + float: left; + margin-right: 20px; + margin-bottom: 30px; +} + +#themes #grid .theme:nth-child(even) { + margin-right: 0; +} + +#themes #grid .theme .screenshot, +#themes #grid .theme .no_screenshot, +#themes #grid .theme .theme_right { + float: left; +} + +#themes #grid .theme .screenshot img, +#themes #grid .theme .no_screenshot { + width: 150px; + height: 115px; +} + +#themes #grid .theme .meta { + margin-top: 7px; +} + +#themes #grid .theme .no_screenshot { + text-align: center; + line-height: 115px; + border: 1px solid #dfdfdf; + font-size: 11px; +} + +#themes #grid .theme .screenshot .view_full { + font-size: 11px; + margin-top: 3px; +} + +#themes #grid .theme .theme_right { + margin-left: 7px; +} + +#themes #grid .theme .theme_right .options li { + margin-bottom: 7px; + font-size: 12px; +} + +#themes #grid .theme .name { + font-weight: bold; + font-size: 13px; +} diff --git a/admin/css/control_panel_users.css b/admin/css/control_panel_users.css new file mode 100644 index 0000000..59b64d1 --- /dev/null +++ b/admin/css/control_panel_users.css @@ -0,0 +1,84 @@ +#toolbar { + height: 25px; + line-height: 25px; +} + +#user_search_form { + float: left; + position: relative; + padding: 0; + height: 25px; +} + +#user_search_form .textfield { + position: relative; + top: -3px; + height: 25px; + padding-right: 25px; + margin: 0; +} + +#user_search_form .search_btn { + position: absolute; + right: 0; + top: 0; + width: 25px; + height: 25px; + background: url('../../images/button-search.png') center center no-repeat; + border: none; + outline: none; + cursor: pointer; +} + +#sort { + float: right; + font-size: 12px; +} + +#sort .active { + font-weight: bold; + color: #454545; +} + +#error { + margin-top: 10px; +} + +#users .user { + margin-top: 10px; + border: 1px solid #efefef; + padding: 5px; +} + +#users .user img, +#users .user div.beside_img { + float: left; +} + +#users .user img { + width: 48px; + height: 48px; +} + +#users .user div.beside_img { + margin-left: 7px; +} + +#users .user div.beside_img span, +#users .user div.beside_img a.username { + font-weight: bold; +} + +#users .user div.beside_img span { + font-size: 12px; +} + +#users .user div.beside_img > div { + margin-top: 3px; +} + +#users .user .options { + margin-top: 7px; + font-size: 12px; + color: #ccc; +} diff --git a/admin/css/setup-2.css b/admin/css/setup-2.css new file mode 100644 index 0000000..8226222 --- /dev/null +++ b/admin/css/setup-2.css @@ -0,0 +1,28 @@ +#themes { + margin-top: 5px; +} + +#themes label { + position: relative; + top: -2px; + left: 5px; + color: #575757; +} + +#themes ul, +#themes img { + float: left; +} + +#themes ul { + width: 200px; +} + +#themes ul li { + margin-bottom: 4px; +} + +#themes img { + width: 340px; + height: 225px; +} diff --git a/admin/css/setup_4.css b/admin/css/setup_4.css new file mode 100644 index 0000000..af67f20 --- /dev/null +++ b/admin/css/setup_4.css @@ -0,0 +1,72 @@ +#category_table { + background: #FAFDFC; + border: 1px solid #E9F3F0; + border-radius: 2px; +} + +#category_table .col { + float: left; + padding: 0 6px; + box-sizing: border-box; + height: 30px; + overflow: hidden; +} + +#category_table #body { + height: 130px; + overflow-y: auto; +} + +#category_table #theader { + border-bottom: 1px solid #E5EBE9; + color: #aaa; + font-size: 11px; + font-weight: bold; + line-height: 30px; + background: -webkit-linear-gradient(top, #FAFDFC, #F8FCFB); + background: -moz-linear-gradient(top, #FAFDFC, #F8FCFB); + background: -ms-linear-gradient(top, #FAFDFC, #F8FCFB); + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +#category_table .col1 { + width: 125px; + border-right: 1px solid #EEF5F3; +} + +#category_table #body .row { + font-size: 12px; + border-bottom: 1px solid #EEF5F3; + position: relative; + line-height: 30px; +} + +#category_table #body .row .col1 { + color: #A0ABA8; +} + +#category_table #body .row:hover { + background: #eef5f3; +} + +#category_table #body .remove { + color: #e20; + font-size: 11px; + position: absolute; + right: 6px; +} + +#category_table #body .remove:hover { + text-decoration: underline; +} + +#table_wrapper #info { + font-size: 11px; + color: #647583; + margin-top: 3px; +} + +#finish_btn { + float: right; +} diff --git a/admin/css/setup_main.css b/admin/css/setup_main.css new file mode 100644 index 0000000..3108771 --- /dev/null +++ b/admin/css/setup_main.css @@ -0,0 +1,149 @@ +html { + padding-bottom: 30px; +} + +body { + font-family: Arial; + font-size: 14px; + background: #E9F4F1; +} + +body, form, h1, ul, li, input , p{ + margin: 0; + padding: 0; +} + +h1 { + text-align: center; + font-style: italic; +} + +.error { + font-size: 12px; + font-weight: normal; + color: #e30; +} + +span.error { + margin-left: 10px; +} + +body > div { + width: 600px; + margin: 50px auto 0 auto; + background: #F4FBF9; + border: 1px solid #E0EDE9; + border-radius: 5px; +} + +ul, li { + list-style-type: none; +} + +a { + text-decoration: none; +} + +#header { + padding: 10px 30px 5px 30px; + border-bottom: 1px solid #E9F2EF; + background: #FAFDFC; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +form { + padding: 20px 30px 30px 30px; +} + +form > div { + margin-bottom: 15px; +} + +form > div:last-child { + margin-bottom: 0; +} + +form > div > label { + font-size: 12px; + font-weight: bold; + color: #444; +} + +form > div#instruction { + color: #9BAFAA; +} + +form > div#instruction .error { + margin-top: 5px; +} + +form label { + cursor: pointer; +} + +#steps { + margin-top: 30px; + font-size: 11px; +} + +#steps span { + cursor: default; +} + +#steps span.sep { + color: #CADFD9; + margin: 0 3px; + position: relative; + top: -1px; +} + +#steps span.step { + color: #999999; +} + +#steps span.step.active { + color: #333333; + font-weight: bold; +} + +.textfield { + width: 100%; + box-sizing: border-box; + padding: 7px; + font-size: 14px; + outline: none; + border: 1px solid #E5EBE9; + border-radius: 2px; + background: #FAFDFC; + margin-top: 5px; +} + +form .textfield:focus { + border-color: #F5D894; + background-color: #FFFFFF; +} + +form input[type="submit"] { + padding: 8px 17px; + font-size: 14px; + background: #6EC1AA; + color: #ffffff; + border: 1px solid #5EAD98; + border-radius: 2px; + box-shadow: inset 0 1px 0 #9DE2CF; + outline: none; + cursor: pointer; +} + +form input[type="submit"]:hover { + background: #68BAA3; +} + +form input[type="submit"]:active { + background: #5FAE98; +} + +.clearfix { + clear: both; +} diff --git a/admin/css/subcategory.css b/admin/css/subcategory.css new file mode 100644 index 0000000..ef9507c --- /dev/null +++ b/admin/css/subcategory.css @@ -0,0 +1,23 @@ +h3 { + margin-top: 5px; +} + +#subcat_form { + margin-top: 10px; +} + +#subcat_form > div { + margin-top: 10px; +} + +#subcat_form > div .textfield { + width: 100%; + margin-top: 3px; +} + +#footer { + position: absolute; + bottom: 0; + box-sizing: border-box; + width: 100%; +} diff --git a/admin/edit_subcategory.php b/admin/edit_subcategory.php new file mode 100644 index 0000000..b8610d5 --- /dev/null +++ b/admin/edit_subcategory.php @@ -0,0 +1,29 @@ + +
+
+ +

Edit:

+ +
+ +
+ \ No newline at end of file diff --git a/admin/form/control_panel.php b/admin/form/control_panel.php new file mode 100644 index 0000000..d9a4d6a --- /dev/null +++ b/admin/form/control_panel.php @@ -0,0 +1,418 @@ +add('control_panel', 'site_name', 'Cannot be empty'); + } + elseif (strlen($sitename) > 59) + { + $error->add('control_panel', 'site_name', 'Sitename cannot be more than 59 characters.'); + } + elseif (preg_match('~[^A-Za-z0-9_\-\(\):\\\' ]~', $sitename)) + { + $error->add('control_panel', 'site_name', 'Sitename can only contain the following characters: A-Z, a-z, 0-9, _, -, :, ', (, ) or a space.'); + } + else + { + modify_config_setting(CONFIG_DIR.'/config.php', 'site_name', str_replace('\'', "'.APOSTROPHE.'", $sitename)); + } + } + else + { + $error->add('control_panel', 'site_name', 'Please enter your sitename'); + } + break; + case 'change_site_logo': + if (isset($_FILES['logo'])) + { + require(ADMIN_DIR . '/includes/file_upload.php'); + require(ADMIN_DIR . '/includes/site_logo.php'); + + $rules = array( + 'extensions' => array('.jpg', '.jpeg', '.png', '.gif'), + 'max_upload_size' => $config['site_logo_max_filesize'], + ); + + if (validate_uploaded_file($_FILES['logo'], $rules, $error_str, 'logo')) + { + $filename = random_logo_filename().'.'.pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION); + $logo_path = IMAGES_DIR.'/'.$filename; + + $logo = load_logo($_FILES['logo']['tmp_name'], $_FILES['logo']['type']); + if ($logo != null) + { + resize_logo($logo, $_FILES['logo']['type'], $logo_path); + + // Delete previous logo + $previous_logo_path = IMAGES_DIR.'/'.basename($config['site_logo_url']); + if (file_exists($previous_logo_path)) + { + unlink($previous_logo_path); + } + + modify_config_setting(CONFIG_DIR.'/config.php', 'site_logo_url', 'IMAGES_URL.\'/'.$filename.'\''); + } + } + else + { + $error->add('control_panel', 'site_logo', $error_str); + } + } + else + { + $error->add('control_panel', 'site_logo', 'Please select a logo'); + } + + break; + case 'categories': + if (isset($_POST['cats'])) + { + $cats = @json_decode($_POST['cats']); + if ($cats != null) + { + if (empty($cats)) + { + $error->add('control_panel', 'categories', 'Please add a category'); + } + else + { + $cat_ids = array(); + + foreach ($cats as $c) + { + if ( ! isset($c->name) || ! isset($c->description)) + { + continue; + } + + $c->name = trim($c->name); + $c->description = trim($c->description); + + if ($c->name == '' || $c->description == '') + { + continue; + } + + if (isset($c->id)) + { + // Edit category. + $cat->edit_category($c->id, $c->name, $c->description); + + $cat_ids[] = $c->id; + } + else + { + $cat_id = 0; + + // If category name exists, edit the category to avoid creating + // multiple category with the same name. + $meta = $cat->get_cat_by_name($c->name); + if ($meta != null) + { + // Edit category. + $cat_id = $cat->edit_category($meta['id'], $c->name, $c->description); + } + else + { + // Add category. + $cat_id = $cat->add_category(CATEGORY_NO_PARENT, $c->name, $c->description); + } + + $cat_ids[] = $cat_id; + } + } + + if ( ! empty($cat_ids)) + { + // Delete all cats who's id is not found in $cat_ids + $cat->delete_all_except($cat_ids, CATEGORY_NO_PARENT); + } + } + } + else + { + $error->add('control_panel', 'categories', 'Please add a category.'); + } + } + else + { + $error->add('control_panel', 'categories', 'Please add a category.'); + } + break; + case 'install_theme': + if (isset($_FILES['theme'])) + { + $rules = array( + 'max_upload_size' => $config['site_theme_max_filesize'], + 'extensions' => '.zip', + ); + if (validate_uploaded_file($_FILES['theme'], $rules, $error_msg, 'theme')) + { + if (!Theme_manager::install_theme($_FILES['theme']['tmp_name'], $_FILES['theme']['name'], $install_error)) + { + $error->add('control_panel', 'site_theme', "Unable to install theme: $install_error"); + } + else + { + if (isset($_POST['use'])) + { + $theme_name = explode('.', $_FILES['theme']['name'])[0]; + modify_config_setting(CONFIG_DIR.'/config.php', 'site_theme', $theme_name); + } + } + } + else + { + $error->add('control_panel', 'site_theme', $error_msg); + } + } + else + { + $error->add('control_panel', 'site_theme', 'Please select a theme.'); + } + + break; + case 'set_theme': + $theme_name = get_theme_name(); + if ($theme_name != null) + { + $themes = Theme_manager::get_themes(); + if (in_array($theme_name, array_keys($themes))) + { + modify_config_setting(CONFIG_DIR.'/config.php', 'site_theme', $theme_name); + } + else + { + $error->add('control_panel', 'site_theme', 'Unable to set theme: Theme not found'); + } + } + break; + case 'delete_theme': + $theme_name = get_theme_name(); + if ($theme_name != null) + { + $themes = Theme_manager::get_themes(); + if (in_array($theme_name, array_keys($themes))) + { + // Users can't delete their current theme. + if ($theme_name !== $config['site_theme']) + { + Theme_manager::delete_theme($theme_name); + } + else + { + $error->add('control_panel', 'site_theme', 'Unable to delete theme: Theme currently in use'); + } + } + else + { + $error->add('control_panel', 'site_theme', 'Unable to delete theme: Theme not found.'); + } + } + else + { + $error->add('control_panel', 'site_theme', 'Unable to delete theme: Theme not found.'); + } + + break; + case 'add_mod': + case 'edit_mod': + $forum_cats = array(); + foreach ($cat->get_categories(CATEGORY_NO_PARENT) as $c) + { + array_push($forum_cats, $c['cat_id']); + } + + $username = null; + $mod_cats = array(); + foreach (array_keys($_POST) as $k) + { + $val = post_clean($k); + if ($k == 'user') + { + $username = $val; + } + elseif (preg_match('~^cat\d+$~', $k) && preg_match('/^\d+$/', $val)) + { + if (!in_array($val, $forum_cats) || in_array($val, $mod_cats)) + { + continue; + } + $mod_cats[] = $_POST[$k]; + } + } + + if ($username != null && ($usermeta = $user_db->get_user_meta($username)) != null) + { + if (!empty($mod_cats)) + { + $user_db->remove_moderator($usermeta['user_id']); + + foreach ($mod_cats as $cat_id) + { + $user_db->add_moderator($usermeta['user_id'], $cat_id); + } + } + else + { + $error->add('control_panel', 'mod', 'Please choose a category for the moderator.'); + } + } + + break; + case 'remove_mod': + if (isset($_GET['user'])) + { + $usermeta = $user_db->get_user_meta(get_clean('user')); + if ($usermeta != null) + { + $user_db->remove_moderator($usermeta['user_id']); + } + else + { + $error->add('control_panel', 'mod', '\''.get_clean('user').'\' does not exists.'); + } + } + break; + case 'rm_user': + if (isset($_GET['user'])) + { + $usermeta = $user_db->get_user_meta(get_clean('user')); + if ($usermeta != null) + { + require $config['database_path'].'/threads_database.php'; + require $config['database_path'].'/threads_replies_database.php'; + + require( $config['__'] . '/file.php' ); + + // Delete all attachments for threads user has created. + $thread_db = new Threads_database(); + $threads = $thread_db->get_threads_by_user_id($usermeta['user_id'], 0, -1); + foreach ($threads as $t) + { + $path = $config['attachment_path'].'/thread'.$t->thread_id; + if (file_exists($path)) + { + delete_folder($path); + } + } + + // Delete all attachments for user's replies to threads. + $reply_db = new Threads_replies_database(); + $replies = $reply_db->get_replies_by_user_id($usermeta['user_id']); + foreach ($replies as $r) + { + $path = $config['attachment_path'].'/thread'.$r->thread_id.'/reply'.$r->reply_id; + if (file_exists($path)) + { + delete_folder($path); + } + } + + // Delete user's profile picture. + $picture_path = $usermeta['picture_filename']; + if ($picture_path != null && file_exists("$config[user_picture_path]/$picture_path")) + { + unlink("$config[user_picture_path]/$picture_path"); + } + + $user_db->remove_user($usermeta['user_id']); + } + else + { + $error->add('control_panel', 'user', 'User \''.get_clean('user').'\' does not exists.'); + } + } + break; + } + + $redirect_url = "$config[http_host]/admin/control_panel.php?s="; + if (in_array($_GET['a'], array('set_theme', 'delete_theme')) + || ($_GET['a'] == 'install_theme' && ! $error->exists('control_panel', 'site_theme'))) + { + $redirect_url .= 'my_themes'; + } + elseif (in_array($_GET['a'], array('add_mod', 'edit_mod', 'remove_mod'))) + { + $redirect_url .= 'moderators'; + if (isset($_POST['user']) && ($_GET['a'] == 'add_mod' || $_GET['a'] == 'edit_mod')) + { + $redirect_url .= '&' . explode('_', $_GET['a'])[0] . '=' . post_clean('user'); + } + } + elseif ($_GET['a'] == 'rm_user') + { + $redirect_url .= 'users'; + } + else + { + $redirect_url .= $_GET['a']; + } + + if ($error->exists_group('control_panel')) + { + if ($_GET['a'] == 'change_site_name' && isset($_POST['sitename'])) + { + $error->add_field_value('control_panel', 'site_name', trim($_POST['sitename'])); + } + $redirect_url .= '&e_k='.$error->save(); + } + header("Location: $redirect_url"); +} +else +{ + header("Location: $config[http_host]/admin/control_panel.php"); + die(); +} + +function get_theme_name() +{ + $theme_name = null; + if (isset($GLOBALS['_GET']['th'])) + { + $theme_name = strtolower(trim($GLOBALS['_GET']['th'])); + } + return $theme_name; +} diff --git a/admin/form/setup.php b/admin/form/setup.php new file mode 100644 index 0000000..e6b14a3 --- /dev/null +++ b/admin/form/setup.php @@ -0,0 +1,462 @@ + htmlspecialchars($host), + 'user' => htmlspecialchars($user), + 'pass' => htmlspecialchars($pass), + 'name' => htmlspecialchars($name), + ); + } + } + else + { + $setup['error'] = null; // Add 'error', to redirect back to same page. + } + } + elseif (CURRENT_STEP == 2) // Admin account creation. + { + if (isset($_POST['username']) && isset($_POST['pass']) && isset($_POST['re_pass']) && isset($_POST['email'])) + { + $username = trim($_POST['username']); + $pass = trim($_POST['pass']); + $re_pass = trim($_POST['re_pass']); + $email = trim($_POST['email']); + + // Validate username + if ($username == '') + { + $setup['error']['user'] = 'Field is required'; + } + elseif (strlen($username) < 2 || strlen($username) > 12) + { + $setup['error']['user'] = 'Must be between 2 - 12 characters long.'; + } + elseif ( ! preg_match('/^[a-zA-Z][a-zA-Z_0-9]+$/', $username)) + { + $setup['error']['user'] = 'Must begin with a letter, followed by either a letter, a number or _'; + } + + // Validate password + if ($pass == '') + { + $setup['error']['pass'] = 'Field is required.'; + } + elseif (strlen($pass) < 8) + { + $setup['error']['pass'] = 'Must be at least 8 characters.'; + } + elseif (strtolower($pass) == strtolower($username)) + { + $setup['error']['pass'] = 'Must be different from your username.'; + } + + // Validate re-entered password + if ($re_pass == '') + { + $setup['error']['re_pass'] = 'Field is required'; + } + elseif ($re_pass != $pass) + { + $setup['error']['re_pass'] = 'Does not match password you provided above'; + } + + // Validate email. + if ($email == '') + { + $setup['error']['email'] = 'Field is required'; + } + elseif ( ! filter_var($email, FILTER_VALIDATE_EMAIL)) + { + $setup['error']['email'] = 'Email address is not valid'; + } + + if (isset($setup['error'])) + { + $setup['form_data'] = array( + 'user' => htmlspecialchars($username), + 'email' => htmlspecialchars($email), + ); + } + else + { + // Hash password. + require($config['includes_path'] . '/password.php'); + $salt = generate_user_salt(); + $pass = hash_password($salt, $pass); + + // Add to database. + require($config['database_path'] . '/user_database.php'); + $user_db = new User_database(); + $user_db->add_admin($username, $email, $salt, $pass); + } + } + else + { + $setup['error'] = null; // Add 'error' to redirect user to the same page. + } + } + elseif (CURRENT_STEP == 3) + { + if (isset($_POST['site_name']) && isset($_FILES['site_logo']) && isset($_POST['site_theme'])) + { + $name = trim($_POST['site_name']); + $logo = $_FILES['site_logo']; + $theme = trim(strtolower($_POST['site_theme'])); + + // Validate site name. + if ($name == '') + { + $setup['error']['site_name'] = 'Field is required.'; + } + elseif (strlen($name) > 59) + { + $setup['error']['site_name'] = 'Must not exceed 59 characters.'; + } + elseif (preg_match('~[^A-Za-z0-9_\-\(\):\\\' ]~', $name)) + { + $setup['error']['site_name'] = 'Can only contain the following characters: A-Z, a-z, 0-9, _, -, :, ', (, ) or a space.'; + } + + // Validate site_logo + require(ADMIN_DIR . '/includes/file_upload.php'); + $rules = array( + 'extensions' => array('.jpg', '.jpeg', '.png', '.gif'), + 'max_upload_size' => $config['site_logo_max_filesize'], + ); + if ( ! validate_uploaded_file($_FILES['site_logo'], $rules, $logo_error, 'logo')) + { + $setup['error']['site_logo'] = $logo_error; + } + + // Validate theme + require($config['__'] . '/theme_manager.php'); + $themes = Theme_manager::get_themes(); + if ( ! array_key_exists($theme, $themes)) + { + $setup['error']['site_theme'] = 'Please select your site theme.'; + } + + if (isset($setup['error'])) + { + $setup['form_data'] = array( + 'site_name' => htmlspecialchars($name), + 'site_theme' => htmlspecialchars($theme), + ); + } + else + { + // Set site name. + modify_config_setting(CONFIG_TEMPLATE_PATH, 'site_name', str_replace('\'', "'.APOSTROPHE.'", $name)); + + // Set site banner. + require(ADMIN_DIR . '/includes/site_logo.php'); + $filename = random_logo_filename().'.'.pathinfo($logo['name'], PATHINFO_EXTENSION); + $logo_path = IMAGES_DIR.'/'.$filename; + + $logo_img = load_logo($logo['tmp_name'], $logo['type']); + if ($logo_img != null) + { + resize_logo($logo_img, $logo['type'], $logo_path); + modify_config_setting(CONFIG_TEMPLATE_PATH, 'site_logo_url', 'IMAGES_URL.\'/'.$filename.'\''); + } + + // Set site theme. + modify_config_setting(CONFIG_TEMPLATE_PATH, 'site_theme', $theme); + } + } + else + { + $setup['error'] = null; // Add 'error' to redirect user to the same page. + } + } + elseif (CURRENT_STEP == 4) + { + if (isset($_POST['cats'])) + { + $cats = @json_decode($_POST['cats']); + if ($cats != null) + { + if (empty($cats)) + { + $setup['error']['category'] = 'Please add a category'; + } + else + { + // Create the main categories. + require($config['database_path'] . '/thread_category.php'); + $cats_db = new Thread_category(); + + foreach ($cats as $c) + { + if ( ! isset($c->name) || ! isset($c->description)) + { + continue; + } + + $c->name = trim($c->name); + $c->description = trim($c->description); + + // Skip if 'name' and 'description' are empty. + if ($c->name != '' && $c->description != '') + { + $cats_db->add_category(CATEGORY_NO_PARENT, $c->name, $c->description); + } + } + } + } + else + { + $setup['error']['category'] = 'Please add a category'; + } + } + else + { + $setup['error']['category'] = 'Please add a category.'; + } + } + + if (CURRENT_STEP == SETUP_TOTAL_STEPS && ! array_key_exists('error', $setup)) + { + unlink(TMP_DIR.'/'.$_COOKIE['setup'].'.txt'); + setcookie('setup', '', time() - 3600); + + // Create config file. + $config_file_src = file_get_contents(CONFIG_TEMPLATE_PATH); + file_put_contents(CONFIG_DIR.'/config.php', $config_file_src); + + header("Location: $config[http_host]"); + } + else + { + // If no error, take user to the next step. + if ( ! array_key_exists('error', $setup)) + { + $setup['current_step']++; + } + file_put_contents(TMP_DIR.'/'.$_COOKIE['setup'].'.txt', serialize($setup)); + + header("Location: $config[http_host]/admin/setup.php"); + } +} diff --git a/admin/form/subcategory.php b/admin/form/subcategory.php new file mode 100644 index 0000000..4b36dad --- /dev/null +++ b/admin/form/subcategory.php @@ -0,0 +1,99 @@ + 1) + { + $cat_meta = $cat->get_cat_by_id($_GET['cat_id']); + if ($cat_meta !== NULL) + { + if (isset($_GET['a']) && in_array(strtolower($_GET['a']), array('add', 'edit', 'delete'))) + { + $base_cat_id = $cat->get_base_cat($_GET['cat_id']); + $is_moderator = $user_db->is_moderator($sess_info['user_id'], $base_cat_id); + + if (USER_IS_ADMIN || $is_moderator) + { + if ((strtolower($_GET['a']) === 'add' || strtolower($_GET['a']) === 'edit') + && (!isset($_POST['name']) || !isset($_POST['description']))) + { + $redirect_url .= '/threads.php?cat_id=' . $_GET['cat_id']; + } + else + { + switch(strtolower($_GET['a'])) + { + case 'add': + if (validate(trim($_POST['name']), trim($_POST['description']))) + { + $subcat_id = $cat->add_category($_GET['cat_id'], trim($_POST['name']), trim($_POST['description'])); + $redirect_url .= '/threads.php?cat_id=' . $subcat_id; + } + else + { + $error_key = $error->save(); + $redirect_url .= '/admin/create_subcategory.php?cat_id=' . $_GET['cat_id'] . '&e_k=' . $error_key; + } + break; + case 'edit': + if (validate(trim($_POST['name']), trim($_POST['description']))) + { + $cat->edit_category($_GET['cat_id'], trim($_POST['name']), trim($_POST['description'])); + $redirect_url .= '/threads.php?cat_id=' . $cat_meta['parent']; + } + else + { + $error_key = $error->save(); + $redirect_url .= '/admin/edit_subcategory.php?cat_id=' . $_GET['cat_id'] . '&e_k=' . $error_key; + } + break; + case 'delete': + $cat->delete_category($cat_meta['id']); + + // Redirect to parent category. + $redirect_url .= '/threads.php?cat_id=' . $cat_meta['parent']; + break; + } + } + } + else + { + $redirect_url .= '/threads.php?cat_id=' . $_GET['cat_id']; + } + } + else + { + $redirect_url .= '/threads.php?cat_id=' . $_GET['cat_id']; + } + } + } +} + +function validate($cat_name, $description) { + global $error; + + if ($cat_name === '') { + $error->add('sub_cat', 'name', 'Category name cannot be empty'); + } + + if ($description === '') { + $error->add('sub_cat', 'description', 'Description cannot be empty'); + } + + if ($error->exists_group('sub_cat')) { + $error->add_field_value('subcat', 'name', $cat_name); + $error->add_field_value('subcat', 'description', $description); + + return False; + } + + return True; +} + +header("Location: $redirect_url"); diff --git a/admin/includes/config.php b/admin/includes/config.php new file mode 100644 index 0000000..818d41c --- /dev/null +++ b/admin/includes/config.php @@ -0,0 +1,21 @@ + +
+ class="textfield" /> + +
+
+
Sort: 'A - Z', 'za' => 'Z - A', 'time' => 'Time joined',); + $links = array(); + foreach ($sort as $k => $v) + { + $url = cp_link($_GET['s'], array('sort_by'=>$k)); + if (isset($_POST['q']) && post_clean('q') != '') + { + $url .= '&q='.$_POST['q']; + } + $anchor = ''; + $links[] = $anchor; + } + echo implode(' | ', $links); + ?>
+
+ diff --git a/admin/includes/file_upload.php b/admin/includes/file_upload.php new file mode 100644 index 0000000..7a2daa8 --- /dev/null +++ b/admin/includes/file_upload.php @@ -0,0 +1,52 @@ + $rules['max_upload_size']) + { + $error_msg = ucfirst($name).' exceeds the max filesize which is '.convert_bytes($rules['max_upload_size']); + return False; + } + elseif ( ! in_array('.'.pathinfo($file['name'], PATHINFO_EXTENSION), $rules['extensions'])) + { + $error_msg = 'Only ' . implode(', ', $rules['extensions']) . ' files allowed'; + return False; + } + } + + return True; +} + +function random_logo_filename() +{ + $charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'; + $filename = ''; + for ($i = 0; $i < 20; $i++) + { + $filename .= $charset[ rand(0, strlen($charset) - 1) ]; + } + return $filename; +} diff --git a/admin/includes/setup_inc.php b/admin/includes/setup_inc.php new file mode 100644 index 0000000..da9de15 --- /dev/null +++ b/admin/includes/setup_inc.php @@ -0,0 +1,26 @@ + $config['site_logo_max_width']) + { + list($logo_w, $logo_h) = calc_size_by_width($config['site_logo_max_width'], $aspect_ratio); + } + else + { + $logo_w = $w; $logo_h = $h; + } + + if ($logo_h > $config['site_logo_max_height']) + { + list($logo_w, $logo_h) = calc_size_by_height($config['site_logo_max_height'], $aspect_ratio); + } + + return array($logo_w, $logo_h); +} + +function calc_size_by_height($height, $aspect_ratio) +{ + return array($height * $aspect_ratio, $height); +} + +function calc_size_by_width($width, $aspect_ratio) +{ + return array($width, $width / $aspect_ratio); +} diff --git a/admin/includes/subcategory_form.php b/admin/includes/subcategory_form.php new file mode 100644 index 0000000..30a598f --- /dev/null +++ b/admin/includes/subcategory_form.php @@ -0,0 +1,15 @@ +
+
+
+ /> +
+
+
+ +
+
+
diff --git a/admin/includes/subcategory_init.php b/admin/includes/subcategory_init.php new file mode 100644 index 0000000..7b20d61 --- /dev/null +++ b/admin/includes/subcategory_init.php @@ -0,0 +1,41 @@ +get_cat_by_id($_GET['cat_id']); + if ($meta != null) + { + define('CURRENT_CATEGORY_ID', $meta['id']); + define('CURRENT_CATEGORY_NAME', $meta['name']); + define('CURRENT_CATEGORY_DESCRIPTION', $meta['description']); + + $base_cid = $cat->get_base_cat(CURRENT_CATEGORY_ID); + define('USER_IS_MODERATOR', $user_db->is_moderator($sess_info['user_id'], $base_cid)); + + if (!USER_IS_ADMIN && !USER_IS_MODERATOR) + { + redirect_to_index(); + } + } + else + { + redirect_to_index(); + } +} +else +{ + redirect_to_index(); +} diff --git a/admin/js/setup_4.js b/admin/js/setup_4.js new file mode 100644 index 0000000..35b7f4e --- /dev/null +++ b/admin/js/setup_4.js @@ -0,0 +1,193 @@ +function addForumCats(forumCats) { + window.forumCats = forumCats; + var i; + for (i = 0; i < forumCats.length; i++) { + window.cats[i] = forumCats[i]; + } + refreshTable(); +} + +var cats = []; + +var table = document.getElementById('category_table').childNodes[1]; + +var catForm = { + state: 'add', + form: document.getElementById('cat_form') || document.getElementById('cp_form'), + + init: function() { + this.nameField = this.form.name, + this.descriptionField = this.form.description, + this.addBtn = this.form.addBtn; + }, + + header: document.getElementById('add_cat_h4'), + + getName: function() { + return this.nameField.value.trim(); + }, + getDescription: function() { + return this.descriptionField.value.trim(); + }, + + setName: function(name) { + this.nameField.value = name; + }, + setDescription: function(description) { + this.descriptionField.value = description; + }, + setState: function(state, cat) { + if (state == 'edit' || state == 'editLock') { + this.state = state; + + this.header.innerHTML = 'Edit Category'; + this.addBtn.value = 'Edit'; + + if (cat != null) { + this.setName(cat.name); + this.setDescription(cat.description); + } + if (this.state == 'editLock') { + this.editCatName = cat.name; + } + } else { + this.state = 'add'; + this.header.innerHTML = 'Add Category'; + this.addBtn.value = 'Add'; + this.editCatName = null; + } + }, + setFormError: function(err) { + document.getElementById('error').innerHTML = err; + }, + setFieldError: function(field, err) { + var errSpan = document.getElementsByClassName('error_' + field)[0]; + errSpan.innerHTML = err; + }, + clearErrors: function() { + document.getElementById('error').innerHTML = ''; + document.getElementsByClassName('error_name')[0].innerHTML = ''; + document.getElementsByClassName('error_description')[0].innerHTML = ''; + }, + addHiddenField: function(name, value) { + this.form.innerHTML += ""; + }, + reset: function() { + this.state = 'add'; + this.header.innerHTML = 'Add Category'; + this.setName(''); + this.setDescription(''); + this.addBtn.value = 'Add'; + } +}; +catForm.init(); +catForm.nameField.onkeyup = function() { + if (catForm.state == 'editLock') { + return; + } + + if (getCatIndex(cats, this.value) != -1) { + catForm.setState('edit', null); + } else { + catForm.setState('add', null); + } +} + +function refreshTable() { + table.innerHTML = ''; + var i; + for (i = 0; i < cats.length; i++) { + table.innerHTML += '
' + + '
' + cats[i].name + '
' + + '
' + cats[i].description + '
' + + '
Remove' + + '
' + + '
'; + } + table.scrollTop = table.scrollHeight; + + var rows = table.getElementsByClassName('row'); + for (i = 0; i < rows.length; i++) { + rows[i].data = cats[i]; + rows[i].onclick = function() { + catForm.setState('editLock', this.data); + } + rows[i].getElementsByClassName('remove')[0].onclick = function(e) { + cats.splice(cats.indexOf(this.parentNode.data), 1); + refreshTable(); + e.stopPropagation(); + e.preventDefault(); + } + } +} + +function addCat() { + var nameOk = true, descriptionOk = true; + + catForm.clearErrors(); + + if (catForm.getName() == '') { + catForm.setFieldError('name', 'Field is required'); + nameOk = false; + } + if (catForm.getDescription() == '') { + catForm.setFieldError('description', 'Field is required'); + descriptionOk = false; + } + + if (!nameOk || !descriptionOk) { + return; + } + + var i; + if (catForm.state == 'editLock') { + i = getCatIndex(cats, catForm.editCatName); + } else { + i = getCatIndex(cats, catForm.getName()); + } + + if (i != -1) { + cats[i].modify(catForm.getName(), catForm.getDescription()); + } else { + if (window.forumCats && (i = getCatIndex(forumCats, catForm.getName())) != -1) { + cats.push(forumCats[i].modify(catForm.getName(), catForm.getDescription())); + } else { + cats.push(new Category(null, catForm.getName(), catForm.getDescription())); + } + } + catForm.reset(); + refreshTable(); +} + +function getCatIndex(catArray, name) { + var i; + for (i = 0; i < catArray.length; i++) { + if (catArray[i].name.toLowerCase() == name.toLowerCase()) { + return i; + } + } + return -1; +} + +function addToForm(e) { + if (cats.length == 0) { + catForm.setFormError('Please add a category.'); + e.preventDefault(); + } else { + catForm.addHiddenField('cats', JSON.stringify(cats)); + } +} + +function Category(id, name, description) { + if (id != null) { + this.id = id; + } + this.name = name.trim(), + this.description = description.trim(); + + this.modify = function(name, description) { + this.name = name.trim(), + this.description = description.trim(); + return this; + }; +} diff --git a/admin/setup.php b/admin/setup.php new file mode 100644 index 0000000..e0f871f --- /dev/null +++ b/admin/setup.php @@ -0,0 +1,232 @@ + 1); + file_put_contents(TMP_DIR . "/$tmp_filename.txt", serialize($setup)); +} + +define('CURRENT_STEP', $setup['current_step']); + +function steps() { + $steps = array(); + for ($i = 1; $i <= SETUP_TOTAL_STEPS; $i++) { + $step = 'STEP $i"; + $step .= ''; + + $steps[] = $step; + } + echo implode('', $steps); +} + +function error($key, $tag) +{ + global $setup; + if (isset($setup['error'][$key])) + { + if ($tag != '') + { + echo '<'.$tag.' class="error">'; + } + echo $setup['error'][$key]; + if ($tag != '') + { + echo ''; + } + } +} + +function field_value($key) +{ + global $setup; + if (isset($setup['form_data'][$key])) + { + echo ' value="'.$setup['form_data'][$key].'"'; + } +} +?> + + + + <?php + $titles = array( + 'Database setup', + 'Create Admin Account', + 'Create Site', + 'Create Forum Categories' + ); + echo $titles[CURRENT_STEP - 1]; + ?> – ForumHopper + + + + + + + + +
+ + +
+
Enter details needed for database connection.
+
+
+ /> +
+
+
+ /> +
+
+
+ /> +
+
+
+ /> +
+
+
+ +
+
Setup your admin account
+
+
+ /> +
+
+
+ +
+
+
+ +
+
+
+ /> +
+
+
+ +
+
Setup your site
+
+
+ /> +
+
+
+ +
+
+ +
+ '; + foreach ($themes as $th => $th_meta) + { + echo '
  • '; + } + echo ''; + ?> + +
    + +
    +
    +
    +
    + +
    +
    Setup Categories
    +
    +
    +
    Category
    +
    Description
    +
    +
    +
    +
    Click on a row above to edit.
    +
    +

    Add Category

    +
    +
    + +
    +
    +
    + +
    +
    + + +
    +
    + +
    + +
    + + + From 3d6eccbc1b520bf8c211508c0f671e5ae74b4984 Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:12:58 +0100 Subject: [PATCH 02/12] Add files via upload --- classes/Theme.php | 68 +++++++++++++ classes/Theme_manager.php | 197 ++++++++++++++++++++++++++++++++++++++ classes/attachments.php | 93 ++++++++++++++++++ classes/error_message.php | 87 +++++++++++++++++ classes/file.php | 60 ++++++++++++ classes/pagination.php | 50 ++++++++++ classes/user_session.php | 79 +++++++++++++++ 7 files changed, 634 insertions(+) create mode 100644 classes/Theme.php create mode 100644 classes/Theme_manager.php create mode 100644 classes/attachments.php create mode 100644 classes/error_message.php create mode 100644 classes/file.php create mode 100644 classes/pagination.php create mode 100644 classes/user_session.php diff --git a/classes/Theme.php b/classes/Theme.php new file mode 100644 index 0000000..d667997 --- /dev/null +++ b/classes/Theme.php @@ -0,0 +1,68 @@ +'; + + echo '
    '; + echo ''.$user['username'].''; + + echo '
    '; + echo '

    '.$user['username'].'

    '; + + echo '
    '; + foreach ($user['info'] as $k => $v) + { + $k = ucfirst($k); + echo "

    $k: $v

    "; + } + echo '
    '; + + echo '
    '; + + echo '
    '; + + echo '
    '.implode(' | ', $user['options']).'
    '; + + echo '
    '; + + echo ''; + } +} diff --git a/classes/Theme_manager.php b/classes/Theme_manager.php new file mode 100644 index 0000000..8c7b627 --- /dev/null +++ b/classes/Theme_manager.php @@ -0,0 +1,197 @@ +screenshot_path)) + { + $themes[ $th ]->screenshot_url = $GLOBALS['config']['themes_url']."/$th/".$themes[ $th ]->screenshot_path; + $themes[ $th ]->screenshot_path = $GLOBALS['config']['themes_path'] . "/$th/" . $themes[ $th ]->screenshot_path; + } + } + return $themes; + } + + /** + * Installs a new theme. + * + * @param string Path to zip file. + */ + static function install_theme($theme_path, $theme_zip_filename, &$error) + { + $theme_name = strtolower(explode('.', $theme_zip_filename)[0]); + + $theme_zip = zip_open($theme_path); + if ($theme_zip) + { + // Fetch all entries in zip file. + $entries = array(); + while ($theme_zip_entry = zip_read($theme_zip)) + { + $entry_name = strtolower(zip_entry_name($theme_zip_entry)); + $entries['zip_entry_res'][] = $theme_zip_entry; + $entries['files'][] = $entry_name; + } + + // Look for required files. + $required = array( + // CSS files. + "$theme_name/css/", + "$theme_name/css/button.css", + "$theme_name/css/currently_viewing.css", + "$theme_name/css/main.css", + "$theme_name/css/nav_menu.css", + "$theme_name/css/nav_search.css", + "$theme_name/css/side_nav.css", + "$theme_name/css/side_sections_right.css", + "$theme_name/css/side_topics.css", + "$theme_name/css/tab.css", + "$theme_name/css/thread_item.css", + "$theme_name/css/thread_reply.css", + "$theme_name/css/threads_toolbar.css", + // Theme class file. + "$theme_name/$theme_name.php", + // JSON file. + "$theme_name/meta.json", + ); + foreach ($required as $rq) + { + if (!in_array($rq, $entries['files'])) + { + $error = "'$theme_zip_filename/$rq' not found"; + return false; + } + } + + $meta = null; + // Check meta file. + foreach ($entries['files'] as $i => $f) + { + if ($f == "$theme_name/meta.json") + { + $entry_res_id = $entries['zip_entry_res'][$i]; + zip_entry_open($theme_zip, $entry_res_id); + + $meta = @json_decode(zip_entry_read($entry_res_id, zip_entry_filesize($entry_res_id))); + if ($meta !== null) + { + $meta = (array) $meta; + // Check for required keys + foreach (array('name', 'screenshot_path', 'version') as $k) + { + if (!in_array($k, array_keys($meta))) + { + $error = "‘$k’ not found in ‘$theme_zip_filename/$f’"; + return false; + } + } + + // Validate required keys. + foreach ($meta as $k => $v) + { + $meta[$k] = trim($v); + } + + // Validate $meta['name']. + if (strlen($meta['name']) == 0) + { + $error = "‘name’ in ‘meta.json’ cannot be empty"; + return false; + } + elseif (preg_match('~[^0-9a-zA-Z\-_ ]~', $meta['name'])) + { + $error = "‘name’ in ‘meta.json’ can only contain the following characters: 0-9, A-Z, a-z, -, _ and a white space"; + return false; + } + + // Validate $meta['version'] + if (strlen($meta['version']) == 0) + { + $error = "‘version’ in ‘meta.json’ cannot be empty"; + return false; + } + elseif (!preg_match('~^\d+\.\d+$~', $meta['version'])) + { + $error = "‘version’ in ‘meta.json’ must be in the format ‘a.b’. Example: 1.0, 1.1"; + return false; + } + } + else + { + $error = "Invalid ‘meta.json’ file"; + return false; + } + + break; + } + } + + // Delete theme if theme is installed. + $installed_themes = self::get_themes(); + if (in_array($theme_name, array_keys($installed_themes))) + { + self::delete_theme($theme_name); + } + + // Install theme. + require_once($GLOBALS['config']['__'] . '/file.php'); + for ($i = 0; $i < count($entries['zip_entry_res']); $i++) + { + $name = $entries['files'][$i]; + if ($name[strlen($name) - 1] == '/') + { + create_path($GLOBALS['config']['themes_path'] . '/' . rtrim($name, '/')); + } + else + { + $entry_resid = $entries['zip_entry_res'][$i]; + + // ^^ Meta file has been read before. + if ($name == "$theme_name/meta.json") + { + $content = json_encode($meta); + } + else + { + zip_entry_open($theme_zip, $entries['zip_entry_res'][$i]); + $content = zip_entry_read($entry_resid, zip_entry_filesize($entry_resid)); + } + + $fp = fopen($GLOBALS['config']['themes_path'] . '/' . $name, 'w+'); + fwrite($fp, $content); + fclose($fp); + + zip_entry_close($entry_resid); + } + } + + return true; + } + } + + static function delete_theme($theme_name) + { + require_once($GLOBALS['config']['__'] . '/file.php'); + delete_folder($GLOBALS['config']['themes_path'].'/'.$theme_name); + } +} diff --git a/classes/attachments.php b/classes/attachments.php new file mode 100644 index 0000000..61cae3c --- /dev/null +++ b/classes/attachments.php @@ -0,0 +1,93 @@ + $config['attachment_max'] - 1) { + break; + } + + $filename = $_FILES['attachment']['name'][$i]; + $error_code = $_FILES['attachment']['error'][$i]; + if ($error_code !== 0) + { + if ($error_code == 1 || $error_code == 2) + { + $attachment_error = $filename . ' exceeds the max size limit which is ' . convert_bytes($config['attachment_max_size']); + return false; + } + elseif ($error_code === 4) + { + continue; + } + } + else + { + if (!in_array(strtolower(pathinfo($filename, PATHINFO_EXTENSION)), $config['attachment_supported_extensions'])) + { + $attachment_error = 'Only .'.implode(', .', $config['attachment_supported_extensions']).' file(s) allowed'; + return false; + } + elseif ($_FILES['attachment']['size'][$i] > $config['attachment_max_size']) + { + $attachment_error = $filename . ' exceeds the max size limit which is ' . convert_bytes($config['attachment_max_size']); + return false; + } + } + } + + return true; +} + +function save_attachments($relpath) +{ + global $config; + + if (count($_FILES['attachment']['name']) === 1 && $_FILES['attachment']['error'][0] === 4) + { + return; + } + + if ( ! file_exists($config['attachment_path'])) + { + create_path($config['attachment_path']); + } + + $tmp = ''; + foreach (explode('/', $relpath) as $p) + { + $tmp .= '/' . $p; + if ( ! file_exists($config['attachment_path'] . $tmp)) + { + mkdir($config['attachment_path'] . $tmp); + } + } + + for ($i = 0; $i < count($_FILES['attachment']['name']); $i++) + { + if ($i > $config['attachment_max'] - 1) + { + break; + } + + $filename = $_FILES['attachment']['name'][$i]; + + move_uploaded_file($_FILES['attachment']['tmp_name'][$i], $config['attachment_path'] . '/' . $relpath . '/' . $filename); + } +} + +function generate_random_filename() +{ + $valid_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'; + $name = ''; + for ($i = 0; $i < 30; $i++) + { + $name .= $valid_chars[rand(0, strlen($valid_chars) - 1)]; + } + return $name; +} diff --git a/classes/error_message.php b/classes/error_message.php new file mode 100644 index 0000000..45b8d75 --- /dev/null +++ b/classes/error_message.php @@ -0,0 +1,87 @@ + array(), + 'value' => array() + ); + + var $error_db = NULL; + + function __construct() + { + require($GLOBALS['config']['database_path'] . '/error_database.php'); + $this->error_db = new Error_database(); + + if (isset($_GET['e_k'])) { + $errors = $this->error_db->get($_GET['e_k']); + if ($errors !== null) { + $this->data = unserialize($errors); + $this->error_db->delete($_GET['e_k']); + } + } + } + + function add($group, $key, $message) + { + $this->data['error'][$group][$key] = $message; + } + + function get_message($group, $key) + { + return ' ' . $this->data['error'][$group][$key] . ''; + } + + function exists($group, $key) + { + if ($this->exists_group($group)) + { + return array_key_exists($key, $this->data['error'][$group]); + } + else + { + return False; + } + } + + function exists_group($group) + { + return array_key_exists($group, $this->data['error']); + } + + function add_field_value($group, $key, $value) + { + $this->data['value'][$group][$key] = $value; + } + + function field_value_exists($group, $key) + { + if (array_key_exists($group, $this->data['value'])) + { + return array_key_exists($key, $this->data['value'][$group]); + } + else + { + return False; + } + } + + function get_field_value($group, $key) + { + return $this->data['value'][$group][$key]; + } + + function save() + { + $serialized = serialize($this->data); + $error_key = hash('md5', uniqid(). $serialized); + $this->error_db->save($error_key, $serialized); + return $error_key; + } + + function clear($group) + { + //unset($_SESSION['error'][$group]); + //unset($_SESSION['value'][$group]); + } +} diff --git a/classes/file.php b/classes/file.php new file mode 100644 index 0000000..981dea9 --- /dev/null +++ b/classes/file.php @@ -0,0 +1,60 @@ += 1024*1024*1024) { + return round($bytes / (1024*1024*1024), 2) . 'GB'; + } elseif ($bytes >= 1024 * 1024) { + return round($bytes / (1024*1024), 2) . 'MB'; + } elseif ($bytes >= 1024) { + return round($bytes / 1024, 2) . 'KB'; + } + + return $bytes.'B'; +} + +function create_path($path) +{ + $basepath = dirname($path); + + if (!file_exists($basepath)) + { + create_path($basepath); + } + + mkdir($path); +} diff --git a/classes/pagination.php b/classes/pagination.php new file mode 100644 index 0000000..1574bfb --- /dev/null +++ b/classes/pagination.php @@ -0,0 +1,50 @@ +items_per_page = $items_per_page; + + $this->num_pages = (int)($items_total / $items_per_page); + if ($items_total % $items_per_page !== 0) + { + $this->num_pages++; + } + } + + function get_start_index($page) + { + return ($page - 1) * $this->items_per_page; + } + + function get_number_of_pages() + { + return $this->num_pages; + } + + function get_items_per_page() + { + return $this->items_per_page; + } + + function get_current_page() + { + $current_page = 1; + if (isset($_GET['page'])) + { + if (preg_match('~^[0-9]+$~', $_GET['page']) && $_GET['page'] <= $this->num_pages) + { + $current_page = $_GET['page']; + } + elseif ($_GET['page'] == 'l') + { + $current_page = $this->num_pages; + } + } + + return $current_page; + } +} diff --git a/classes/user_session.php b/classes/user_session.php new file mode 100644 index 0000000..2a33534 --- /dev/null +++ b/classes/user_session.php @@ -0,0 +1,79 @@ +session_db = new User_Session_Database(); + + define('SESS_COOKIE_NAME', $GLOBALS['config']['session_cookie_name']); + + ini_set('session.name', SESS_COOKIE_NAME); + ini_set('session.cookie_httponly', '1'); + ini_set('session.cookie_path', '/'); + } + + function start_new_sess($user_id, $keep_user_logged_in) + { + if (!session_id()) + { + if ($keep_user_logged_in) + { + ini_set('session.cookie_lifetime', $GLOBALS['config']['session_cookie_lifetime']); + } + else + { + ini_set('session.cookie_lifetime', '0'); + } + session_start(); + $this->session_db->add_sess_info(session_id(), $user_id, $_SERVER['HTTP_USER_AGENT'], $_SERVER['REMOTE_ADDR']); + $this->update_last_online($user_id); + } + + return session_id(); + } + + function end_current_session() + { + if (isset($_COOKIE[SESS_COOKIE_NAME])) + { + $this->session_db->delete_sess_record($_COOKIE[SESS_COOKIE_NAME]); + + $params = session_get_cookie_params(); + setcookie(SESS_COOKIE_NAME, '', time() - 3600, $params['path'], $params['domain'], $params['secure'], isset($params['httponly'])); + } + } + + function is_logged_in() + { + if (isset($_COOKIE[SESS_COOKIE_NAME])) + { + $sess_info = $this->session_db->get_sess_info($_COOKIE[SESS_COOKIE_NAME]); + if ($sess_info !== NULL) + { + return $sess_info; + } + else + { + $params = session_get_cookie_params(); + setcookie(SESS_COOKIE_NAME, '', time() - 3600, $params['path'], $params['domain'], $params['secure'], isset($params['httponly'])); + return FALSE; + } + } + else + { + return FALSE; + } + } + + function get_last_online($user_id) + { + return $this->session_db->get_last_active($user_id); + } + + function update_last_online($user_id) + { + $this->session_db->set_last_active(time(), $user_id); + } +} From 5b8e67419543cd178bfb3b8e71906eb5558682e9 Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:15:36 +0100 Subject: [PATCH 03/12] Add files via upload --- config/config_inc.php | 26 ++++++++++++++++++ config/config_template.php | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 config/config_inc.php create mode 100644 config/config_template.php diff --git a/config/config_inc.php b/config/config_inc.php new file mode 100644 index 0000000..0445fb3 --- /dev/null +++ b/config/config_inc.php @@ -0,0 +1,26 @@ + Date: Sun, 17 Sep 2017 00:16:49 +0100 Subject: [PATCH 04/12] Add files via upload --- css/banned.css | 12 +++++++ css/create_thread.css | 25 ++++++++++++++ css/edit_profile.css | 36 ++++++++++++++++++++ css/edit_reply.css | 16 +++++++++ css/footer.css | 26 ++++++++++++++ css/index.css | 3 ++ css/login.css | 20 +++++++++++ css/main.css | 77 ++++++++++++++++++++++++++++++++++++++++++ css/profile.css | 44 ++++++++++++++++++++++++ css/profile_layout.css | 18 ++++++++++ css/search.css | 8 +++++ css/settings.css | 32 ++++++++++++++++++ css/signup.css | 13 +++++++ css/threads.css | 20 +++++++++++ css/view.css | 56 ++++++++++++++++++++++++++++++ 15 files changed, 406 insertions(+) create mode 100644 css/banned.css create mode 100644 css/create_thread.css create mode 100644 css/edit_profile.css create mode 100644 css/edit_reply.css create mode 100644 css/footer.css create mode 100644 css/index.css create mode 100644 css/login.css create mode 100644 css/main.css create mode 100644 css/profile.css create mode 100644 css/profile_layout.css create mode 100644 css/search.css create mode 100644 css/settings.css create mode 100644 css/signup.css create mode 100644 css/threads.css create mode 100644 css/view.css diff --git a/css/banned.css b/css/banned.css new file mode 100644 index 0000000..49a213b --- /dev/null +++ b/css/banned.css @@ -0,0 +1,12 @@ +#content { + font-size: 16px; +} + +#content div { + margin-top: 15px; +} + +#content div img { + width: 230px; + height: 230px; +} diff --git a/css/create_thread.css b/css/create_thread.css new file mode 100644 index 0000000..f88e2f9 --- /dev/null +++ b/css/create_thread.css @@ -0,0 +1,25 @@ +h3 { + margin-top: 5px; +} + +#thread_form { + margin-top: 30px; +} + +#thread_form .group { + margin-bottom: 10px; +} + +#thread_form .group .textfield, +#thread_form .group textarea { + margin-top: 3px; +} + +#thread_form .group .attachments input { + margin-top: 3px; +} + +#thread_form .textfield, +#thread_form textarea { + width: 100%; +} diff --git a/css/edit_profile.css b/css/edit_profile.css new file mode 100644 index 0000000..7124af4 --- /dev/null +++ b/css/edit_profile.css @@ -0,0 +1,36 @@ +#left_small #file_chooser_wrapper { + margin-top: 10px; +} + +#left_small #file_chooser_wrapper input { + margin-top: 5px; +} + +#left_small input { + width: 100%; +} + +#edit_form { + margin-top: 10px; +} + +#right_large > div { + margin-bottom: 10px; +} + +#right_large > div select { + margin-right: 5px; +} + +#right_large > div select.birthdate { + margin-top: 5px; +} + +#right_large > div select#sex { + margin-left: 5px; +} + +#right_large > div .textfield { + width: 100%; + margin-top: 5px; +} diff --git a/css/edit_reply.css b/css/edit_reply.css new file mode 100644 index 0000000..a4c9d25 --- /dev/null +++ b/css/edit_reply.css @@ -0,0 +1,16 @@ +h3 { + margin-top: 5px; +} + +#edit_form { + margin-top: 30px; +} + +#edit_form textarea { + width: 100%; + margin-top: 3px; +} + +#edit_form > div { + margin-bottom: 5px; +} diff --git a/css/footer.css b/css/footer.css new file mode 100644 index 0000000..9a03fda --- /dev/null +++ b/css/footer.css @@ -0,0 +1,26 @@ +#footer { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 70px; +} + +#footer .bull { + font-size: 7px; + position: relative; + top: -2px; +} + +#footer #left.float { + float: left; +} + +#footer #col3 { + float: right; + width: 187px; +} + +#footer #col3 #logout_link { + margin-top: 3px; +} diff --git a/css/index.css b/css/index.css new file mode 100644 index 0000000..2bf6cb6 --- /dev/null +++ b/css/index.css @@ -0,0 +1,3 @@ +#categories { + margin-top: 10px; +} diff --git a/css/login.css b/css/login.css new file mode 100644 index 0000000..d7911e6 --- /dev/null +++ b/css/login.css @@ -0,0 +1,20 @@ +form#right > div { + margin-top: 10px; +} + +form#right > div .textfield { + margin-top: 3px; +} + +form#right .textfield { + width: 100%; +} + +form#right #error_p { + margin-top: 5px; +} + +form#right #remember_div label { + position: relative; + top: -2px; +} \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..cc88f1d --- /dev/null +++ b/css/main.css @@ -0,0 +1,77 @@ +html { + min-height: 100%; + position: relative; +} + +body { + margin: 0; + padding: 0; +} + +p, h1, h2, h3, h4, h5, form, input, ul, li {margin:0;padding:0;} + +ul{list-style:none;} + +label { + cursor: pointer; +} + +.clearfix{clear:both;} + +#nav, +#content, +#footer #wrapper { + padding: 15px; +} + +/* Nav */ + +#nav #nav_left { + float: left; + width: 720px; +} + +#nav #nav_left #site_banner { + float: left; +} + +#nav #nav_left #nav_menu { + font-size: 12px; + float: right; +} + +#nav #nav_search { + float: right; +} + +#content { + padding-top: 0; + margin-top: 15px; + margin-bottom: 70px; /* Height of footer. */ +} + +#content #left { + float: left; + width: 720px; +} + +#content #right { + float: right; + width: 187px; +} + +#side_nav { + width: 130px; + padding-right: 20px; +} + +select, +.textfield, +textarea { + box-sizing: border-box; + outline: none; +} + +textarea { + resize: none; +} diff --git a/css/profile.css b/css/profile.css new file mode 100644 index 0000000..4ee6c12 --- /dev/null +++ b/css/profile.css @@ -0,0 +1,44 @@ +h3 span { + font-weight: normal; +} + +#left #edit_link { + margin-top: 3px; +} + +#left #user_content { + margin-top: 15px; +} + +#left_small ul li { + margin-top: 5px; +} + +#left_small ul li span { + font-weight: bold; + font-size: 11px; +} + +#left_small ul li div { + margin-top: 2px; +} + +#left_small ul li.online { + color: green; +} + +#left_small ul li.online b { + font-size: 14px; +} + +#left #ban_option { + margin-top: 5px; +} + +#left #ban_option a { + color: #f10; +} + +#left #ban_option a:hover { + text-decoration: underline; +} diff --git a/css/profile_layout.css b/css/profile_layout.css new file mode 100644 index 0000000..259bbcb --- /dev/null +++ b/css/profile_layout.css @@ -0,0 +1,18 @@ +#left_small, +#right_large { + float: left; +} + +#left_small { + width: 150px; +} + +#left_small #profile_pic_wrapper img { + width: 150px; + height: 150px; +} + +#right_large { + width: 550px; + padding-left: 20px; +} diff --git a/css/search.css b/css/search.css new file mode 100644 index 0000000..cf17959 --- /dev/null +++ b/css/search.css @@ -0,0 +1,8 @@ +#results { + margin-top: 10px; +} + +#results .needle { + background: #ffff77; + padding: 1px; +} diff --git a/css/settings.css b/css/settings.css new file mode 100644 index 0000000..73423c8 --- /dev/null +++ b/css/settings.css @@ -0,0 +1,32 @@ +#outer { + margin-top: 10px; +} + +#outer #side_nav, +#outer #settings_form_wrapper { + float: left; +} + +#outer #settings_form_wrapper { + width: 570px; +} + +#outer #settings_form_wrapper form > div { + margin-bottom: 10px; +} + +#outer #settings_form_wrapper .textfield { + width: 100%; +} + +#outer #settings_form_wrapper .textfield { + margin-top: 3px; +} + +form table tr td { + padding-bottom: 10px; +} + +form table tr td.col2 { + padding-left: 30px; +} diff --git a/css/signup.css b/css/signup.css new file mode 100644 index 0000000..7c67bd9 --- /dev/null +++ b/css/signup.css @@ -0,0 +1,13 @@ +form#signup_form > div { + margin-top: 15px; +} + +form#signup_form .textfield { + width: 100%; + margin-top: 5px; +} + +form#signup_form #accept_terms_div label { + position: relative; + top: -2px; +} diff --git a/css/threads.css b/css/threads.css new file mode 100644 index 0000000..939cea4 --- /dev/null +++ b/css/threads.css @@ -0,0 +1,20 @@ +h3 { + margin-top: 5px; +} + +#left #top { + font-size: 13px; + margin-bottom: 6px; +} + +#cat-hierarchy { +} + +#options { + margin-top: 10px; +} + +div.no_thread, +p#page_not_found { + margin-top: 15px; +} diff --git a/css/view.css b/css/view.css new file mode 100644 index 0000000..e34cd57 --- /dev/null +++ b/css/view.css @@ -0,0 +1,56 @@ +h3 { + margin-top: 5px; +} + +#meta { + margin-top: 5px; +} + +#users_replies { + margin-top: 30px; +} + +#users_replies #left_col, +#users_replies #replies_wrapper { + float: left; +} + +#users_replies #left_col { + width: 110px; +} + +#users_replies #replies_wrapper { + width: 600px; + margin-left: 10px; +} + +#users_replies #replies_wrapper #reply_form { + margin-top: 10px; +} + +#users_replies #replies_wrapper #reply_form label { + font-weight: bold; + font-size: 11px; +} + +#users_replies #replies_wrapper #reply_form textarea { + margin-top: 3px; + padding: 5px; + width: 100%; +} + +#users_replies #replies_wrapper #reply_form input { + margin-top: 3px; +} + +#users_replies #replies_wrapper #reply_form input[type="submit"] { + margin-top: 7px; +} + +#error_message { + margin-top: 10px; +} + +#log_in { + margin-top: 10px; +} From 64bfc206a323056d076b9258fbe89c1a7d8d1d84 Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:18:17 +0100 Subject: [PATCH 05/12] Add files via upload --- database/database.php | 125 +++++++++ database/error_database.php | 32 +++ database/thread_category.php | 170 ++++++++++++ database/threads_database.php | 210 +++++++++++++++ database/threads_replies_database.php | 153 +++++++++++ database/threads_views_database.php | 135 ++++++++++ database/user_database.php | 357 ++++++++++++++++++++++++++ database/user_session_database.php | 49 ++++ database/users_settings_database.php | 56 ++++ 9 files changed, 1287 insertions(+) create mode 100644 database/database.php create mode 100644 database/error_database.php create mode 100644 database/thread_category.php create mode 100644 database/threads_database.php create mode 100644 database/threads_replies_database.php create mode 100644 database/threads_views_database.php create mode 100644 database/user_database.php create mode 100644 database/user_session_database.php create mode 100644 database/users_settings_database.php diff --git a/database/database.php b/database/database.php new file mode 100644 index 0000000..1a2f138 --- /dev/null +++ b/database/database.php @@ -0,0 +1,125 @@ +db = new mysqli( + $GLOBALS['config']['db_host'], + $GLOBALS['config']['db_user'], + $GLOBALS['config']['db_pass'], + $GLOBALS['config']['db_name']); + $GLOBALS['db'] = $this->db; + } + else + { + $this->db = $GLOBALS['db']; + } + + $this->tables = (object) array( + 'users' => 'users', + 'usermeta' => 'usermeta', + 'users_settings' => 'users_settings', + 'categories' => 'categories', + 'users_sessions' => 'users_sessions', + 'threads' => 'threads', + 'threads_replies' => 'threads_replies', + 'threads_views' => 'threads_views', + 'errors' => 'errors', + 'moderators' => 'moderators', + ); + } + + /** + * Selects the first row from the database. Execute the statement first then pass the statement as argument. + * + * @param object The statement + */ + function select_one_row($stmt) + { + $stmt->store_result(); + + if ($stmt->num_rows > 0) + { + $meta = $stmt->result_metadata(); + $fields = $meta->fetch_fields(); + + $row = array(); + foreach ($fields as $f) + { + $row[$f->name] = ''; + $row[$f->name] =& $row[$f->name]; + } + + call_user_func_array(array($stmt, 'bind_result'), $row); + $stmt->fetch(); + + return $row; + } + else + { + return NULL; + } + } + + function select_rows($stmt) + { + $stmt->store_result(); + + if ($stmt->num_rows > 0) + { + $results = array(); + + $meta = $stmt->result_metadata(); + $fields = $meta->fetch_fields(); + + while (True) + { + $row = $this->_get_result_holder($fields); + call_user_func_array(array($stmt, 'bind_result'), $row); + + if ($stmt->fetch() === NULL) + { + break; + } + + array_push($results, (object) $row); + } + + return $results; + } + else + { + return NULL; + } + } + + function _get_result_holder($fields) + { + $row = array(); + foreach ($fields as $f) + { + $row[$f->name] = ''; + $row[$f->name] =& $row[$f->name]; + } + return $row; + } + + function last_insert_id() + { + return $this->db->insert_id; + } + + function log_error($file, $line) + { + $error = $this->db->error; + + $stmt = $this->db->prepare("INSERT INTO error_log(file,line,message)VALUES(?,?,?)"); + $file = substr($file, strlen(FORUM_ROOT) + 1); + $stmt->bind_param('sis', $file, $line, $error); + $stmt->execute(); + + die($error); + } +} diff --git a/database/error_database.php b/database/error_database.php new file mode 100644 index 0000000..186b613 --- /dev/null +++ b/database/error_database.php @@ -0,0 +1,32 @@ +db->prepare("SELECT message FROM {$this->tables->errors} WHERE error_key=?") or die($this->db->error); + $stmt->bind_param('s', $error_key); + $stmt->execute(); + + return $this->select_one_row($stmt)['message']; + } + + function save($error_key, $message) + { + $stmt = $this->db->prepare("INSERT INTO {$this->tables->errors}(error_key, message)VALUES(?, ?)") or die($this->db->error); + $stmt->bind_param('ss', $error_key, $message); + $stmt->execute(); + } + + function delete($error_key) + { + $stmt = $this->db->prepare("DELETE FROM {$this->tables->errors} WHERE error_key=?") or die($this->db->error); + $stmt->bind_param('s', $error_key); + $stmt->execute(); + } +} diff --git a/database/thread_category.php b/database/thread_category.php new file mode 100644 index 0000000..c81b6ce --- /dev/null +++ b/database/thread_category.php @@ -0,0 +1,170 @@ +db->prepare("INSERT INTO {$this->tables->categories}(parent,cat,description)VALUES(?,?,?)") or die($this->db->error); + $stmt->bind_param('iss', $parent, $category, $description); + $stmt->execute(); + + return $this->last_insert_id(); + } + + function edit_category($cat_id, $name, $description) + { + $stmt = $this->db->prepare("UPDATE {$this->tables->categories} SET cat=?, description=? WHERE cat_id=?") or die($this->db->error); + $stmt->bind_param('ssi', $name, $description, $cat_id); + $stmt->execute(); + } + + function get_base_cat($cat_id) + { + $stmt = $this->db->prepare(" + SELECT parent + FROM {$this->tables->categories} + WHERE cat_id=? + ") or die($this->db->error); + $stmt->bind_param('i', $cat_id); + $stmt->execute(); + + $row = $this->select_one_row($stmt); + if ($row['parent'] == 1) + { + return $cat_id; + } + else + { + return $this->get_base_cat($row['parent']); + } + } + + function get_categories($parent) + { + $stmt = $this->db->prepare(" + SELECT c1.cat_id, c1.cat, c1.description, t.thread_count + FROM {$this->tables->categories} AS c1 + LEFT JOIN ( + SELECT cat_id, COUNT(thread_id) AS thread_count + FROM {$this->tables->threads} + GROUP BY cat_id + ) AS t + ON t.cat_id=c1.cat_id + WHERE c1.parent=? + "); + $stmt->bind_param('i', $parent); + $stmt->execute(); + + $stmt->store_result(); + + $stmt->bind_result($cat_id, $cat, $description, $thread_count); + + $categories = array(); + while ($stmt->fetch() !== NULL) + { + $categories[] = array( + 'cat_id' => $cat_id, + 'child_count' => intval($thread_count), + 'category' => $cat, + 'description' => $description); + } + return $categories; + } + + function get_hierarchy($cat_id) + { + $stmt = $this->db->prepare(" + SELECT + c1.parent, c2.cat + FROM categories AS c1 + INNER JOIN categories AS c2 + ON c2.cat_id=c1.parent + WHERE c1.cat_id=?"); + + $parent = $cat_id; + $cat_name = ''; + + $hierarchy = array(); + while($parent != null) + { + $stmt->bind_param('i', $parent); + $stmt->execute(); + + $stmt->store_result(); + $stmt->bind_result($parent, $cat_name); + + if ($stmt->fetch() !== NULL && $parent != 1) + { + $hierarchy[] = (object) array('id' => $parent, 'name' => $cat_name); + } + } + + return array_reverse($hierarchy); + } + + function get_cat_by_name($name) + { + return $this->_get_cat_where('c.cat=?', $name); + } + + function get_cat_by_id($cat_id) + { + return $this->_get_cat_where('c.cat_id=?', $cat_id); + } + + function _get_cat_where($where, $value) + { + $stmt = $this->db->prepare(" + SELECT + c.cat_id AS id, + c.parent AS parent, + c.cat AS name, + c.description AS description, + t.threads AS threads + FROM {$this->tables->categories} AS c + + LEFT JOIN ( + SELECT cat_id, COUNT(thread_id) AS threads + FROM {$this->tables->threads} + GROUP BY cat_id + ) AS t + ON t.cat_id=c.cat_id + + WHERE ".$where) or die($this->db->error); + $stmt->bind_param(is_int($value) ? 'i' : 's', $value); + $stmt->execute(); + + return $this->select_one_row($stmt); + } + + function delete_category($cat_id) + { + $stmt = $this->db->prepare("DELETE FROM {$this->tables->categories} WHERE cat_id=?") or die($this->db->error); + $stmt->bind_param('i', $cat_id); + $stmt->execute(); + } + + function delete_all_except($cat_ids, $parent) + { + $stmt = $this->db->prepare(" + DELETE FROM {$this->tables->categories} + WHERE + NOT cat_id IN (".rtrim(str_repeat('?,', count($cat_ids)), ',').") + AND parent=?") or die($this->db->error); + $params = array_merge(array(str_repeat('i', count($cat_ids)).'i'), $cat_ids, array($parent)); + for ($i = 1; $i < count($params); $i++) + { + $params[$i] =& $params[$i]; + } + call_user_func_array(array($stmt, 'bind_param'), $params); + $stmt->execute(); + } +} diff --git a/database/threads_database.php b/database/threads_database.php new file mode 100644 index 0000000..9a75525 --- /dev/null +++ b/database/threads_database.php @@ -0,0 +1,210 @@ +db->prepare("INSERT INTO {$this->tables->threads}(user_id, cat_id, topic, time_created)VALUES(?,?,?,?)") or die($this->db->error); + $stmt->bind_param('sisi', $user_id, $cat_id, $topic, time()); + $stmt->execute(); + + return $this->last_insert_id(); + } + + function get_thread_meta($thread_id) + { + $stmt = $this->db->prepare(" + SELECT + t.cat_id AS cat_id, + u.username AS username, + t.topic AS topic, + t.time_created AS time_created, + r.replies AS replies + FROM {$this->tables->threads} AS t + + LEFT JOIN {$this->tables->users} AS u + ON u.user_id=t.user_id + + INNER JOIN ( + SELECT thread_id, COUNT(reply_id) AS replies + FROM {$this->tables->threads_replies} + GROUP BY thread_id + ) AS r + ON t.thread_id=r.thread_id + + WHERE t.thread_id=?") or die($this->db->error); + $stmt->bind_param('i', $thread_id); + $stmt->execute(); + + return $this->select_one_row($stmt); + } + + function get_recent_threads($cat_id, $current_thread_id, $limit) + { + $stmt = $this->db->prepare(" + SELECT + t.thread_id AS thread_id, + t.topic AS topic + + FROM {$this->tables->threads} AS t + + INNER JOIN ( + SELECT + thread_id, + MAX(time_replied) AS last_replied + FROM {$this->tables->threads_replies} + GROUP BY thread_id + ) AS r + ON r.thread_id=t.thread_id + + WHERE + t.cat_id=? + AND NOT t.thread_id=? + + ORDER BY r.last_replied DESC + LIMIT ?") or die($this->db->error); + $stmt->bind_param('iii', $cat_id, $current_thread_id, $limit); + $stmt->execute(); + + return $this->select_rows($stmt); + } + + function get_thread_count_by_topic($topic) + { + $stmt = $this->db->prepare("SELECT COUNT(`thread_id`) AS count FROM {$this->tables->threads} WHERE `topic` LIKE ?") or die($this->db->error); + $like = "%$topic%"; + $stmt->bind_param('s', $like); + $stmt->execute(); + + return $this->select_one_row($stmt)['count']; + } + + function get_threads_by_topic($topic, $offset,$count) + { + $stmt = $this->db->prepare(" + SELECT + t.cat_id, + t.thread_id AS thread_id, + u.username AS op, + t.topic AS topic, + t.time_created AS time_created, + v.views AS views, + r.replies AS replies + FROM {$this->tables->threads} AS t + + INNER JOIN {$this->tables->users} AS u + ON u.user_id=t.user_id + + LEFT JOIN ( + SELECT thread_id, SUM(times_viewed) AS views + FROM {$this->tables->threads_views} + GROUP BY thread_id + ) AS v + ON v.thread_id=t.thread_id + + INNER JOIN ( + SELECT + thread_id, + COUNT(reply_id) AS replies, + MAX(time_replied) AS last_reply_sec + FROM {$this->tables->threads_replies} + GROUP BY thread_id + ) AS r + ON r.thread_id=t.thread_id + + WHERE t.topic LIKE ? + + ORDER BY r.last_reply_sec DESC + + LIMIT ?, ?") or die($this->db->error); + $like = '%'.$topic.'%'; + $stmt->bind_param('sii', $like, $offset, $count); + $stmt->execute(); + return $this->select_rows($stmt); + } + + function get_threads_by_cat_id($cat_id, $offset, $count) + { + return $this->_get_threads_by('t.cat_id', $cat_id, $offset, $count, 'r.last_replied DESC'); + } + + function get_threads_by_user_id($user_id, $offset, $count) + { + return $this->_get_threads_by('t.user_id', $user_id, $offset, $count, 't.time_created DESC'); + } + + /** + * @param count -1 Will select all items in the database. + */ + function _get_threads_by($field, $value, $offset, $count, $order_by) + { + $sql = " + SELECT + t.cat_id, + t.thread_id, + u.username AS op, + t.topic, + t.time_created, + r.reply_count AS replies, + v.views + FROM {$this->tables->threads} AS t + + INNER JOIN {$this->tables->users} AS u + ON u.user_id=t.user_id + + INNER JOIN ( + SELECT + thread_id, + COUNT(reply_id) AS reply_count, + MAX(time_replied) AS last_replied + FROM {$this->tables->threads_replies} + GROUP BY thread_id + ) AS r + ON r.thread_id=t.thread_id + + LEFT JOIN ( + SELECT thread_id, SUM(times_viewed) AS views + FROM {$this->tables->threads_views} + GROUP BY thread_id + ) AS v + ON v.thread_id=t.thread_id + + WHERE $field=? + ORDER BY $order_by"; + if ($count != -1) + { + $sql .= " LIMIT ?,?"; + } + $stmt = $this->db->prepare($sql) or die($this->db->error); + if ($count != -1) + { + $stmt->bind_param('iii', $value, $offset, $count); + } + else + { + $stmt->bind_param('i', $value); + } + $stmt->execute(); + + return $this->select_rows($stmt); + } + + function get_num_threads_by_user($user_id) { + $stmt = $this->db->prepare("SELECT COUNT(thread_id) AS threads FROM {$this->tables->threads} WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('i', $user_id); + $stmt->execute(); + + return $this->select_one_row($stmt)['threads']; + } + + function delete_thread($thread_id) + { + $stmt = $this->db->prepare("DELETE FROM {$this->tables->threads} WHERE thread_id=?") or die($this->db->error); + $stmt->bind_param('i', $thread_id); + $stmt->execute(); + } +} diff --git a/database/threads_replies_database.php b/database/threads_replies_database.php new file mode 100644 index 0000000..f2a9ee5 --- /dev/null +++ b/database/threads_replies_database.php @@ -0,0 +1,153 @@ +db->prepare("INSERT INTO {$this->tables->threads_replies}(thread_id, user_id, reply, time_replied, first_post)VALUES(?,?,?,?,?)"); + $first_post = $is_first_post ? 1 : 0; + $stmt->bind_param('iisii', $thread_id, $user_id, $reply, time(), $first_post); + $stmt->execute(); + + return $this->last_insert_id(); + } + + function get_replies($thread_id, $offset, $count) + { + return $this->get_replies_by('r.thread_id=?', $thread_id, $offset, $count); + } + + function edit_reply($reply_id, $reply) + { + $stmt = $this->db->prepare("UPDATE {$this->tables->threads_replies} SET reply=? WHERE reply_id=?"); + $stmt->bind_param('si', $reply, $reply_id); + $stmt->execute(); + } + + function delete_reply($reply_id) + { + $stmt = $this->db->prepare("DELETE FROM {$this->tables->threads_replies} WHERE reply_id=? AND NOT first_post=?"); + $first_post = 1; + $stmt->bind_param('ii', $reply_id, $first_post); + $stmt->execute(); + } + + function get_reply_by_id($reply_id) + { + $stmt = $this->db->prepare(" + SELECT + t.cat_id, + c.cat, + r.user_id, + t.topic, + r.thread_id, + r.reply + FROM {$this->tables->threads_replies} AS r + INNER JOIN {$this->tables->threads} AS t + ON t.thread_id=r.thread_id + INNER JOIN {$this->tables->categories} AS c + ON c.cat_id=t.cat_id + WHERE r.reply_id=? + "); + $stmt->bind_param('i', $reply_id); + $stmt->execute(); + + return $this->select_one_row($stmt); + } + + function get_reply_count_by_reply($reply) + { + $stmt = $this->db->prepare(" + SELECT COUNT(`reply_id`) AS count + FROM {$this->tables->threads_replies} + WHERE `reply` LIKE ? + "); + $like = "%$reply%"; + $stmt->bind_param('s', $like); + $stmt->execute(); + + return $this->select_one_row($stmt)['count']; + } + + function get_replies_by_reply($reply, $offset, $count) + { + $stmt = $this->db->prepare(" + SELECT + r.reply_id, + r.thread_id, + t.topic, + r.reply, + u.username, + r.time_replied + FROM {$this->tables->threads_replies} AS r + + INNER JOIN (SELECT thread_id, topic FROM {$this->tables->threads}) AS t + ON t.thread_id=r.thread_id + + INNER JOIN (SELECT user_id, username FROM {$this->tables->users}) AS u + ON u.user_id=r.user_id + + WHERE r.reply LIKE ? + ORDER BY r.time_replied DESC + LIMIT ?, ? + "); + $like = "%$reply%"; + $stmt->bind_param('sii', $like, $offset, $count); + $stmt->execute(); + + return $this->select_rows($stmt); + } + + function get_replies_by_user_id($user_id) + { + return $this->get_replies_by('r.user_id=?', $user_id, 0, -1); + } + + private function get_replies_by($where, $val, $offset, $count, $order_by='') + { + $sql = " + SELECT + r.user_id, + r.thread_id, + r.reply_id, + t.topic, + r.reply, + u.username, + r.time_replied, + r.first_post + FROM {$this->tables->threads_replies} AS r + + INNER JOIN (SELECT thread_id, topic FROM {$this->tables->threads}) AS t + ON t.thread_id=r.thread_id + + INNER JOIN (SELECT user_id, username FROM {$this->tables->users}) AS u + ON u.user_id=r.user_id + + WHERE $where"; + if ($order_by != '') + { + $sql .= "ORDER BY $order_by"; + } + if ($count != -1) + { + $sql .= ' LIMIT ?,?'; + } + $stmt = $this->db->prepare($sql); + if ($count != -1) + { + $stmt->bind_param('sii', $val, $offset, $count); + } + else + { + $stmt->bind_param('s', $val); + } + $stmt->execute(); + + return $this->select_rows($stmt); + } +} diff --git a/database/threads_views_database.php b/database/threads_views_database.php new file mode 100644 index 0000000..da15fef --- /dev/null +++ b/database/threads_views_database.php @@ -0,0 +1,135 @@ +db->prepare("INSERT INTO {$this->tables->threads_views}(thread_id, user_id, last_viewed, ip)VALUES(?,?,?,?)") or die($this->db->error); + $ip = IP_ADDRESS; + $stmt->bind_param('iiis', $thread_id, $user_id, time(), $ip); + $stmt->execute(); + } + + function increase_view($thread_id, $user_id) + { + $query = " + UPDATE {$this->tables->threads_views} + SET + times_viewed=times_viewed+1, + last_viewed=? + WHERE thread_id=? AND user_id=?"; + if ($user_id === GUEST_ID) + { + $query .= ' AND ip=?'; + } + $stmt = $this->db->prepare($query) or die($this->db->error); + if ($user_id === GUEST_ID) { + $ip = IP_ADDRESS; + $stmt->bind_param('iiis', time(), $thread_id, $user_id, $ip); + } else + $stmt->bind_param('iii', time(), $thread_id, $user_id); + + $stmt->execute(); + } + + /** + * Updates the time a thread was last viewed by a user. + * + * @param int The thread's database id. + * @param int The user's database id. + */ + function update_last_viewed($thread_id, $user_id) { + $query = "UPDATE {$this->tables->threads_views} SET last_viewed=? WHERE thread_id=? AND user_id=?"; + if ($user_id === GUEST_ID) { + $query .= ' AND ip=?'; + } + $stmt = $this->db->prepare($query) or die($this->db->error); + if ($user_id === GUEST_ID) { + $ip = IP_ADDRESS; + $stmt->bind_param('iiis', time(), $thread_id, $user_id, $ip); + } else { + $stmt->bind_param('iii', time(), $thread_id, $user_id); + } + $stmt->execute(); + } + + function user_has_viewed($thread_id, $user_id) + { + $stmt = null; + if ($user_id === GUEST_ID) + { + $stmt = $this->db->prepare("SELECT view_id FROM {$this->tables->threads_views} WHERE thread_id=? AND user_id=? AND ip=?") or die($this->db->error); + $ip = IP_ADDRESS; + $stmt->bind_param('iis', $thread_id, $user_id, $ip); + } + else + { + $stmt = $this->db->prepare("SELECT view_id FROM {$this->tables->threads_views} WHERE thread_id=? AND user_id=?") or die($this->db->error); + $stmt->bind_param('ii', $thread_id, $user_id); + } + $stmt->execute(); + + return $this->select_one_row($stmt) !== NULL; + } + + /** + * Returns the number of users not logged in current viewing the threads. + * + * @param int + */ + function get_number_of_guests($thread_id) + { + $stmt = $this->db->prepare(" + SELECT COUNT(view_id) AS guest_count + FROM {$this->tables->threads_views} + WHERE + user_id=? + AND thread_id=? + AND (? - last_viewed) < ?") or die($this->db->error); + $guest_id = GUEST_ID; + $sec_diff = 60; + $stmt->bind_param('iiii', $guest_id, $thread_id, time(), $sec_diff); + $stmt->execute(); + + return $this->select_one_row($stmt)['guest_count']; + } + + /** + * Returns a list of the usernames of the users currently viewing the thread. + * + * @param int + * @param int + */ + function get_current_viewers($thread_id, $exclude) + { + $query = " + SELECT u.username + FROM {$this->tables->threads_views} AS v + + INNER JOIN {$this->tables->users} AS u + ON u.user_id=v.user_id + + WHERE v.thread_id=? + AND (?-v.last_viewed) < ? + AND NOT v.user_id=?"; + if ($exclude !== NULL) + $query .= ' AND NOT v.user_id=?'; + $stmt = $this->db->prepare($query) or die($this->db->error); + + $sec_diff = 60; + $guest_id = GUEST_ID; + if ($exclude === null) + $stmt->bind_param('iiii', $thread_id, time(), $sec_diff, $guest_id); + else + $stmt->bind_param('iiiii', $thread_id, time(), $sec_diff, $guest_id, $exclude); + + $stmt->execute(); + + return $this->select_rows($stmt); + } +} diff --git a/database/user_database.php b/database/user_database.php new file mode 100644 index 0000000..b05f03d --- /dev/null +++ b/database/user_database.php @@ -0,0 +1,357 @@ +_add_user($username, $email, $salt, $password, False); + } + + function add_admin($username, $email, $salt, $password) + { + return $this->_add_user($username, $email, $salt, $password, True); + } + + private function _add_user($user, $email, $salt, $password, $is_admin) + { + $stmt = $this->db->prepare("INSERT INTO {$this->tables->users}(username, email, salt, password, joined, is_admin)VALUES(?,?,?,?,?,?)"); + $sec_joined = time(); + $is_admin = $is_admin ? 1 : 0; + $stmt->bind_param('ssssii', $user, $email, $salt, $password, $sec_joined, $is_admin); + $stmt->execute(); + + $user_id = $this->last_insert_id(); + + $meta_stmt = $this->db->prepare("INSERT INTO {$this->tables->usermeta}(user_id)VALUES(?)") or die($this->db->error); + $meta_stmt->bind_param('i', $user_id); + $meta_stmt->execute(); + + // Create user settings. + $setting_stmt = $this->db->prepare("INSERT INTO {$this->tables->users_settings}(user_id,num_threads_per_page,num_replies_per_page)VALUES(?,?,?)") or die($this->db->error); + $setting_stmt->bind_param('iii', $user_id, $GLOBALS['config']['default_num_threads_per_page'], $GLOBALS['config']['default_num_replies_per_page']); + $setting_stmt->execute(); + + return $user_id; + } + + function get_salt($login) + { + $stmt = $this->db->prepare("SELECT salt FROM {$this->tables->users} WHERE username=? OR email=?"); + if ( ! $stmt) die($this->db->error); + $stmt->bind_param('ss', $login, $login); + $stmt->execute(); + + $result = $this->select_one_row($stmt); + return $result !== NULL ? $result['salt'] : NULL; + } + + /** + * Fetches the user's database row id using the user's username or email and password. + * + * @param string The user's username or email + * @param string The user's password + */ + function get_user_id_by_login($login, $pass) + { + $stmt = $this->db->prepare("SELECT user_id FROM {$this->tables->users} WHERE (username=? OR email=?) AND password=?"); + if ( ! $stmt) die( $this->db->error ); + $stmt->bind_param('sss', $login, $login, $pass); + $stmt->execute(); + + $result = $this->select_one_row($stmt); + return $result !== NULL ? $result['user_id'] : NULL; + } + + function get_user_by_id($user_id) + { + $stmt = $this->db->prepare("SELECT username, email FROM {$this->tables->users} WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('i', $user_id); + $stmt->execute(); + + return $this->select_one_row($stmt); + } + + function get_user_meta($username) + { + $stmt = $this->db->prepare(" + SELECT + u.user_id, + u.username, + m.fullname, + m.birthdate, + m.picture_filename, + m.sex, + m.location, + u.joined, + u.is_banned, + u.last_active + FROM {$this->tables->users} AS u + + INNER JOIN {$this->tables->usermeta} AS m + ON m.user_id=u.user_id + + WHERE u.username=?") or die($this->db->error); + $stmt->bind_param('s', $username); + $stmt->execute(); + + return $this->select_one_row($stmt); + } + + function user_exists($field, $value) + { + $stmt = $this->db->prepare("SELECT user_id FROM {$this->tables->users} WHERE {$field}=?") or die($this->db->error); + $stmt->bind_param('s', $value); + $stmt->execute(); + return $this->select_one_row($stmt) !== NULL; + } + + function change_email($user_id, $email) + { + $this->_update($user_id, 'email', $email); + } + + function change_password($user_id, $password) + { + $this->_update($user_id, 'password', $password); + } + + function _update($user_id, $field, $value) + { + $stmt = $this->db->prepare("UPDATE {$this->tables->users} SET `$field`=? WHERE `user_id`=?") or die($this->db->error); + $stmt->bind_param('si', $value, $user_id); + $stmt->execute(); + } + + function update_meta($user_id, $array) + { + $fields = ''; + foreach (array_keys($array) as $k) + { + $fields .= "`$k`=?,"; + } + $fields = rtrim($fields, ','); + + $stmt = $this->db->prepare("UPDATE `{$this->tables->usermeta}` SET $fields WHERE `user_id`=?") or die($this->db->error); + + $type = ''; + for ($i = 0; $i < count($array); $i++) + { + $type .= 's'; + } + $type .= 'i'; // For user_id + + $values = array($type); + $values = array_merge($values, array_values($array)); + $values[] = $user_id; + for ($i = 0; $i < count($values); $i++) + { + $values[ $i ] =& $values[ $i ]; + } + call_user_func_array(array($stmt, 'bind_param'), $values); + + $stmt->execute(); + } + + function set_ban($user_id, $ban) { + $stmt = $this->db->prepare("UPDATE {$this->tables->users} SET is_banned=? WHERE user_id=?") or die($this->db->error); + $ban = ($ban == true ? 1 : 0); + $stmt->bind_param('ii', $ban, $user_id); + $stmt->execute(); + } + + function is_admin($user_id) { + $stmt = $this->db->prepare("SELECT is_admin FROM `{$this->tables->users}` WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('i', $user_id); + $stmt->execute(); + + return $this->select_one_row($stmt)['is_admin'] === 1; + } + + function is_banned($user_id) { + $stmt = $this->db->prepare("SELECT is_banned FROM `{$this->tables->users}` WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('i', $user_id); + $stmt->execute(); + + return $this->select_one_row($stmt)['is_banned'] == 1; + } + + function get_moderators($sort_by, $username_like='') + { + $sql = " + SELECT + u.username, + m.cat_id, + c.cat, + u.joined + FROM {$this->tables->moderators} AS m + INNER JOIN {$this->tables->users} AS u + ON u.user_id=m.mod_id + INNER JOIN {$this->tables->categories} AS c + ON c.cat_id=m.cat_id + "; + + + if ($username_like != null && $username_like != '') + { + $sql .= ' AND u.username LIKE ?'; + } + + if ($sort_by == 'az') + { + $sql .= ' ORDER BY u.username ASC'; + } + elseif ($sort_by == 'za') + { + $sql .= ' ORDER BY u.username DESC'; + } + elseif ($sort_by == 'time') + { + $sql .= ' ORDER BY u.joined ASC'; + } + + $stmt = $this->db->prepare($sql) or $this->log_error(__FILE__, __LINE__); + if ($username_like != null && $username_like != '') + { + $username_like .= '%'; + $stmt->bind_param('s', $username_like); + } + $stmt->execute(); + + return $this->select_rows($stmt); + } + + function is_moderator($user_id, $cat_id=0) + { + $sql = "SELECT id FROM {$this->tables->moderators} WHERE mod_id=?"; + if ($cat_id != 0) + { + $sql .= ' AND cat_id=?'; + } + $stmt = $this->db->prepare($sql) or $this->log_error(__FILE__, __LINE__); + if ($cat_id != 0) + { + $stmt->bind_param('ii', $user_id, $cat_id); + } + else + { + $stmt->bind_param('i', $user_id); + } + $stmt->execute(); + + return $this->select_one_row($stmt) !== null; + } + + /** + * Adds a moderator. + * + * @since 1.0 + */ + function add_moderator($user_id, $cat_id) + { + $stmt = $this->db->prepare("INSERT INTO {$this->tables->moderators}(mod_id,cat_id)VALUES(?,?)") or $this->log_error(__FILE__, __LINE__); + $stmt->bind_param('ii', $user_id, $cat_id); + $stmt->execute(); + } + + /** + * Removes a moderator from a category. + * + * @since 1.0 + * + * @param + * @param + */ + function remove_moderator($user_id, $cat_id=0) + { + $sql = "DELETE FROM {$this->tables->moderators} WHERE mod_id=?"; + if ($cat_id != 0) + { + $sql .= " AND cat_id=?"; + } + $stmt = $this->db->prepare($sql) or $this->log_error(__FILE__, __LINE__); + if ($cat_id == 0) + { + $stmt->bind_param('i', $user_id); + } + else + { + $stmt->bind_param('ii', $user_id, $cat_id); + } + $stmt->execute(); + } + + function get_moderator_cat_ids($user_id) + { + $stmt = $this->db->prepare("SELECT cat_id FROM {$this->tables->moderators} WHERE mod_id=?") or die(); + $stmt->bind_param('i', $user_id); + $stmt->execute(); + + $cat_ids = array(); + $rows = $this->select_rows($stmt); + if ($rows != null) + { + foreach ($rows as $r) + { + $cat_ids[] = $r->cat_id; + } + } + return $cat_ids; + } + + function get_all_users($admin_id, $sort_by, $username_like) + { + $sql = "SELECT last_active, user_id, username, joined, is_banned + FROM {$this->tables->users} + WHERE NOT user_id=?"; + + if ($username_like != null && $username_like != '') + { + $sql .= ' AND username LIKE ?'; + } + + switch ($sort_by) + { + case 'az': + $sql .= ' ORDER BY username ASC'; + break; + case 'za': + $sql .= ' ORDER BY username DESC'; + break; + case 'time': + $sql .= ' ORDER BY joined ASC'; + break; + } + $stmt = $this->db->prepare($sql) or $this->log_error(__FILE__, __LINE__); + if ($username_like != '' && $username_like != null) + { + $username_like .= '%'; + $stmt->bind_param('is', $admin_id, $username_like); + } + else + { + $stmt->bind_param('i', $admin_id); + } + $stmt->execute(); + return $this->select_rows($stmt); + } + + function remove_user($user_id) + { + $stmt = $this->db->prepare("DELETE FROM {$this->tables->users} WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('i', $user_id); + $stmt->execute(); + } +} diff --git a/database/user_session_database.php b/database/user_session_database.php new file mode 100644 index 0000000..016e4c0 --- /dev/null +++ b/database/user_session_database.php @@ -0,0 +1,49 @@ +db->prepare("INSERT INTO {$this->tables->users_sessions}(session_id, user_id, user_agent, ip_addr, time_created)VALUES(?,?,?,?,?)") or die($this->db->error); + $sess_created = time(); + $stmt->bind_param('sissi', $session_id, $user_id, $user_agent, $ip, $sess_created); + $stmt->execute(); + } + + function get_sess_info($session_id) + { + $stmt = $this->db->prepare("SELECT user_id, user_agent, ip_addr FROM {$this->tables->users_sessions} WHERE session_id=?") or die($this->db->error); + $stmt->bind_param('s', $session_id); + $stmt->execute(); + + return $this->select_one_row($stmt); + } + + function get_last_active($user_id) + { + $stmt = $this->db->prepare("SELECT last_active FROM {$this->tables->users} WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('i', $user_id); + $stmt->execute(); + + return $this->select_one_row($stmt)['last_active']; + } + + function set_last_active($sec_last_active, $user_id) + { + $stmt = $this->db->prepare("UPDATE {$this->tables->users} SET last_active=? WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('ii', $sec_last_active, $user_id); + $stmt->execute(); + } + + function delete_sess_record($session_id) + { + $stmt = $this->db->prepare("DELETE FROM {$this->tables->users_sessions} WHERE session_id=?") or die($this->db->error); + $stmt->bind_param('s', $session_id); + $stmt->execute(); + } +} diff --git a/database/users_settings_database.php b/database/users_settings_database.php new file mode 100644 index 0000000..6444d57 --- /dev/null +++ b/database/users_settings_database.php @@ -0,0 +1,56 @@ +user_id = $user_id; + } + + function set_num_replies_per_page($value) + { + $this->_change_setting('num_replies_per_page', $value); + } + + function set_num_threads_per_page($value) + { + $this->_change_setting('num_threads_per_page', $value); + } + + function get_num_replies_per_page() + { + return $this->_get_setting('num_replies_per_page'); + } + + function get_num_threads_per_page() + { + return $this->_get_setting('num_threads_per_page'); + } + + function _change_setting($setting, $value) + { + $stmt = $this->db->prepare("UPDATE {$this->tables->users_settings} SET $setting=? WHERE user_id=?") or die($this->db->error); + + $type = 'i'; + if (is_int($value)) + { + $type .= 'i'; + } + elseif (is_string($value)) + { + $type .= 's'; + } + $stmt->bind_param($type, $value, $this->user_id); + $stmt->execute(); + } + + function _get_setting($setting) + { + $stmt = $this->db->prepare("SELECT `$setting` FROM {$this->tables->users_settings} WHERE user_id=?") or die($this->db->error); + $stmt->bind_param('i', $this->user_id); + $stmt->execute(); + + return $this->select_one_row($stmt)[$setting]; + } +} From 8420ca080fd64fe1adc6c5a7961b3ec25451b7b6 Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:21:29 +0100 Subject: [PATCH 06/12] Add files via upload --- form/auth.php | 51 +++++++++++ form/create_thread.php | 66 +++++++++++++ form/delete_thread.php | 52 +++++++++++ form/edit_profile.php | 204 +++++++++++++++++++++++++++++++++++++++++ form/reply_action.php | 81 ++++++++++++++++ form/reply_thread.php | 54 +++++++++++ form/settings.php | 184 +++++++++++++++++++++++++++++++++++++ form/signup.php | 99 ++++++++++++++++++++ form/user_ban.php | 49 ++++++++++ 9 files changed, 840 insertions(+) create mode 100644 form/auth.php create mode 100644 form/create_thread.php create mode 100644 form/delete_thread.php create mode 100644 form/edit_profile.php create mode 100644 form/reply_action.php create mode 100644 form/reply_thread.php create mode 100644 form/settings.php create mode 100644 form/signup.php create mode 100644 form/user_ban.php diff --git a/form/auth.php b/form/auth.php new file mode 100644 index 0000000..fbd1058 --- /dev/null +++ b/form/auth.php @@ -0,0 +1,51 @@ +get_salt($login)) !== NULL) + { + require($config['includes_path'] . '/password.php'); + + $pass = hash_password($salt, $pass); + $user_id = $user_db->get_user_id_by_login($login, $pass); + if ($user_id == NULL) + { + $error->add('login', 'login', 'Incorrect password'); + $error->add_field_value('login', 'login', $login); + $error_key = $error->save(); + header('Location: ' . $config['http_host'] . '?e_k=' . $error_key); + } + else + { + $sess->start_new_sess($user_id, isset($_POST['remember'])); + + $redirect_url = $config['http_host']; + if (isset($_POST['ref'])) + { + $redirect_url .= '/'.urldecode(trim($_POST['ref'])); + } + header('Location: ' . $redirect_url); + } + } + else + { + $error->add('login', 'login', 'Incorrect username/e-mail'); + $error->add_field_value('login', 'login', $login); + $error_key = $error->save(); + header('Location: ' . $config['http_host'] . '?e_k=' . $error_key); + } +} +else +{ + header('Location: ' . $config['http_host'] . '/index.php'); +} diff --git a/form/create_thread.php b/form/create_thread.php new file mode 100644 index 0000000..2e90068 --- /dev/null +++ b/form/create_thread.php @@ -0,0 +1,66 @@ +get_cat_by_id($_GET['cat_id']) === NULL) + { + header('Location: ' . $config['http_host']); + die(); + } + + if (strlen(trim($_POST['topic'])) < $config['min_thread_topic_length']) + { + $error->add('thread', 'topic', 'Topic should be at least ' . $config['min_thread_topic_length'] . ' characters long'); + } + elseif (strlen(trim($_POST['topic'])) > 150) + { + $error->add('thread', 'topic', 'Topic should not exceed 150 characters.'); + } + + if (trim($_POST['post']) === '') + { + $error->add('thread', 'body', 'Body cannot be empty'); + } + + require($config['__'] . '/attachments.php'); + if (!validate_attachments()) + { + $error->add('thread', 'attachment', $attachment_error); + } + + if ($error->exists_group('thread')) + { + $error->add_field_value('thread', 'topic', trim($_POST['topic'])); + $error->add_field_value('thread', 'body', trim($_POST['post'])); + $error_key = $error->save(); + header('Location: ' . $config['http_host'] . '/create_thread.php?cat_id=' . $_GET['cat_id'] . '&e_k=' . $error_key); + } + else + { + require($config['database_path'] . '/threads_database.php'); + require($config['database_path'] . '/threads_replies_database.php'); + + $threads_db = new Threads_Database(); + $thread_id = $threads_db->add_new_thread($sess_info['user_id'], $_GET['cat_id'], trim($_POST['topic'])); + + $threads_replies_db = new Threads_replies_database(); + $reply_id = $threads_replies_db->add_reply($thread_id, $sess_info['user_id'], trim($_POST['post']), true); + + save_attachments('thread'.$thread_id.'/reply'.$reply_id); + + header('Location: ' . $config['http_host'] . '/view.php?id=' . $thread_id); + } +} +else +{ + header('Location: ' . $config['http_host']); +} diff --git a/form/delete_thread.php b/form/delete_thread.php new file mode 100644 index 0000000..927be24 --- /dev/null +++ b/form/delete_thread.php @@ -0,0 +1,52 @@ +get_thread_meta($_GET['id']); + if ($thread === NULL) + { + header('Location: ' . $config['http_host']); + } + else + { + $base_cat_id = $cat->get_base_cat($thread['cat_id']); + $is_moderator = $user_db->is_moderator($sess_info['user_id'], $base_cat_id); + if (USER_IS_ADMIN || $is_moderator) + { + $threads_db->delete_thread($_GET['id']); + + // Delete attachments + require($config['__'] . '/file.php'); + $attachment_path = $config['attachment_path'] . '/thread'.$_GET['id']; + delete_folder($attachment_path); + } + + $redirect_url = $config['http_host']; + if (isset($_GET['ref'])) + { + $redirect_url .= '/' . $_GET['ref']; + } + else + { + $redirect_url .= '/threads.php?cat_id=' . $thread['cat_id']; + } + header("Location: $redirect_url"); + } + } + else + { + header('Location: ' . $config['http_host']); + } +} +else +{ + header('Location: ' . $config['http_host']); +} + diff --git a/form/edit_profile.php b/form/edit_profile.php new file mode 100644 index 0000000..e6209d4 --- /dev/null +++ b/form/edit_profile.php @@ -0,0 +1,204 @@ + $h) { + $s = $h; + } else { + $s = $w; + } + + $cropped = imagecreatetruecolor($s, $s); + if ($w > $h) + { + $src_x = ($w / 2) - ($s / 2); + imagecopyresampled($cropped, $image, 0, 0, $src_x, 0, $s, $s, $s, $s); + } + else + { + $src_y = ($h / 2) - ($s / 2); + imagecopyresampled($cropped, $image, 0, 0, 0, $src_y, $s, $s, $s, $s); + } + + $filename = generate_filename() . '.jpg'; + imagejpeg($cropped, $GLOBALS['config']['user_picture_path'] . '/' . $filename, 100); + return $filename; +} + +if (USER_IS_LOGGED_IN) +{ + $edit = array(); + + foreach ($_POST as $k => $v) + { + $_POST[$k] = trim($v); + } + + // Validate sex + if (isset($_POST['sex'])) + { + if (in_array(strtolower($_POST['sex']), array('female', 'male'))) + { + $edit['sex'] = $_POST['sex']; + } + } + + // Validate birthdate + if (isset($_POST['birthday']) && isset($_POST['birthmonth']) && isset($_POST['birthyear'])) + { + $day = 0; + $month = 0; + $year = 0; + + if (preg_match('~^\d+$~', $_POST['birthday']) && $_POST['birthday'] > 0 && $_POST['birthday'] <= 31) + { + $day = $_POST['birthday']; + } + + if (preg_match('~^\d+$~', $_POST['birthmonth']) && $_POST['birthmonth'] > 0 && $_POST['birthmonth'] <= 12) + { + $month = $_POST['birthmonth']; + } + + if (preg_match('~^\d{4}$~', $_POST['birthyear']) && $_POST['birthyear'] >= 1940 && $_POST['birthyear'] <= date('Y')) + { + $year = $_POST['birthyear']; + } + + if ($day !== 0 && $month !== 0 && $year !== 0) + { + // Check if day has exceeded numbers of days for the month + $months_max_days = array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + if ($day <= $months_max_days[$month - 1]) + { + $edit['birthdate'] = "$year:$month:$day"; + } + else + { + $months = array('January', 'February', 'March', + 'April', 'May', 'June', + 'July', 'August', 'September', + 'October', 'November', 'December'); + $error->add('edit_profile', 'birthdate', 'Day is more than the number of days in ' . $months[ $month - 1 ]); + } + } + } + + // Validate fullname + if (isset($_POST['fullname']) && $_POST['fullname'] !== '') + { + if (preg_match('/^[a-zA-Z]+(\s+[a-zA-Z]+)*$/', $_POST['fullname'])) + { + $edit['fullname'] = preg_replace('/\s+/', ' ', $_POST['fullname']); + } + else + { + $error->add('edit_profile', 'fullname', 'Invalid fullname!'); + } + } + + // Validate location + if (isset($_POST['location']) && $_POST['location'] !== '') + { + $edit['location'] = $_POST['location']; + } + + // Validate profile picture + if (isset($_FILES['profile_pic'])) + { + require($config['__'] . '/file.php'); + + if ($_FILES['profile_pic']['error'] !== 4) + { + if ($_FILES['profile_pic']['error'] === 0) + { + $ext = pathinfo($_FILES['profile_pic']['name'], PATHINFO_EXTENSION); + + if ( ! in_array(strtolower($ext), $config['user_photo_extensions'])) + { + $error->add('edit_profile', 'profile_pic', 'Only jpeg, png and gif images allowed.'); + } + elseif ($_FILES['profile_pic']['size'] > $config['user_photo_max_size']) + { + $error->add('edit_profile', 'profile_pic', 'Photo should not exceed ' . convert_bytes($config['user_photo_max_size'])); + } + else + { + if ( ! file_exists($config['user_picture_path'])) + { + create_path($config['user_picture_path']); + } + + // Delete user's current profile picture + $current_pic = $user_db->get_user_meta(USER_NICK)['picture_filename']; + if ($current_pic !== null && file_exists($config['user_picture_path'].'/'.$current_pic)) { + unlink($config['user_picture_path'] . '/' . $current_pic); + } + + // Crop and save newly uploaded photo + $filename = crop_image($ext, $_FILES['profile_pic']['tmp_name']); + + $edit['picture_filename'] = $filename; + } + } elseif ($_FILES['profile_pic']['error'] === 1) { + $error->add('edit_profile', 'profile_pic', 'Photo should not exceed ' . convert_bytes($config['user_photo_max_size'])); + } + } + } + + if ($error->exists_group('edit_profile')) { + $fields = array('fullname', 'location'); + foreach ($fields as $f) { + if (isset($_POST[$f])) { + $error->add_field_value('edit_profile', $f, $_POST[$f]); + } + } + + $error_key = $error->save(); + redirect_to('edit_profile.php?e_k='.$error_key); + } else { + $user_db->update_meta($sess_info['user_id'], $edit); + redirect_to('profile.php'); + } +} +else +{ + redirect_to('profile.php'); +} diff --git a/form/reply_action.php b/form/reply_action.php new file mode 100644 index 0000000..fead562 --- /dev/null +++ b/form/reply_action.php @@ -0,0 +1,81 @@ +get_reply_by_id($_GET['id']); + + // If reply does not exists. + if ($reply === NULL) + { + header('Location: ' . $config['http_host']); + die(); + } + + if (isset($_GET['a']) && in_array(strtolower($_GET['a']), array('edit', 'delete'))) + { + switch (strtolower($_GET['a'])) + { + case 'edit': + if (!isset($_POST['reply'])) + { + header('Location: ' . $config['http_host'] . '/view.php?id=' . $reply['thread_id']); + die(); + } + + // If the logged in user is not the user that posted the reply. To avoid editing + // someone else's reply. + if ($reply['user_id'] !== $sess_info['user_id']) + { + header('Location: ' . $config['http_host'] . '/view.php?id=' . $reply['thread_id']); + die(); + } + + if (trim($_POST['reply']) === '') + { + $error->add('edit_reply', 'reply', 'Reply cannot be empty'); + $error_key = $error->save(); + header('Location: ' . $config['http_host'] . '/edit_reply.php?id=' . $_GET['id'] . '&e_k=' . $error_key); + } + else + { + $reply_db->edit_reply($_GET['id'], trim($_POST['reply'])); + header('Location: ' . $config['http_host'] . '/view.php?id=' . $reply['thread_id']); + } + + break; + case 'delete': + $base_cat_id = $cat->get_base_cat($reply['cat_id']); + $is_moderator = $user_db->is_moderator($sess_info['user_id'], $base_cat_id); + if (USER_IS_ADMIN || $is_moderator) + { + $reply_db->delete_reply($_GET['id']); + // Delete attachments + require($config['__'] . '/file.php'); + $attachment_path = $config['attachment_path'].'/thread'.$reply['thread_id'].'/reply'.$_GET['id']; + delete_folder($attachment_path); + } + header('Location: ' . $config['http_host'] . '/view.php?id=' . $reply['thread_id']); + + break; + } + } + else + { + header('Location: ' . $config['http_host'] . '/view.php?id=' . $reply['thread_id']); + } +} +else +{ + header('Location: ' . $config['http_host']); +} diff --git a/form/reply_thread.php b/form/reply_thread.php new file mode 100644 index 0000000..da780bd --- /dev/null +++ b/form/reply_thread.php @@ -0,0 +1,54 @@ +get_thread_meta($_GET['id']) !== NULL) + { + if (trim($_POST['reply']) === '') + { + $error->add('reply_thread', 'reply', 'Reply cannot be empty'); + } + + require($config['__'] . '/attachments.php'); + if (!validate_attachments()) + { + $error->add('reply_thread', 'attachments', $attachment_error); + } + + if (!$error->exists_group('reply_thread')) + { + require($config['database_path'] . '/threads_replies_database.php'); + + $replies_db = new Threads_replies_database(); + $reply_id = $replies_db->add_reply($_GET['id'], $sess_info['user_id'], trim($_POST['reply']), false); + + save_attachments('thread'.$_GET['id'].'/reply'.$reply_id); + + $redirect_url .= '&page=l#r' . $reply_id; + } + else + { + $error->add_field_value('reply_thread', 'reply', $_POST['reply']); + $error_key = $error->save(); + $redirect_url .= '&e_k=' . $error_key; + } + } + + header('Location: ' . $redirect_url); +} +else +{ + header('Location: ' . $config['http_host']); +} diff --git a/form/settings.php b/form/settings.php new file mode 100644 index 0000000..98ceb17 --- /dev/null +++ b/form/settings.php @@ -0,0 +1,184 @@ +get_user_id_by_login(USER_EMAIL, $password) === $sess_info['user_id']; +} + +$redirect_url = ''; + +if ( ! USER_IS_LOGGED_IN) +{ + $redirect_url = $config['http_host']; +} +else +{ + if (isset($_GET['a'])) + { + if ($_GET['a'] === 'ch_email') + { + if (isset($_POST['current_email']) && isset($_POST['new_email']) && isset($_POST['pass'])) + { + $current_email = trim($_POST['current_email']); + $new_email = trim($_POST['new_email']); + + // Validate user's current email. + if ($current_email === '') + { + $error->add('ch_email', 'current_email', 'Cannot be empty'); + } + elseif ($current_email !== USER_EMAIL) + { + $error->add('ch_email', 'current_email', 'Email address incorrect'); + } + + // Validate user's new email. + if ($new_email === '') + { + $error->add('ch_email', 'new_email', 'Cannot be empty'); + } + elseif (!filter_var($new_email, FILTER_VALIDATE_EMAIL)) + { + $error->add('ch_email', 'new_email', 'Email address is not valid.'); + } + elseif ($new_email === USER_EMAIL) + { + $error->add('ch_email', 'new_email', 'Cannot be the same as your current email.'); + } + elseif ($user_db->user_exists('email', $new_email)) + { + $error->add('ch_email', 'new_email', 'Email address already used by another user.'); + } + + // Validate password. + if ($_POST['pass'] !== '') + { + $salt = $user_db->get_salt(USER_EMAIL); + + if (!validate_password($salt, $_POST['pass'])) { + $error->add('ch_email', 'pass', 'Password incorrect'); + } + } + else + { + $error->add('ch_email', 'pass', 'Cannot be empty'); + } + + if ($error->exists_group('ch_email')) + { + $error->add_field_value('ch_email', 'current_email', $current_email); + $error->add_field_value('ch_email', 'new_email', $new_email); + + $error_key = $error->save(); + $redirect_url = $config['http_host'] . '/settings.php?a=ch_email&e_k=' . $error_key; + } + else + { + $user_db->change_email($sess_info['user_id'], $new_email); + + $redirect_url = $config['http_host'] . '/settings.php?a=ch_email'; + } + } + else + { + $redirect_url = $config['http_host'] . '/settings.php?a=ch_email'; + } + } + elseif ($_GET['a'] === 'ch_password') + { + $salt = $user_db->get_salt(USER_EMAIL); + + $current_password = $_POST['pass']; + $new_password = $_POST['new_pass']; + $re_new_password = $_POST['re_new_pass']; + + // Validate user's current password + if ($current_password === '') + { + $error->add('ch_password', 'current_password', 'Cannot be empty'); + } + elseif (!validate_password($salt, $current_password)) + { + $error->add('ch_password', 'current_password', 'Incorrect password'); + } + + // Validate user's new password + if ($new_password === '') + { + $error->add('ch_password', 'new_password', 'Cannot be empty'); + } + elseif (strlen($new_password) < 8) + { + $error->add('ch_password', 'new_password', 'Must be at least 8 characters'); + } + elseif (strtolower(trim($new_password)) === USER_NICK) + { + $error->add('ch_password', 'new_password', 'Cannot be the same as your username'); + } + + // Validate re-entered new password + if ($re_new_password === '') + { + $error->add('ch_password', 're_new_password', 'Cannot be empty'); + } + elseif ($re_new_password !== $new_password) + { + $error->add('ch_password', 're_new_password', 'Does not match password provided above.'); + } + + if ($error->exists_group('ch_password')) + { + $error_key = $error->save(); + $redirect_url = $config['http_host'] . '/settings.php?a=ch_password&e_k=' . $error_key; + } + else + { + $new_password = hash('sha1', $salt.$new_password); + $user_db->change_password($sess_info['user_id'], $new_password); + + // User have to log in again with their new password after their + // password has been changed. + $sess->end_current_session(); + + $redirect_url = $config['http_host']; + } + } + elseif ($_GET['a'] === 'thr_pagination') + { + require($config['database_path'] . '/users_settings_database.php'); + $user_setting_db = new Users_settings_database($sess_info['user_id']); + + if (isset($_POST['threads']) + && preg_match('~^[0-9]+$~', $_POST['threads']) + && $_POST['threads'] >= 1 && $_POST['threads'] <= 14) + { + $user_setting_db->set_num_threads_per_page($_POST['threads']); + } + + if (isset($_POST['replies']) + && preg_match('~^[0-9]+$~', $_POST['replies']) + && $_POST['replies'] >= 1 && $_POST['replies'] <= 14) + { + $user_setting_db->set_num_replies_per_page($_POST['replies']); + } + + $redirect_url = $config['http_host'] . '/settings.php?a=thr_pagination'; + } + else + { + $redirect_url = $config['http_host'] . '/settings.php?a=' . urlencode($_GET['a']); + } + } + else + { + + } +} + +header('Location: ' . $redirect_url); diff --git a/form/signup.php b/form/signup.php new file mode 100644 index 0000000..3740cee --- /dev/null +++ b/form/signup.php @@ -0,0 +1,99 @@ +add_user($username, $email, $salt, $password); + + // Start user session. + // $sess declared in constants.php + $sess->start_new_sess($user_id, True); + + header('Location: ' . $config['http_host'] . '/index.php'); + } + else + { + $error->add_field_value('signup', 'username', $username); + $error->add_field_value('signup', 'email', $email); + $error->add_field_value('signup', 'password', $password); + $error->add_field_value('signup', 're_password', $re_password); + $error_key = $error->save(); + header('Location: ' . $config['http_host'] . '/signup.php?e_k=' . $error_key); + } + } +} + +function validate($username, $email, $password, $re_password) +{ + global $error, $user_db; + + // Validate username. + if (strlen($username) < 2 || strlen($username) > 12) + { + $error->add('signup', 'username', 'Username must be between 2 - 12 characters long.'); + } + elseif (!preg_match('/^[a-zA-Z][a-zA-Z_0-9]+$/', $username)) + { + $error->add('signup', 'username', 'Username must begin with a letter, followed by either a letter, a number or _.'); + } + elseif ($user_db->user_exists('username', $username)) + { + $error->add('signup', 'username', 'Username already used by another user.'); + } + + // Validate password. + if (strlen($password) < 8) + { + $error->add('signup', 'password', 'Password must be at least 8 characters long.'); + } + elseif (strtolower($password) == strtolower($username)) + { + $error->add('signup', 'password', 'You cannot use your username as password.'); + } + + if ($password !== $re_password) + { + $error->add('signup', 're_password', 'Password does not match the one provided above.'); + } + + // Validate email address. + if ($email == '') + { + $error->add('signup', 'email', 'Email address cannot be empty.'); + } + elseif ( ! filter_var($email, FILTER_VALIDATE_EMAIL)) + { + $error->add('signup', 'email', 'Email address not valid.'); + } + elseif ($user_db->user_exists('email', $email)) + { + $error->add('signup', 'email', 'Email address already used by another user.'); + } + + if (!isset($_POST['terms_accepted'])) + { + $error->add('signup', 'terms_acceptance', 'You must accept the terms and conditions.'); + } + + return !$error->exists_group('signup'); +} diff --git a/form/user_ban.php b/form/user_ban.php new file mode 100644 index 0000000..956088b --- /dev/null +++ b/form/user_ban.php @@ -0,0 +1,49 @@ +is_moderator($sess_info['user_id']))) +{ + redirect(); +} + +if (isset($_GET['u'])) +{ + $meta = $user_db->get_user_meta($_GET['u']); + if ($meta !== null) + { + // The admin cannot be banned. + // User cannot ban himself. + if ( ! $user_db->is_admin($meta['user_id']) && $meta['user_id'] != $sess_info['user_id']) + { + $ban_status = ! $meta['is_banned']; + $user_db->set_ban($meta['user_id'], $ban_status); + } + } + + redirect(); +} +else +{ + redirect(); +} + +function redirect() +{ + global $config; + + if (isset($_GET['ref'])) + { + header("Location: $config[http_host]/$_GET[ref]"); + } + elseif (isset($_GET['u'])) + { + header("Location: $config[http_host]/profile.php?u=".$_GET['u']); + } + else + { + header("Location: $config[http_host]"); + } + die(); +} From 3f9e9822501ce5596179606b8e4933a8a3824de9 Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:23:59 +0100 Subject: [PATCH 07/12] Add files via upload --- images/button-search.png | Bin 0 -> 479 bytes images/default_avatar.png | Bin 0 -> 992 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/button-search.png create mode 100644 images/default_avatar.png diff --git a/images/button-search.png b/images/button-search.png new file mode 100644 index 0000000000000000000000000000000000000000..c89b0441b93893f9cd1c89f981bebdc7422cf9f4 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@)1s;*b3=CYaL71_rn>Pw5$dc~p z>&U>cv9IQL;A9|QA=x9ymw};5m4Tt5nStTwe<1ymfuYoZf#FpG1B2BJ1_tr`N%2SB z7#J8sJzX3_DsCnH;b-9qD0p~?we9uw^~N7Qd@z_XV}^l;mzPljGXvuhj!R$u{48#} zbSbEPW%wZ@C+eJ$sfW;tMqB!qux^?;k(@{QolF+5Z+dA3Ef;;m+pt^8rT~nL0S1nAFwP zvBk&7^K3tU^l0NtW_G>>Ak#0e4qq>EVDD6cri0c>TIr^Z<|ZH4XuXo)el( z(hRP8Cmd3G85msxgf%V|7Zp7?w{`R8WCjieH8v)OG^-Qq6&}2Lb&5eCfg$7X${z+D SAEJSg!QkoY=d#Wzp$P!l^0}4( literal 0 HcmV?d00001 diff --git a/images/default_avatar.png b/images/default_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..6af480cbfcb1eed7ae69044149a3e09270ff3c9c GIT binary patch literal 992 zcmV<610Vc}P)TCIR(Ef8P3aNA;am_9Smf&TNMpB( zNIe?7^hnGEhzRtVBw9o~KYK;Qdg?AMGPcDb8M)KtX_2A1fyli$W3))$I7H;$&b_oq z&m`@BD|!81ph6Fpo9ddk-n*m$jz_3w8+R(P#VIAVVM${9R`1f(S1D>2R*ctcZMC4hYphx8M6CyI&YcN0( znmGw6ng$Gz_SYG~SO(lv2FO&CBmI)28;JoT9i9=smuyMN3=k00jaMV(of->7ZtGVs zUkis*WP(UHDgohxjW0Py_mko7$qOF2vs-425IN43u^()iU$(L|pKXNXyb=iexO9;Z=1EnIc8&9}!q^ zO_(A{9FjYjvP7igV~ANCt$l_FC~akM7dcII$_NpM8TYp37K1#UbH=Os6i_3>%n7G* z7XU<<9{UmFSA(x9P$8oTzvoKbLc*DT;5BHD8q&ImP`mlAy68MXL~J2s^`9--Ay@+u z%O|_0(8Cfb2VWb`9ZPilQDmIAeYd1!41w>talWPT^u-S#)2wC7UVp?8gtwUlxV`HZ zcqamy4$j$pMITNK>-IvEcOgPprK>)oby`fXzFPb5L4xYb2efXm!MTRYNb&Ra6I#Dz zk@Em0Wa_lOAe!a@ouGu&wPlE?ntbglAxU0Bbj^1P3JAD4enXVm!U~Aw4nbPv@7^fmY)3F+%wh`#Y&n}jq>6GY#WCQm~0775X}vAAB@7RcC8mR2Jtb;tsFa8?M&Vf=*!GPC~XO@0QGWB3~7*B|cy O0000 Date: Sun, 17 Sep 2017 00:24:58 +0100 Subject: [PATCH 08/12] Add files via upload --- includes/category_hierarchy.php | 13 +++ includes/constants.php | 64 +++++++++++++ includes/current_cat_meta.php | 17 ++++ includes/fonts/arial.TTF | Bin 0 -> 45260 bytes includes/footer.php | 16 ++++ includes/functions/time.php | 40 ++++++++ includes/functions/user.php | 10 ++ includes/header.php | 164 ++++++++++++++++++++++++++++++++ includes/pages.php | 15 +++ includes/password.php | 18 ++++ includes/right-sections.php | 14 +++ includes/time.php | 37 +++++++ 12 files changed, 408 insertions(+) create mode 100644 includes/category_hierarchy.php create mode 100644 includes/constants.php create mode 100644 includes/current_cat_meta.php create mode 100644 includes/fonts/arial.TTF create mode 100644 includes/footer.php create mode 100644 includes/functions/time.php create mode 100644 includes/functions/user.php create mode 100644 includes/header.php create mode 100644 includes/pages.php create mode 100644 includes/password.php create mode 100644 includes/right-sections.php create mode 100644 includes/time.php diff --git a/includes/category_hierarchy.php b/includes/category_hierarchy.php new file mode 100644 index 0000000..2e97c13 --- /dev/null +++ b/includes/category_hierarchy.php @@ -0,0 +1,13 @@ +get_hierarchy($cat_id); + + $h['Home'] = $GLOBALS['config']['http_host']; + foreach ($hierarchy as $parent) { + $h[ htmlspecialchars($parent->name) ] = $GLOBALS['config']['http_host'] . '/threads.php?cat_id=' . $parent->id; + } + $h[ htmlspecialchars($cat_name) ] = $GLOBALS['config']['http_host'] . '/threads.php?cat_id=' . $cat_id; + + $GLOBALS['theme']->category_hierarchy($h); +} +?> \ No newline at end of file diff --git a/includes/constants.php b/includes/constants.php new file mode 100644 index 0000000..a6dc814 --- /dev/null +++ b/includes/constants.php @@ -0,0 +1,64 @@ +is_logged_in(); + +define('USER_IS_LOGGED_IN', ($sess_info !== False)); + +require($config['database_path'] . '/user_database.php'); +$user_db = new User_Database(); + +if (USER_IS_LOGGED_IN) +{ + // Update session last active + $sess->update_last_online($sess_info['user_id']); + + $user = $user_db->get_user_by_id($sess_info['user_id']); + define('USER_NICK', $user['username']); + define('USER_EMAIL', $user['email']); + + define('USER_IS_ADMIN', $user_db->is_admin($sess_info['user_id'])); + + // Admin cannot be banned. + if ($user_db->is_banned($sess_info['user_id']) && ! USER_IS_ADMIN) + { + define('USER_IS_BANNED', true); + } + else + { + define('USER_IS_BANNED', false); + } +} + +// If user is banned, redirect the user to 'banned.php'. +if (USER_IS_LOGGED_IN && USER_IS_BANNED && pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_BASENAME) !== 'banned.php') +{ + header('Location: '.$config['http_host'].'/banned.php'); + die(); +} + +define('IP_ADDRESS', $_SERVER['REMOTE_ADDR']); + +require($config['database_path'] . '/thread_category.php'); +$cat = new Thread_Category(); + +require($config['__'] . '/error_message.php'); +$error = new Error_message(); + +// Clear current_thread cookie so if user leaves a thread to another page and comes back to it, +// it counts as another view. +if (pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_BASENAME) !== 'view.php') +{ + setcookie('current_thread', '', time() - 3600); +} + +require($config['includes_path'].'/functions/user.php'); + +require($config['__'] . '/Theme_manager.php'); +$theme = Theme_manager::load_theme($config['site_theme']); +if ($theme == null) +{ + die('Unable to load theme'); +} diff --git a/includes/current_cat_meta.php b/includes/current_cat_meta.php new file mode 100644 index 0000000..f1cf2dd --- /dev/null +++ b/includes/current_cat_meta.php @@ -0,0 +1,17 @@ +get_cat_by_id($_GET['cat_id']); +} +elseif (isset($current_thread['cat_id'])) +{ + $current_cat_meta = $cat->get_cat_by_id($current_thread['cat_id']); +} + +if ($current_cat_meta !== NULL) +{ + define('CURRENT_CATEGORY_ID', $current_cat_meta['id']); + define('CURRENT_CATEGORY_NAME', $current_cat_meta['name']); +} +?> diff --git a/includes/fonts/arial.TTF b/includes/fonts/arial.TTF new file mode 100644 index 0000000000000000000000000000000000000000..20b92a27d731cc52d0c7988b2611560653ca2c97 GIT binary patch literal 45260 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 xuejue7lg+zKs9d{{c^^0Ez$r literal 0 HcmV?d00001 diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..931b752 --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/includes/functions/time.php b/includes/functions/time.php new file mode 100644 index 0000000..499e572 --- /dev/null +++ b/includes/functions/time.php @@ -0,0 +1,40 @@ += $day && $diff < $wk) + { + $i = (int)($diff/$day); + $str = $i > 1 ? "$i days ago" : "$i day ago"; + } + elseif ($diff >= $hour && $diff < $day) + { + $i = (int)($diff/$hour); + $str = $i > 1 ? "$i hours ago" : "$i hour ago"; + } + elseif ($diff >= $min && $diff < $hour) + { + $i = (int)($diff/$min); + $str = $i > 1 ? "$i mins ago" : "$i min ago"; + } + elseif ($diff < $min && $diff > 0) + { + $str = $diff > 1 ? "$diff secs ago" : "$diff sec ago"; + } + elseif ($diff == 0) + { + $str = 'Just now'; + } + else + { + $str = date("d M Y, H:ia", $sec); + } + + return $str; +} diff --git a/includes/functions/user.php b/includes/functions/user.php new file mode 100644 index 0000000..a76ebb9 --- /dev/null +++ b/includes/functions/user.php @@ -0,0 +1,10 @@ + + + + <?php echo $title; ?> + + + + '; + } + } + + $theme_css_dir = $config['themes_path'] . '/' . $config['site_theme'] . '/css'; + $theme_css_http_dir = $config['http_host'] . '/themes/' . $config['site_theme'] . '/css'; + + $def = array('main.css', 'nav_menu.css', 'nav_search.css', ); + foreach ($def as $f) + { + if (file_exists($theme_css_dir.'/'.$f)) + { + echo ''; + } + } + + if (isset($theme_css)) + { + foreach ($theme_css as $filename) + { + if (file_exists($theme_css_dir.'/'.$filename)) + { + echo ''; + } + } + } + + if (isset($js_paths)) + foreach ($js_paths as $js) + echo ''; + + $theme_js_dir = $config['themes_path'] . '/' . $config['site_theme'] . '/js'; + if (file_exists($theme_js_dir)) + { + $theme_js_url = $config['themes_url'] . '/' . $config['site_theme'] . '/js'; + foreach (array_slice(scandir($theme_js_dir), 2) as $js) + { + echo ''; + } + } + ?> + + + + nav_search($config['http_host'].'/search.php', $query, $radios, $active_radio); + } + ?> +
    + \ No newline at end of file diff --git a/includes/pages.php b/includes/pages.php new file mode 100644 index 0000000..cf7a21a --- /dev/null +++ b/includes/pages.php @@ -0,0 +1,15 @@ +Pages:'; + +for ($i = 1; $i <= $pages; $i++) +{ + echo ' ('; + if ($i == $current_page) { + echo "$i"; + } else { + echo '' . $i . ''; + } + echo ')'; +} + +echo ''; \ No newline at end of file diff --git a/includes/password.php b/includes/password.php new file mode 100644 index 0000000..abd6f6f --- /dev/null +++ b/includes/password.php @@ -0,0 +1,18 @@ + +
    +

    Sections

    + get_categories(CATEGORY_NO_PARENT); + + for ($i = 0; $i < count($categories); $i++) { + $categories[ $i ]['category'] = htmlspecialchars( $categories[ $i ]['category'] ); + $categories[ $i ]['category_link'] = $config['http_host'] . '/threads.php?cat_id=' . $categories[ $i ]['cat_id']; + } + $theme->side_sections($categories); + ?> +
    + \ No newline at end of file diff --git a/includes/time.php b/includes/time.php new file mode 100644 index 0000000..ac17955 --- /dev/null +++ b/includes/time.php @@ -0,0 +1,37 @@ += $day && $diff < $wk) + { + $i = (int)($diff/$day); + $str = $i > 1 ? "$i days ago" : "$i day ago"; + } + elseif ($diff >= $hour && $diff < $day) + { + $i = (int)($diff/$hour); + $str = $i > 1 ? "$i hours ago" : "$i hour ago"; + } + elseif ($diff >= $min && $diff < $hour) + { + $i = (int)($diff/$min); + $str = $i > 1 ? "$i mins ago" : "$i min ago"; + } + elseif ($diff >= $sec && $diff < $min) + { + $i = (int)($diff/$sec); + $str = $i > 1 ? "$i secs ago" : "$i sec ago"; + } + else + { + $str = date("d M Y, H:ia", $sec); + } + + return $str; +} From fe327388979a6da2bc4b115dcaf17a6c866d5837 Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:27:27 +0100 Subject: [PATCH 09/12] Add files via upload --- themes/default_theme/Default_theme.php | 322 +++++++++++++++++ themes/default_theme/css/button.css | 10 + .../css/control_panel_categories.css | 28 ++ .../css/control_panel_themes.css | 9 + .../default_theme/css/currently_viewing.css | 10 + themes/default_theme/css/main.css | 82 +++++ themes/default_theme/css/nav_menu.css | 14 + themes/default_theme/css/nav_search.css | 40 +++ themes/default_theme/css/side_nav.css | 36 ++ .../default_theme/css/side_sections_right.css | 8 + themes/default_theme/css/side_topics.css | 8 + themes/default_theme/css/tab.css | 28 ++ themes/default_theme/css/thread_item.css | 45 +++ themes/default_theme/css/thread_reply.css | 80 +++++ themes/default_theme/css/threads_toolbar.css | 10 + themes/default_theme/images/attachment.png | Bin 0 -> 610 bytes themes/default_theme/images/button-search.png | Bin 0 -> 479 bytes themes/default_theme/meta.json | 5 + themes/default_theme/screenshot.JPG | Bin 0 -> 50442 bytes themes/sapphire/Sapphire.php | 339 ++++++++++++++++++ themes/sapphire/css/button.css | 19 + .../sapphire/css/control_panel_categories.css | 27 ++ themes/sapphire/css/control_panel_themes.css | 11 + themes/sapphire/css/currently_viewing.css | 16 + themes/sapphire/css/main.css | 68 ++++ themes/sapphire/css/nav_menu.css | 18 + themes/sapphire/css/nav_search.css | 70 ++++ themes/sapphire/css/side_nav.css | 30 ++ themes/sapphire/css/side_sections_right.css | 34 ++ themes/sapphire/css/side_topics.css | 8 + themes/sapphire/css/tab.css | 15 + themes/sapphire/css/thread_item.css | 102 ++++++ themes/sapphire/css/thread_reply.css | 82 +++++ themes/sapphire/css/threads_toolbar.css | 12 + themes/sapphire/images/attachment.png | Bin 0 -> 610 bytes themes/sapphire/images/button-search.png | Bin 0 -> 479 bytes themes/sapphire/images/down_arrow.png | Bin 0 -> 222 bytes themes/sapphire/js/search.js | 41 +++ themes/sapphire/meta.json | 5 + themes/sapphire/screenshot.jpg | Bin 0 -> 50134 bytes 40 files changed, 1632 insertions(+) create mode 100644 themes/default_theme/Default_theme.php create mode 100644 themes/default_theme/css/button.css create mode 100644 themes/default_theme/css/control_panel_categories.css create mode 100644 themes/default_theme/css/control_panel_themes.css create mode 100644 themes/default_theme/css/currently_viewing.css create mode 100644 themes/default_theme/css/main.css create mode 100644 themes/default_theme/css/nav_menu.css create mode 100644 themes/default_theme/css/nav_search.css create mode 100644 themes/default_theme/css/side_nav.css create mode 100644 themes/default_theme/css/side_sections_right.css create mode 100644 themes/default_theme/css/side_topics.css create mode 100644 themes/default_theme/css/tab.css create mode 100644 themes/default_theme/css/thread_item.css create mode 100644 themes/default_theme/css/thread_reply.css create mode 100644 themes/default_theme/css/threads_toolbar.css create mode 100644 themes/default_theme/images/attachment.png create mode 100644 themes/default_theme/images/button-search.png create mode 100644 themes/default_theme/meta.json create mode 100644 themes/default_theme/screenshot.JPG create mode 100644 themes/sapphire/Sapphire.php create mode 100644 themes/sapphire/css/button.css create mode 100644 themes/sapphire/css/control_panel_categories.css create mode 100644 themes/sapphire/css/control_panel_themes.css create mode 100644 themes/sapphire/css/currently_viewing.css create mode 100644 themes/sapphire/css/main.css create mode 100644 themes/sapphire/css/nav_menu.css create mode 100644 themes/sapphire/css/nav_search.css create mode 100644 themes/sapphire/css/side_nav.css create mode 100644 themes/sapphire/css/side_sections_right.css create mode 100644 themes/sapphire/css/side_topics.css create mode 100644 themes/sapphire/css/tab.css create mode 100644 themes/sapphire/css/thread_item.css create mode 100644 themes/sapphire/css/thread_reply.css create mode 100644 themes/sapphire/css/threads_toolbar.css create mode 100644 themes/sapphire/images/attachment.png create mode 100644 themes/sapphire/images/button-search.png create mode 100644 themes/sapphire/images/down_arrow.png create mode 100644 themes/sapphire/js/search.js create mode 100644 themes/sapphire/meta.json create mode 100644 themes/sapphire/screenshot.jpg diff --git a/themes/default_theme/Default_theme.php b/themes/default_theme/Default_theme.php new file mode 100644 index 0000000..6ca066a --- /dev/null +++ b/themes/default_theme/Default_theme.php @@ -0,0 +1,322 @@ +'; + + echo '
    '; + echo '
    ' . $thread->topic . ' (' . $thread->replies . ')
    '; + + echo '
    '; + foreach ($options as $o => $link) + { + echo '' . $o . ''; + } + echo '
    '; + + echo '
    '; + echo '
    '; + + echo '
    '; + echo '
    '; + if (isset($thread->op)) + { + echo 'By '.$thread->op.', '; + } + echo ''.$thread->time_created.'
    '; + echo '
    ' . $thread->views . ' view' . ($thread->views > 1 ? 's' : '') . '
    '; + echo '
    '; + echo '
    '; + + echo ''; + } + + function category_item($category) + { + echo '
    '; + + echo '
    '; + echo '' . $category['category'] . ' (' . $category['child_count'] . ')'; + echo '
    '; + + echo '
    ' . $category['description'] . '
    '; + + echo '
    '; + } + + function sub_category_item($category, $options) + { + echo '
    '; + + echo '
    '; + echo '
    ' . $category['category'] . ' (' . $category['child_count'] . ')
    '; + if ( ! empty($options)) + { + echo '
    '; + foreach ($options as $o => $link) + { + echo '' . $o . ''; + } + echo '
    '; + } + echo '
    '; + + echo '
    '; + + echo '
    ' . $category['description'] . '
    '; + + echo '
    '; + } + + function search_result_topic_item($topic, $options) + { + $this->thread_item($topic, $options); + } + + function search_result_reply_item($reply) + { + echo '
    '; + + echo '
    '; + + echo ''; + echo '
    '; + echo 'By '.$reply->username.': '; + echo ''.$reply->time_replied.' '; + echo '
    '; + + echo '
    '; + + echo '
    '.$reply->reply.'
    '; + + if (isset($reply->num_attachments) && $reply->num_attachments > 0) + { + echo '
    '.$reply->num_attachments.' attachment'; + if ($reply->num_attachments > 1) + { + echo 's'; + } + echo '
    '; + } + + echo '
    '; + } + + function thread_reply($reply, $attachments, $options) + { + echo '
    '; + + echo '
    '; + echo ''.$reply->username.': '; + echo ''.$reply->time_replied.''; + echo '
    '; + + echo '
    '; + echo '
    ' . $reply->reply . '
    '; + + if ( ! empty($attachments)) + { + echo '
    Attachments
      '; + foreach ($attachments as $a) + { + echo '
    • ' . $a['name'] . ' (' . $a['size'] . ')'; + echo 'Download'; + echo '
    • '; + } + echo '
    '; + } + + echo '
    '; + foreach ($options as $option => $href) + { + echo ''.$option.''; + } + echo '
    '; + echo '
    '; + + echo '
    '; + } + + function pages($pages, $current_page, $page_link) + { + echo '
    '; + for ($i = 1; $i <= $pages; $i++) + { + if ($i == $current_page) + { + echo "$i"; + } + else + { + echo '' . $i . ''; + } + } + echo '
    '; + } + + function side_sections($categories) + { + echo '
      '; + foreach ($categories as $c) + { + echo '
    • ' . $c['category'] . ' (' . $c['child_count'] . ')
    • '; + } + echo '
    '; + } + + function currently_viewing_thread($users, $guests_count) + { + echo '
    '; + echo '
    Currently viewing this thread
    '; + + $buf = ''; + for ($i = 0; $i < count($users); $i++) + { + $sep = ''; + if ($i == count($users) - 1 && $guests_count === 0) + { + $sep = $buf !== '' ? ' and ' : ''; + } + else + { + $sep = $buf !== '' ? ', ' : ''; + } + + if (is_string($users[$i])) + { + $buf .= $users[$i]; + } + else + { + $buf .= $sep . '' . $users[$i]->username . ''; + } + } + + if ($guests_count > 0) + { + $buf .= ($buf !== '' ? ' and ' : '') . $guests_count . ' guest'.($guests_count > 1 ? 's' : ''); + } + + echo "

    $buf

    "; + + echo '
    '; + } + + function nav_menu($menus, $active) + { + echo ''; + } + + function tab_menu($menus, $active) + { + echo '
    '; + $this->_menu($menus, $active); + echo '
    '; + } + + private function _menu($menus, $active) + { + foreach ($menus as $m => $url) + { + echo '' . $m . ''; + } + } + + function category_hierarchy($hierarchy) + { + echo '
    '; + + $str = ''; + foreach ($hierarchy as $cat => $url) + { + $str .= '' . $cat . ' › '; + } + echo substr($str, 0, count($str) - (strlen(' › ') + 1)); + + echo '
    '; + } + + function side_nav($menus, $active) + { + echo '
    '; + foreach ($menus as $menu => $submenus) + { + echo ''; + } + echo '
    '; + } + + function side_topics($title, $topics) + { + echo '
    '; + echo "
    $title
    "; + echo ''; + echo '
    '; + } + + function nav_search($form_action, $query, $radios, $active_radio) + { + echo ''; + } +} + \ No newline at end of file diff --git a/themes/default_theme/css/button.css b/themes/default_theme/css/button.css new file mode 100644 index 0000000..8643105 --- /dev/null +++ b/themes/default_theme/css/button.css @@ -0,0 +1,10 @@ +.button { + padding: 9px 18px; + font-size: 12px; + font-weight: bold; + background: #cc3300; + color: #f9f9f9; + border: none; + border-radius: 3px; + cursor: pointer; +} diff --git a/themes/default_theme/css/control_panel_categories.css b/themes/default_theme/css/control_panel_categories.css new file mode 100644 index 0000000..b9bc4fa --- /dev/null +++ b/themes/default_theme/css/control_panel_categories.css @@ -0,0 +1,28 @@ +#table_wrapper #theader { + background: #D28A49; + color: #fff; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +#table_wrapper #body { + border-left: 1px solid #FADDC3; + border-right: 1px solid #FADDC3; + border-bottom: 1px solid #FADDC3; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} + +#table_wrapper #body .row { + border-bottom: 1px solid #F9EADC; +} + +#table_wrapper #body .row .col1 { + color: #88796C; +} + +#table_wrapper #info { + font-size: 10px; + color: #888; + margin-top: 4px; +} diff --git a/themes/default_theme/css/control_panel_themes.css b/themes/default_theme/css/control_panel_themes.css new file mode 100644 index 0000000..2b36779 --- /dev/null +++ b/themes/default_theme/css/control_panel_themes.css @@ -0,0 +1,9 @@ +#current_theme_span { + color: #aaa; +} + +#themes h5 { + border-bottom: 1px solid #e9e9e9; + padding-bottom: 4px; + color: #bbb; +} diff --git a/themes/default_theme/css/currently_viewing.css b/themes/default_theme/css/currently_viewing.css new file mode 100644 index 0000000..aaf02e1 --- /dev/null +++ b/themes/default_theme/css/currently_viewing.css @@ -0,0 +1,10 @@ +#currently_viewing { + background: #FEFDD2; + padding: 5px; + border: 1px solid #E0E0B8; + margin-top: 30px; +} + +#currently_viewing p { + margin-top: 5px; +} \ No newline at end of file diff --git a/themes/default_theme/css/main.css b/themes/default_theme/css/main.css new file mode 100644 index 0000000..6004c7d --- /dev/null +++ b/themes/default_theme/css/main.css @@ -0,0 +1,82 @@ +body { + font-family: Arial; + color: #333333; + font-size: 13px; + background: #FEFEF7; +} + +input { + font-family: Arial; + font-size: 14px; +} + +a { + text-decoration: none; + color: #1A7FA0; +} + +a:hover { + color: #BF6428; +} + +#nav { + background: #000000; + color: #ffffff; +} + +#pages { + margin-top: 10px; +} + +#pages .page { + margin-right: 13px; +} + +#pages a.page:hover { + text-decoration: underline; +} + +label.bold { + font-size: 11px; + font-weight: bold; + color: #777777; +} + +span.error { + font-size: 12px; + font-weight: normal; + color: #ef0000; +} + +select, +.textfield, +textarea { + font-family: Arial; + font-size: 13px; + padding: 5px; + border: 1px solid #dddddd; + border-radius: 2px; +} + +select:focus, +.textfield:focus, +textarea:focus { + border-color: #c9c9c9; +} + +label:hover { + color: #555555; +} + +#footer { + background-color: #fbfbfb; + border-top: 1px solid #dfdfdf; +} + +#footer .bull { + color: #bbb; +} + +#footer #powered { + color: #888; +} diff --git a/themes/default_theme/css/nav_menu.css b/themes/default_theme/css/nav_menu.css new file mode 100644 index 0000000..8f26a98 --- /dev/null +++ b/themes/default_theme/css/nav_menu.css @@ -0,0 +1,14 @@ +#nav #nav_left #nav_menu a { + color: #ccc; + margin-left: 20px; +} + +#nav #nav_left #nav_menu a:hover { + color: #f9f9f9; +} + +#nav #nav_left #nav_menu a.active { + color: #cc3300; + font-weight: bold; + padding-bottom: 8px; +} diff --git a/themes/default_theme/css/nav_search.css b/themes/default_theme/css/nav_search.css new file mode 100644 index 0000000..7c9cbe9 --- /dev/null +++ b/themes/default_theme/css/nav_search.css @@ -0,0 +1,40 @@ +#nav_search #search-box-wrapper { + border: 1px solid #cccccc; + border-radius: 3px; + background: #ffffff; +} + +#nav_search #search-box-wrapper input { + border: none; + outline: none; + float: left; + height: 25px; + background: none; +} + +#nav_search #search-box-wrapper input[type="text"] { + width: 160px; + padding: 0 5px; + box-sizing: border-box; + font-size: 13px; +} + +#nav_search #search-box-wrapper #search-btn { + width: 25px; + background: url('../images/button-search.png') center center no-repeat; + cursor: pointer; +} + +#nav_search #radio-wrapper { + margin-top: 5px; +} + +#nav_search #radio-wrapper span { + margin-right: 7px; +} + +#nav_search #radio-wrapper span label { + margin-left: 2px; + position: relative; + top: -2px; +} diff --git a/themes/default_theme/css/side_nav.css b/themes/default_theme/css/side_nav.css new file mode 100644 index 0000000..2f95dbb --- /dev/null +++ b/themes/default_theme/css/side_nav.css @@ -0,0 +1,36 @@ +#side_nav h5 { + border-bottom: 1px solid #999999; + color: #999999; + background: #f9f9f9; + padding: 3px; +} + +#side_nav .menu { + margin-bottom: 7px; + border: 1px solid #999999; +} + +#side_nav ul li { + line-height: 28px; + height: 28px; +} + +#side_nav ul li a { + display: block; + width: 100%; + height: 28px; + color: #777777; + text-indent: 3px; +} + +#side_nav ul li a:hover { + background: #EAEAE9; + color: #333333; +} + +#side_nav ul li a.active { + color: #333333; + font-weight: bold; + background: none; + font-size: 12px; +} diff --git a/themes/default_theme/css/side_sections_right.css b/themes/default_theme/css/side_sections_right.css new file mode 100644 index 0000000..a5036ca --- /dev/null +++ b/themes/default_theme/css/side_sections_right.css @@ -0,0 +1,8 @@ +#right ul { + margin-top: 10px; +} + +#right ul li { + margin-bottom: 5px; + line-height: 20px; +} diff --git a/themes/default_theme/css/side_topics.css b/themes/default_theme/css/side_topics.css new file mode 100644 index 0000000..92bf8a6 --- /dev/null +++ b/themes/default_theme/css/side_topics.css @@ -0,0 +1,8 @@ +.side_topics ul { + margin-top: 10px; +} + +.side_topics ul li { + margin-bottom: 10px; + font-size: 11px; +} diff --git a/themes/default_theme/css/tab.css b/themes/default_theme/css/tab.css new file mode 100644 index 0000000..9b59ef9 --- /dev/null +++ b/themes/default_theme/css/tab.css @@ -0,0 +1,28 @@ +.tab .tab_menu { + border-bottom: 1px solid #efefef; + padding-left: 10px; +} + +.tab .tab_menu a { + font-size: 11px; + font-weight: bold; + height: 20px; + display: inline-block; + color: #999; + padding: 0 9px; + margin-right: 2px; +} + +.tab .tab_menu a:hover { + color: #777; + border-bottom: 3px solid #777; +} + +.tab .tab_menu a.active { + color: #333333; + border-bottom: 3px solid #cc3300; +} + +.tab .tab_content { + padding-top: 15px; +} diff --git a/themes/default_theme/css/thread_item.css b/themes/default_theme/css/thread_item.css new file mode 100644 index 0000000..e9b88f9 --- /dev/null +++ b/themes/default_theme/css/thread_item.css @@ -0,0 +1,45 @@ +.item { + padding: 5px; + margin-bottom: 2px; + border-radius: 3px; +} + +.item:nth-child(odd) { + background: #FAF5EF; + border: 1px solid #E8E8E0; +} + +.item:nth-child(even) { + background: #fbfbfb; + border: 1px solid #E2E8EA; +} + +.item.thread_item .topic a, +.item.sub_category_item .name a, +.item.category_item .name a{ + font-weight: bold; +} + +.item.thread_item .topic, +.item.sub_category_item .name { + float:left; +} + +.item.thread_item .creation_info { + float: left; +} + +.item.thread_item .views_count, +.item.thread_item .options, +.item.sub_category_item .options { + float: right; +} + +.item .options a { + margin-left: 12px; +} + +.item .meta, +.item .description { + margin-top: 5px; +} diff --git a/themes/default_theme/css/thread_reply.css b/themes/default_theme/css/thread_reply.css new file mode 100644 index 0000000..3a04185 --- /dev/null +++ b/themes/default_theme/css/thread_reply.css @@ -0,0 +1,80 @@ +.reply { + margin-bottom: 4px; + border-radius: 3px; +} + +.reply:nth-child(odd) { + background: #F9FDFE; + border: 1px solid #E2E8EA; +} + +.reply:nth-child(even) { + background: #FEFEF9; + border: 1px solid #E8E8E0; +} + +.reply .meta, +.reply .content { + padding: 5px; +} + +.reply .meta { + border-bottom: 1px solid #f2f2f2; +} + +.reply .meta a, +.reply .meta span { + font-weight: bold; + font-size: 12px; +} + +.reply .meta .creation_info a, +.reply .meta .creation_info span { + font-weight: normal; +} + +.reply .topic { + margin-bottom: 5px; +} + +.reply .content .text { + margin-top: 5px; +} + +.reply .content .attachments, +.reply .content .options { + margin-top: 15px; +} + +.reply .content .attachments { + background: #FEFDD2; + padding: 5px; +} + +.reply .content .attachments ul { + margin-top: 7px; +} + +.reply .content .attachments ul li { + margin-top: 2px; + font-size: 12px; + padding-left: 15px; + background: url('../images/attachment.png') no-repeat left center; + background-size: 12px; +} + +.reply .content .attachments .download_link { + margin-left: 10px; +} + +.reply .content .options a { + margin-right: 10px; +} + +.reply .num_attachments { + background: url('../images/attachment.png') no-repeat 5px center; + background-size: 12px; + padding: 5px 5px 5px 20px; + font-size: 11px; + color: #7A8082; +} diff --git a/themes/default_theme/css/threads_toolbar.css b/themes/default_theme/css/threads_toolbar.css new file mode 100644 index 0000000..3722647 --- /dev/null +++ b/themes/default_theme/css/threads_toolbar.css @@ -0,0 +1,10 @@ +#options { + border: 1px solid #efefef; + padding: 5px; +} + +#options a { + margin-right: 20px; + font-size: 12px; + font-weight: bold; +} diff --git a/themes/default_theme/images/attachment.png b/themes/default_theme/images/attachment.png new file mode 100644 index 0000000000000000000000000000000000000000..7bda5322f02dcf90ad4791997dfb5a0b1f2648e9 GIT binary patch literal 610 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47&!ubLR^8|xpU@>A`K%wQD9hv zmjw9*Gw^5nwg36b{F{+&nz_oQXM0~QiGKW3QpUE!spVnQO1)G6ApJ>6MPJeP&q~#;4EcW+0j=2N z>Eak7A<24>Iq5Kvd%*jW+ScBi+j6U4|F7R<%@DTE_~|A&a^kZY zPnJZzF%8~%DplL7{lxLs*|&H9j@la05nOS?#l=%gPh|C09l7}mKJIUyd-1u+UTN2N zTq)9U-Ms2-xqP$Oho4v9)<5U0W4L+W?WaED19_drLdy3)Zjt~xm%-E3&t;ucLK6VN C1vQ5N literal 0 HcmV?d00001 diff --git a/themes/default_theme/images/button-search.png b/themes/default_theme/images/button-search.png new file mode 100644 index 0000000000000000000000000000000000000000..c89b0441b93893f9cd1c89f981bebdc7422cf9f4 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@)1s;*b3=CYaL71_rn>Pw5$dc~p z>&U>cv9IQL;A9|QA=x9ymw};5m4Tt5nStTwe<1ymfuYoZf#FpG1B2BJ1_tr`N%2SB z7#J8sJzX3_DsCnH;b-9qD0p~?we9uw^~N7Qd@z_XV}^l;mzPljGXvuhj!R$u{48#} zbSbEPW%wZ@C+eJ$sfW;tMqB!qux^?;k(@{QolF+5Z+dA3Ef;;m+pt^8rT~nL0S1nAFwP zvBk&7^K3tU^l0NtW_G>>Ak#0e4qq>EVDD6cri0c>TIr^Z<|ZH4XuXo)el( z(hRP8Cmd3G85msxgf%V|7Zp7?w{`R8WCjieH8v)OG^-Qq6&}2Lb&5eCfg$7X${z+D SAEJSg!QkoY=d#Wzp$P!l^0}4( literal 0 HcmV?d00001 diff --git a/themes/default_theme/meta.json b/themes/default_theme/meta.json new file mode 100644 index 0000000..fb1509c --- /dev/null +++ b/themes/default_theme/meta.json @@ -0,0 +1,5 @@ +{ + "name": "Default", + "screenshot_path": "screenshot.jpg", + "version": "1.0" +} diff --git a/themes/default_theme/screenshot.JPG b/themes/default_theme/screenshot.JPG new file mode 100644 index 0000000000000000000000000000000000000000..806f857f3f2964db6c147ca597d715de2ffb4a75 GIT binary patch literal 50442 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 literal 0 HcmV?d00001 diff --git a/themes/sapphire/Sapphire.php b/themes/sapphire/Sapphire.php new file mode 100644 index 0000000..587255b --- /dev/null +++ b/themes/sapphire/Sapphire.php @@ -0,0 +1,339 @@ +'; + + echo ''; + + echo '
    '; + if (isset($thread->op)) + { + echo ""; + } + echo "
    $thread->time_created
    "; + echo '
    '; + + echo '
    '; + echo '

    '.$thread->views.' view'.($thread->views > 1 ? 's' : '').' &

    '; + echo '

    '.$thread->replies.' repl'.($thread->replies > 1 ? 'ies' : 'y').'

    '; + echo '
    '; + + echo '
    '; + + echo ''; + } + + function category_item($category) + { + echo '
    '; + + echo '
    '; + echo '' . $category['category'] . ' '.$category['child_count'].''; + echo '
    '; + + echo '
    ' . $category['description'] . '
    '; + + echo '
    '; + } + + function sub_category_item($category, $options) + { + echo '
    '; + + echo '
    '; + echo '
    ' . $category['category'] . ' '.$category['child_count'].'
    '; + if ( ! empty($options)) + { + echo '
    '; + foreach ($options as $o => $link) + { + echo '' . $o . ''; + } + echo '
    '; + } + echo '
    '; + + echo '
    '; + + echo '
    ' . $category['description'] . '
    '; + + echo '
    '; + } + + function search_result_topic_item($topic, $options) + { + $this->thread_item($topic, $options); + } + + function search_result_reply_item($reply) + { + return $this->_thread_reply($reply); + } + + function thread_reply($reply, $attachments, $options) + { + return $this->_thread_reply($reply, $attachments, $options); + } + + private function _thread_reply($reply, $attachments=array(), $options=array()) + { + echo '
    '; + + echo '
    '; + if (isset($reply->thread_link) && isset($reply->topic)) + { + echo ''; + } + + echo 'thread_link) && isset($reply->topic) ? ' class="row2"' : '').'>'; + echo ''.$reply->username.': '; + echo $reply->time_replied; + echo '
    '; + + echo '
    '; + + echo '
    '; + + if (!empty($options)) + { + echo '
    '; + foreach ($options as $name => $url) + { + echo ''.$name.''; + } + echo '
    '; + } + + echo '
    '.$reply->reply.'
    '; + + if (!empty($attachments)) + { + echo '
    Attachments
      '; + foreach ($attachments as $a) + { + echo '
    • '; + echo '
      '.$a['name'].' ('.$a['size'].')
      '; + echo ''; + echo '
    • '; + } + echo '
    '; + } + + if (isset($reply->num_attachments) && $reply->num_attachments > 0) + { + echo '
    '.$reply->num_attachments.' attachment'; + if ($reply->num_attachments > 1) + { + echo 's'; + } + echo '
    '; + } + + echo '
    '; + + echo ''; + } + + function pages($pages, $current_page, $page_link) + { + echo '
    '; + for ($i = 1; $i <= $pages; $i++) + { + if ($i == $current_page) + { + echo "$i"; + } + else + { + echo '' . $i . ''; + } + } + echo '
    '; + } + + function side_sections($categories) + { + echo ''; + } + + function currently_viewing_thread($users, $guests_count) + { + echo '
    '; + echo '
    Currently viewing this thread
    '; + + $buf = ''; + for ($i = 0; $i < count($users); $i++) + { + $sep = ''; + if ($i == count($users) - 1 && $guests_count === 0) + { + $sep = $buf !== '' ? ' and ' : ''; + } + else + { + $sep = $buf !== '' ? ', ' : ''; + } + + if (is_string($users[$i])) + { + $buf .= $users[$i]; + } + else + { + $buf .= $sep . '' . $users[$i]->username . ''; + } + } + + if ($guests_count > 0) + { + $buf .= ($buf !== '' ? ' and ' : '') . $guests_count . ' guest'.($guests_count > 1 ? 's' : ''); + } + + echo "

    $buf

    "; + + echo '
    '; + } + + function nav_menu($menus, $active) + { + echo ''; + } + + function tab_menu($menus, $active) + { + echo '
    '; + $this->_menu($menus, $active); + echo '
    '; + } + + private function _menu($menus, $active) + { + foreach ($menus as $m => $url) + { + echo '' . $m . ''; + } + } + + function category_hierarchy($hierarchy) + { + echo '
    '; + + $str = ''; + foreach ($hierarchy as $cat => $url) + { + $str .= '' . $cat . ' › '; + } + echo substr($str, 0, count($str) - (strlen(' › ') + 1)); + + echo '
    '; + } + + function side_nav($menus, $active) + { + echo '
    '; + foreach ($menus as $menu => $submenus) + { + echo ''; + } + echo '
    '; + } + + function side_topics($title, $topics) + { + echo '
    '; + echo "
    $title
    "; + echo ''; + echo '
    '; + } + + function nav_search($form_action, $query, $radios, $active_radio) + { + echo ''; + } +} + \ No newline at end of file diff --git a/themes/sapphire/css/button.css b/themes/sapphire/css/button.css new file mode 100644 index 0000000..78485f1 --- /dev/null +++ b/themes/sapphire/css/button.css @@ -0,0 +1,19 @@ +.button { + padding: 8px 11px; + font-size: 11px; + background-color: #5D829C; + font-weight: bold; + color: #ffffff; + border: 1px solid #46657B; + border-radius: 1px; + outline: none; + cursor: pointer; +} + +.button:hover { + background: #46657B; +} + +.button:active { + background: #3F5D72; +} diff --git a/themes/sapphire/css/control_panel_categories.css b/themes/sapphire/css/control_panel_categories.css new file mode 100644 index 0000000..6d960ad --- /dev/null +++ b/themes/sapphire/css/control_panel_categories.css @@ -0,0 +1,27 @@ +#category_table { + border: 1px solid #67717A; + border-radius: 1px; +} + +#category_table #theader { + background: #67717A; + color: #fff; +} + +#category_table #body .row { + border-bottom: 1px solid #DAE1E6; +} + +#category_table #body .row:hover { + background: #DAE1E6; +} + +#category_table #body .row .col1 { + color: #647583; +} + +#table_wrapper #info { + font-size: 11px; + color: #647583; + margin-top: 3px; +} \ No newline at end of file diff --git a/themes/sapphire/css/control_panel_themes.css b/themes/sapphire/css/control_panel_themes.css new file mode 100644 index 0000000..2328030 --- /dev/null +++ b/themes/sapphire/css/control_panel_themes.css @@ -0,0 +1,11 @@ +#current_theme #current_theme_span { + color: #647583; +} + +#themes h5 { + background: #67717A; + padding: 5px; + color: #fff; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} diff --git a/themes/sapphire/css/currently_viewing.css b/themes/sapphire/css/currently_viewing.css new file mode 100644 index 0000000..03f0ec6 --- /dev/null +++ b/themes/sapphire/css/currently_viewing.css @@ -0,0 +1,16 @@ +#currently_viewing { + margin-top: 30px; + border: 1px solid #67717A; + border-radius: 2px; + background: #F9FDFE; +} + +#currently_viewing h5 { + background: #67717A; + color: #fff; + padding: 5px; +} + +#currently_viewing p { + padding: 5px; +} \ No newline at end of file diff --git a/themes/sapphire/css/main.css b/themes/sapphire/css/main.css new file mode 100644 index 0000000..0bdb1eb --- /dev/null +++ b/themes/sapphire/css/main.css @@ -0,0 +1,68 @@ +body { + font-family: Tahoma; + color: #333333; + font-size: 13px; + background-color: #E5EBF0; +} + +input { + font-family: Tahoma; + font-size: 14px; +} + +a { + text-decoration: none; + color: #1A7FA0; +} + +a:hover { + color: #BF6428; +} + +#pages { + margin-top: 10px; +} + +#pages .page { + margin-right: 13px; +} + +#pages a.page:hover { + text-decoration: underline; +} + +label.bold { + font-size: 11px; + font-weight: bold; + color: #777777; +} + +span.error { + font-size: 11px; + font-weight: normal; + color: #ef0000; +} + +select, +.textfield, +textarea { + font-family: Tahoma; + font-size: 13px; + padding: 5px; + border: 1px solid #C6D1D8; + border-radius: 2px; +} + +select:focus, +.textfield:focus, +textarea:focus { + border-color: #AAB8C1; +} + +label:hover { + color: #555555; +} + +#footer { + border-top: 1px solid #DDE5EC; +} diff --git a/themes/sapphire/css/nav_menu.css b/themes/sapphire/css/nav_menu.css new file mode 100644 index 0000000..014813d --- /dev/null +++ b/themes/sapphire/css/nav_menu.css @@ -0,0 +1,18 @@ +#nav #nav_left #nav_menu a { + margin-left: 20px; + font-weight: bold; + font-size: 11px; + color: #7F868B; + display: inline-block; +} + +#nav #nav_left #nav_menu a:hover { + color: #4F555A; +} + +#nav #nav_left #nav_menu a.active { + padding: 7px 8px; + background-color: #cc3300; + color: #ffffff; + border-radius: 3px; +} diff --git a/themes/sapphire/css/nav_search.css b/themes/sapphire/css/nav_search.css new file mode 100644 index 0000000..6f8f7aa --- /dev/null +++ b/themes/sapphire/css/nav_search.css @@ -0,0 +1,70 @@ +#nav_search #search-box-wrapper { + border: 1px solid #C6D1D8; + width: 185px; + border-radius: 3px; + background: #ffffff; +} + +#nav_search #search-box-wrapper input { + border: none; + outline: none; + height: 25px; + background: none; +} + +#nav_search #search-box-wrapper input[type="text"] { + width: 145px; + padding: 0 5px; + box-sizing: border-box; + font-size: 12px; +} + +#nav_search #search-box-wrapper input, +#nav_search #search-box-wrapper #radio_dropdown_wrapper { + float: left; +} + +#nav_search #search-box-wrapper #radio_dropdown_wrapper { + width: 15px; + height: 25px; + position: relative; +} + +#nav_search #search-box-wrapper #radio_dropdown_wrapper #down_arrow { + height: 25px; + background: url('../images/down_arrow.png') center center no-repeat; + cursor: pointer; +} + +#nav_search #search-box-wrapper #radio_dropdown_wrapper #down_arrow:hover { + background-color: #efefef; +} + +#nav_search #search-box-wrapper #radio_dropdown_wrapper #dropdown { + position: absolute; + top: 27px; + right: 0; + width: 130px; + background: #efefef; + box-shadow: -1px 1px 4px rgba(0,0,0,0.1); + display: none; +} + +#nav_search #search-box-wrapper #radio_dropdown_wrapper #dropdown li { + padding: 5px 10px; +} + +#nav_search #search-box-wrapper #radio_dropdown_wrapper #dropdown li input { + position: relative; + top: -5px; +} + +#nav_search #search-box-wrapper #radio_dropdown_wrapper #dropdown li label { + margin-left: 5px; +} + +#nav_search #search-box-wrapper #search-btn { + width: 25px; + background: url('../images/button-search.png') center center no-repeat; + cursor: pointer; +} diff --git a/themes/sapphire/css/side_nav.css b/themes/sapphire/css/side_nav.css new file mode 100644 index 0000000..44064b0 --- /dev/null +++ b/themes/sapphire/css/side_nav.css @@ -0,0 +1,30 @@ +#side_nav .menu { + margin-bottom: 7px; + border: 1px solid #67717A; + border-radius: 1px; +} + +#side_nav h5 { + padding: 5px 3px; +} + +#side_nav h5 { + background: #67717A; + color: #fff; +} + +#side_nav ul li { + border-top: 1px solid #DAE1E6; +} + +#side_nav ul li a { + display: block; + padding: 6px 3px; +} + +#side_nav ul li a.active { + background: #DAE1E6; + color: #454545; + font-weight: bold; + font-size: 11px; +} diff --git a/themes/sapphire/css/side_sections_right.css b/themes/sapphire/css/side_sections_right.css new file mode 100644 index 0000000..131a8a8 --- /dev/null +++ b/themes/sapphire/css/side_sections_right.css @@ -0,0 +1,34 @@ +#right ul { + margin-top: 10px; + border: 1px solid #DAE1E6; + border-bottom: none; +} + +#right ul li { + font-size: 11px; + font-weight: bold; + border-bottom: 1px solid #DAE1E6; +} + +#right ul li a { + display: block; + padding: 7px; +} + +#right ul li a:hover { + background: #DAE1E6; +} + +#right ul li a span.wrapper { + height: 15px; + line-height: 15px; +} + +#right ul li a span.threads_count { + float: right; + width: 15px; + text-align: center; + background: #9CADBB; + border-radius: 1px; + color: #fff; +} diff --git a/themes/sapphire/css/side_topics.css b/themes/sapphire/css/side_topics.css new file mode 100644 index 0000000..92bf8a6 --- /dev/null +++ b/themes/sapphire/css/side_topics.css @@ -0,0 +1,8 @@ +.side_topics ul { + margin-top: 10px; +} + +.side_topics ul li { + margin-bottom: 10px; + font-size: 11px; +} diff --git a/themes/sapphire/css/tab.css b/themes/sapphire/css/tab.css new file mode 100644 index 0000000..950d326 --- /dev/null +++ b/themes/sapphire/css/tab.css @@ -0,0 +1,15 @@ +.tab .tab_menu a { + font-size: 11px; + font-weight: bold; + margin-right: 10px; +} + +.tab .tab_menu a.active { + color: #333333; + border-bottom: 2px solid #cc3300; + padding: 0 5px 5px 5px; +} + +.tab .tab_content { + margin-top: 15px; +} diff --git a/themes/sapphire/css/thread_item.css b/themes/sapphire/css/thread_item.css new file mode 100644 index 0000000..68af209 --- /dev/null +++ b/themes/sapphire/css/thread_item.css @@ -0,0 +1,102 @@ +.item { + font-size: 12px; + margin-bottom: 4px; + border: 1px solid #67717A; + border-radius: 2px; + background: #F9FDFE; +} + +.item.thread_item { + height: 57px; +} + +.item.category_item, +.item.sub_category_item { + padding: 5px; +} + +.item .topic, +.item .creation_info, +.item .stats { + height: 100%; + float: left; + padding: 5px; + box-sizing: border-box; +} + +.item .topic { + width: 466px; + position: relative; + border-right: 1px solid #67717A; +} + +#user_content #right_large .tab_content .item .topic { + width: 296px; +} + +.item .creation_info { + width: 144px; + border-right: 1px solid #67717A; +} + +.item .topic a.name, +.item.category_item .name a, +.item.sub_category_item .name a, +.item .creation_info a { + font-weight: bold; +} + +.item .topic .options { + position: absolute; + bottom: 5px; + left: 5px; +} + +.item .creation_info .user { + margin-bottom: 5px; +} + +.item .creation_info .time, +.item .stats span { + color: #6E7276; +} + +.item .stats { + width: 108px; +} + +.item.sub_category_item .name { + float: left; +} + +.item .topic .options a { + color: #8C9399; +} + +.item.category_item .name span, +.item.sub_category_item .name span { + background: #9CADBB; + border-radius: 2px; + color: #fff; + font-size: 11px; + font-weight: bold; + padding: 1px 3px; + margin-left: 5px; +} + +.item .topic .options a:hover { + color: #9B5C39; +} + +.item.sub_category_item .options { + float: right; +} + +.item.sub_category_item .options a { + margin-left: 10px; +} + +.item.category_item .description, +.item.sub_category_item .description { + margin-top: 5px; +} diff --git a/themes/sapphire/css/thread_reply.css b/themes/sapphire/css/thread_reply.css new file mode 100644 index 0000000..599cdc7 --- /dev/null +++ b/themes/sapphire/css/thread_reply.css @@ -0,0 +1,82 @@ +.reply { + margin-bottom: 4px; + border: 1px solid #67717A; + border-radius: 2px; + background: #F9FDFE; +} + +.reply .meta { + background: #67717A; + color: #fff; +} + +.reply .meta a { + color: #7CD8F6; +} + +.reply .meta a:hover { + color: #EE8A49; +} + +.reply .meta .topic { + margin-bottom: 3px; +} + +.reply .meta .row2 a { + font-weight: normal; +} + +.reply .meta, +.reply .content .options, +.reply .content .text { + padding: 5px; +} + +.reply .meta a, +.reply .meta span { + font-weight: bold; + font-size: 12px; +} + +.reply .content .num_attachments { + background: url('../images/attachment.png') no-repeat 5px center; + background-size: 12px; + padding: 5px 5px 5px 20px; + font-size: 11px; + color: #7A8082; +} + +.reply .content .options a { + font-size: 11px; +} + +.reply .content .attachments { + background: #EBF1F6; +} + +.reply .content .attachments li { + margin-top: 2px; + border-top: 1px solid #DDE5EC; + padding: 5px; +} + +.reply .content .attachments li div { + padding-left: 17px; +} + +.reply .content .attachments h5 { + padding: 3px 5px; +} + +.reply .content .attachments li div.filename { + background: url('../images/attachment.png') no-repeat left center; + background-size: 12px; +} + +.reply .content .attachments .download_link { + font-size: 11px; +} + +.reply .content .options a { + margin-right: 10px; +} diff --git a/themes/sapphire/css/threads_toolbar.css b/themes/sapphire/css/threads_toolbar.css new file mode 100644 index 0000000..6694dd8 --- /dev/null +++ b/themes/sapphire/css/threads_toolbar.css @@ -0,0 +1,12 @@ +#options { + text-align: right; + border-top: 1px solid #DAE1E6; + border-bottom: 1px solid #DAE1E6; + padding: 7px 0; +} + +#options a { + margin-left: 15px; + font-weight: bold; + font-size: 12px; +} diff --git a/themes/sapphire/images/attachment.png b/themes/sapphire/images/attachment.png new file mode 100644 index 0000000000000000000000000000000000000000..7bda5322f02dcf90ad4791997dfb5a0b1f2648e9 GIT binary patch literal 610 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47&!ubLR^8|xpU@>A`K%wQD9hv zmjw9*Gw^5nwg36b{F{+&nz_oQXM0~QiGKW3QpUE!spVnQO1)G6ApJ>6MPJeP&q~#;4EcW+0j=2N z>Eak7A<24>Iq5Kvd%*jW+ScBi+j6U4|F7R<%@DTE_~|A&a^kZY zPnJZzF%8~%DplL7{lxLs*|&H9j@la05nOS?#l=%gPh|C09l7}mKJIUyd-1u+UTN2N zTq)9U-Ms2-xqP$Oho4v9)<5U0W4L+W?WaED19_drLdy3)Zjt~xm%-E3&t;ucLK6VN C1vQ5N literal 0 HcmV?d00001 diff --git a/themes/sapphire/images/button-search.png b/themes/sapphire/images/button-search.png new file mode 100644 index 0000000000000000000000000000000000000000..c89b0441b93893f9cd1c89f981bebdc7422cf9f4 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@)1s;*b3=CYaL71_rn>Pw5$dc~p z>&U>cv9IQL;A9|QA=x9ymw};5m4Tt5nStTwe<1ymfuYoZf#FpG1B2BJ1_tr`N%2SB z7#J8sJzX3_DsCnH;b-9qD0p~?we9uw^~N7Qd@z_XV}^l;mzPljGXvuhj!R$u{48#} zbSbEPW%wZ@C+eJ$sfW;tMqB!qux^?;k(@{QolF+5Z+dA3Ef;;m+pt^8rT~nL0S1nAFwP zvBk&7^K3tU^l0NtW_G>>Ak#0e4qq>EVDD6cri0c>TIr^Z<|ZH4XuXo)el( z(hRP8Cmd3G85msxgf%V|7Zp7?w{`R8WCjieH8v)OG^-Qq6&}2Lb&5eCfg$7X${z+D SAEJSg!QkoY=d#Wzp$P!l^0}4( literal 0 HcmV?d00001 diff --git a/themes/sapphire/images/down_arrow.png b/themes/sapphire/images/down_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..66f6dd79fdf97c5f61d108e2a06557e2d66289bc GIT binary patch literal 222 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 literal 0 HcmV?d00001 From e2c48459b3dec442849928ec47e33ce8953f445c Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:30:15 +0100 Subject: [PATCH 10/12] Add files via upload --- banned.php | 24 +++++ create_thread.php | 72 +++++++++++++ edit_profile.php | 134 ++++++++++++++++++++++++ edit_reply.php | 69 ++++++++++++ file.php | 150 ++++++++++++++++++++++++++ index.php | 70 +++++++++++++ logout.php | 7 ++ profile.php | 239 ++++++++++++++++++++++++++++++++++++++++++ search.php | 220 ++++++++++++++++++++++++++++++++++++++ settings.php | 166 +++++++++++++++++++++++++++++ signup.php | 87 +++++++++++++++ threads.php | 160 ++++++++++++++++++++++++++++ view.php | 262 ++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 1660 insertions(+) create mode 100644 banned.php create mode 100644 create_thread.php create mode 100644 edit_profile.php create mode 100644 edit_reply.php create mode 100644 file.php create mode 100644 index.php create mode 100644 logout.php create mode 100644 profile.php create mode 100644 search.php create mode 100644 settings.php create mode 100644 signup.php create mode 100644 threads.php create mode 100644 view.php diff --git a/banned.php b/banned.php new file mode 100644 index 0000000..76e4fe2 --- /dev/null +++ b/banned.php @@ -0,0 +1,24 @@ + +
    +

    Error

    +
    Sorry, you have been banned from .
    +
    '; + ?>
    +
    + \ No newline at end of file diff --git a/create_thread.php b/create_thread.php new file mode 100644 index 0000000..194fda8 --- /dev/null +++ b/create_thread.php @@ -0,0 +1,72 @@ +get_cat_by_id($_GET['cat_id']); + if ($meta != null) + { + define('CURRENT_CATEGORY_ID', $meta['id']); + define('CURRENT_CATEGORY_NAME', $meta['name']); + } +} +else +{ + header('Location: ' . $config['http_host']); + die(); +} + +$title = 'Create New Thread – ' . $config['site_name']; + +$css_paths = array('css/create_thread.css'); + +$theme_css = array('side_sections_right.css', 'button.css'); + +include($config['includes_path'] . '/header.php'); +?> +
    +
    + +

    Create New Thread

    +
    +
    +
    + field_value_exists('thread', 'topic')) echo ' value="' . $error->get_field_value('thread', 'topic') . '"'; + ?> class="textfield" /> +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + \ No newline at end of file diff --git a/edit_profile.php b/edit_profile.php new file mode 100644 index 0000000..fa1556b --- /dev/null +++ b/edit_profile.php @@ -0,0 +1,134 @@ +get_user_meta(USER_NICK); +?> +
    +
    +

    Edit Profile

    +
    +
    +
    +
    +
    + + exists('edit_profile', 'profile_pic')) { + echo '
    '.$error->get_message('edit_profile', 'profile_pic').'
    '; + } + ?> +
    +
    +
    +
    + + +
    +
    +
    + + + +
    +
    +
    + field_value_exists('edit_profile', 'fullname')) { + $fullname = $error->get_field_value('edit_profile', 'fullname'); + } else { + $fullname = $user_details['fullname']; + } + + if ($fullname !== null) + { + echo ' value="'.$fullname.'"'; + } + ?> /> +
    +
    +
    + field_value_exists('edit_profile', 'location')) { + $location = $error->get_field_value('edit_profile', 'location'); + } else { + $location = $user_details['location']; + } + + if ($location !== null) { + echo ' value="'.$location.'"'; + } + ?> /> +
    +
    +
    +
    +
    + +
    +
    + diff --git a/edit_reply.php b/edit_reply.php new file mode 100644 index 0000000..ffa3a63 --- /dev/null +++ b/edit_reply.php @@ -0,0 +1,69 @@ +get_reply_by_id(CURRENT_REPLY_ID); + +if ($reply === NULL) +{ + header('Location: ' . $config['http_host']); + die(); +} + +define('CURRENT_THREAD_ID', $reply['thread_id']); + +if ($reply['user_id'] !== $sess_info['user_id']) +{ + header('Location: ' . $config['http_host'] . '/view.php?id=' . CURRENT_THREAD_ID); + die(); +} + +$title = 'Edit reply – ' . $config['site_name']; + +$css_paths = array('css/edit_reply.css'); + +$theme_css = array('side_sections_right.css', 'button.css'); + +include($config['includes_path'] . '/header.php'); +?> +
    +
    + +

    +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    + \ No newline at end of file diff --git a/file.php b/file.php new file mode 100644 index 0000000..a177a76 --- /dev/null +++ b/file.php @@ -0,0 +1,150 @@ + get_mime(pathinfo($filepath, PATHINFO_EXTENSION)), + 'name' => pathinfo($filepath, PATHINFO_BASENAME), + 'bin' => file_get_contents($filepath) + ); + } +} +elseif (isset($_GET['u'])) +{ + $filepath = ''; + + require($config['database_path'] . '/user_database.php'); + $user_db = new User_database(); + $meta = $user_db->get_user_meta($_GET['u']); + + if ($meta['picture_filename'] != null) + { + $filepath = $config['user_picture_path'] . '/' . $meta['picture_filename']; + } + else + { + switch ($meta['sex']) + { + case 'male': + $filepath = IMAGES_DIR . '/default_avatar.png'; + break; + case 'female': + $filepath = IMAGES_DIR . '/default_avatar.png'; + break; + default: + $filepath = IMAGES_DIR . '/default_avatar.png'; + break; + } + } + + $image = array( + 'name' => pathinfo($filepath, PATHINFO_BASENAME), + 'type' => get_mime(pathinfo($filepath, PATHINFO_EXTENSION)) + ); + if ($meta['is_banned']) + { + $image['bin'] = add_ban_sticker($filepath, $image['type']); + } + else + { + $image['bin'] = file_get_contents($filepath); + } +} + +if ($image != null) +{ + if (isset($_GET['download'])) + { + header('Content-Disposition: attachment; filename='.$image['name']); + } + + header('Content-Type: ' . $image['type']); + + echo $image['bin']; +} +else +{ + echo 'File does not exists.'; +} + +function add_ban_sticker($path, $type) +{ + $image = null; + switch($type) + { + case 'image/jpeg': + $image = imagecreatefromjpeg($path); + break; + case 'image/png': + $image = imagecreatefrompng($path); + imagealphablending($image, false); + imagesavealpha($image, true); + break; + case 'image/gif': + $image = imagecreatefromgif($path); + break; + } + + $w = imagesx($image); + $h = imagesy($image); + + $font = $GLOBALS['config']['includes_path'] . '/fonts/arial.ttf'; + $text = 'BANNED!'; + $bbox = imagettfbbox(40, 0, $font, $text); + + $text_width = abs($bbox[4]) - abs($bbox[0]); + $text_height = abs($bbox[5]) - abs($bbox[1]); + + // Draw blue rectangle + $rect_w = $text_width + 40; + $rect_h = $text_height + 40; + $rect = imagecreatetruecolor($rect_w, $rect_h); + + $red = imagecolorallocate($rect, 255, 0, 0); + imagefill($rect, 0, 0, $red); + + // Draw text + $white = imagecolorallocate($image, 255, 255, 255); + imagettftext($rect, 40, 0, 20, $rect_h - 20, $white, $font, $text); + + // Resize sticker + $new_sticker_w = 0.8 * $w; + $new_sticker_h = $new_sticker_w / ($rect_w / $rect_h); + $resized = imagecreatetruecolor($new_sticker_w, $new_sticker_h); + imagecopyresampled($resized, $rect, 0, 0, 0, 0, $new_sticker_w, $new_sticker_h, $rect_w, $rect_h); + + // Rotate sticker + $rect = imagerotate($resized, 15, imagecolorallocatealpha($rect, 0, 0, 0, 127)); + imagealphablending($rect, false); + imagesavealpha($rect, true); + + // Add sticker. + imagecopy($image, $rect, ($w - imagesx($rect)) / 2, ($h - imagesy($rect)) / 2, + 0, 0, imagesx($rect), imagesy($rect)); + + ob_start(); + switch($type) + { + case 'image/jpeg': + imagejpeg($image); + break; + case 'image/png': + imagepng($image); + break; + case 'image/gif': + imagegif($image); + break; + } + $data = ob_get_contents(); + ob_end_clean(); + imagedestroy($image); + + return $data; +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..e064691 --- /dev/null +++ b/index.php @@ -0,0 +1,70 @@ + +
    +
    +
    +

    Welcome to

    +
    + get_categories(CATEGORY_NO_PARENT) as $c) + { + $c['category_link'] = $config['http_host'] . '/threads.php?cat_id=' . $c['cat_id']; + $theme->category_item($c); + } + ?> +
    +
    + + + + +
    +
    +
    + \ No newline at end of file diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..9714d20 --- /dev/null +++ b/logout.php @@ -0,0 +1,7 @@ +end_current_session(); + +header('Location: ' . $config['http_host']); diff --git a/profile.php b/profile.php new file mode 100644 index 0000000..70f7bf9 --- /dev/null +++ b/profile.php @@ -0,0 +1,239 @@ +get_user_meta($_GET['u']); +} +else +{ + if (USER_IS_LOGGED_IN) + { + $user_details = $user_db->get_user_meta(USER_NICK); + } +} + +$title = ' – '.$config['site_name']; + +$theme_css = array('side_sections_right.css'); +if ($user_details !== null) +{ + $title = $user_details['username'].$title; + $css_paths[] = 'css/profile_layout.css'; + $css_paths[] = 'css/profile.css'; + + $theme_css[] = 'thread_item.css'; + $theme_css[] = 'profile.css'; + $theme_css[] = 'tab.css'; +} else { + $title = 'Page not found'.$title; +} +include($config['includes_path'] . '/header.php'); + +require $config['includes_path'].'/functions/time.php'; +?> +
    +
    + +

    (' . $user_details['fullname'] . ')'; + } + ?>

    + + + +
    +
    +
    '.$user_details['username'].' />
    +
    + '; + + if ($user_details['birthdate'] !== null) + { + $date = explode('-', $user_details['birthdate']); + + $age = date('Y') - $date[0]; + if (date('m') < $date[1] || (date('m') == $date[1] && date('d') < $date[2])) + { + $age--; + } + echo '
  • Age '.$age.'
  • '; + } + + if ($user_details['sex'] !== null) + { + echo '
  • Sex '.$user_details['sex'].'
  • '; + } + + if ($user_details['location'] !== null) + { + echo '
  • Location
    ' . $user_details['location'] . '
  • '; + } + + echo '
  • Joined
    '.readable_time($user_details['joined']).'
  • '; + + // Last online + $sec_diff = time() - $user_details['last_active']; + // Show active if user is active at least 1 minute ago. + if ($sec_diff <= 60) + { + echo '
  • Online
  • '; + } + else + { + echo '
  • Last Online
    '.readable_time($user_details['last_active']).'
  • '; + } + + echo ''; + ?> +
    + is_moderator($sess_info['user_id'])) + && !$user_db->is_admin($user_details['user_id'])) : ?> +
    ' . ($user_details['is_banned'] ? 'Unban User' : 'Ban User') . ''; + ?> +
    + +
    +
    +
    + $profile_link, + 'Recent Activities' => $profile_link . (preg_match('~\?.*$~', $profile_link) ? '&' : '?') . 'v=activity', + ); + + $active = 'All Threads'; + if (isset($_GET['v'])) + { + if ($_GET['v'] == 'activity') + { + $active = 'Recent Activities'; + } + } + + $theme->tab_menu($tab_menus, $active); + ?> +
    + get_num_threads_by_user($user_details['user_id']), 6); + + $offset = $pagination->get_start_index($pagination->get_current_page()); + $count = $pagination->get_items_per_page(); + $threads = $threads_db->get_threads_by_user_id($user_details['user_id'], $offset, $count); + + if ($threads !== null) + { + $num_replies_per_page = $config['default_num_replies_per_page']; + if (USER_IS_LOGGED_IN) + { + require($config['database_path'] . '/users_settings_database.php'); + $settings_db = new Users_settings_database($sess_info['user_id']); + $num_replies_per_page = $settings_db->get_num_replies_per_page(); + } + + foreach($threads as $t) + { + $t->topic = htmlspecialchars($t->topic); + $t->thread_link = $config['http_host'] . '/view.php?id=' . $t->thread_id; + unset($t->op); + + $t->time_created = readable_time($t->time_created); + + $options = array(); + $base_cat_id = $cat->get_base_cat($t->cat_id); + $is_moderator = $user_db->is_moderator($sess_info['user_id'], $base_cat_id); + if (USER_IS_LOGGED_IN && (USER_IS_ADMIN || $is_moderator)) + { + $options['Delete'] = $config['http_host'] . '/form/delete_thread.php?id=' + . $t->thread_id . '&ref='; + + // Will be used to redirect back to this page after deleting. + $ref = 'profile.php'; + if ((USER_IS_LOGGED_IN && $user_details['username'] != USER_NICK) || ! USER_IS_LOGGED_IN) + { + $ref .= '?u=' . $user_details['username']; + } + + if (($page = $pagination->get_current_page()) > 1) + { + $ref .= (preg_match('-\?.*$-', $ref)) ? "&page=$page" : "?page=$page"; + } + + $options['Delete'] .= urlencode($ref); + } + + if ($t->replies > $num_replies_per_page) + { + $options['Last »'] = $t->thread_link . '&page=l'; + } + + $theme->thread_item($t, $options); + } + + $pages = $pagination->get_number_of_pages(); + if ($pages > 1) + { + $current_page = $pagination->get_current_page(); + $page_link = $config['http_host'] . '/profile.php'; + if (USER_IS_LOGGED_IN) + { + if ($user_details['username'] != USER_NICK) + { + $page_link .= '?u=' . USER_NICK; + } + } + else + { + $page_link .= '?u=' . urlencode($_GET['u']); + } + + $theme->pages($pages, $current_page, $page_link); + } + } + else + { + echo 'No thread by '.$user_details['username']; + } + } + else + { + echo '
    '; + echo $user_details['username'] . ' created a new thread.'; + echo '
    '; + } + ?> +
    +
    +
    +
    +
    + +

    Page not found

    +

    The page your requested was not found

    + +
    + +
    +
    + \ No newline at end of file diff --git a/search.php b/search.php new file mode 100644 index 0000000..c923fa0 --- /dev/null +++ b/search.php @@ -0,0 +1,220 @@ +$1', $str); +} + +/** + * Returns the page a replies is in a thread. + * + * @param object + * @param int + * @param int + * @param int The number of replies to display in a page. + */ +function calc_page($db, $tid, $rid, $num_replies_per_page) +{ + $i = 0; + do + { + $replies = $db->get_replies($tid, $i, $num_replies_per_page); + $page = -1; + foreach ($replies as $r) + { + if ($r->reply_id == $rid) + { + $page = ($i / $num_replies_per_page) + 1; + break; + } + } + if ($page != -1) + { + return $page; + } + $i += $num_replies_per_page; + } + while ($replies != null); + + return null; +} + +function get_num_attachments($thread_id, $reply_id) +{ + global $config; + $attachment_dir = $config['attachment_path'].'/thread'.$thread_id.'/reply'.$reply_id; + if (file_exists($attachment_dir)) + { + return count(scandir($attachment_dir)) - 2; + } + else + { + return 0; + } +} + +$num_threads_per_page = $config['default_num_threads_per_page']; +$num_replies_per_page = $config['default_num_replies_per_page']; +// If user is logged in, number of threads/replies to show per page +// will be gotten from user settings. +if (USER_IS_LOGGED_IN) +{ + require($config['database_path'] . '/users_settings_database.php'); + $user_setting_db = new Users_settings_database($sess_info['user_id']); + + $num_threads_per_page = $user_setting_db->get_num_threads_per_page(); + $num_replies_per_page = $user_setting_db->get_num_replies_per_page(); +} +?> +
    +
    + get_thread_count_by_topic(trim($_GET['q'])); + + $pagination = new Pagination($result_total, $num_threads_per_page); + + $offset = $pagination->get_start_index($pagination->get_current_page()); + $results = $threads_db->get_threads_by_topic(trim($_GET['q']), $offset, $pagination->get_items_per_page()); + } + elseif ($_GET['w'] == 'posts') + { + require($config['database_path'] . '/threads_replies_database.php'); + $GLOBALS['replies_db'] = new Threads_replies_database(); + + $result_total = $replies_db->get_reply_count_by_reply(trim($_GET['q'])); + + $pagination = new Pagination($result_total, $num_replies_per_page); + + $offset = $pagination->get_start_index($pagination->get_current_page()); + $results = $replies_db->get_replies_by_reply(trim($_GET['q']), $offset, $pagination->get_items_per_page()); + } + + if ($results !== null) + { + require $config['includes_path'] . '/functions/time.php'; + + echo '

    '.$result_total.' result'.($result_total > 1 ? 's' : '').' found

    '; + echo '
    '; + for ($i = 0; $i < count($results); $i++) + { + if ($_GET['w'] == 'topics') + { + $r = $results[$i]; + $r->topic = add_span_tag(htmlspecialchars($r->topic), htmlspecialchars($_GET['q'])); + $r->thread_link = $config['http_host'].'/view.php?id='.$r->thread_id; + $r->op_profile_link = $config['http_host'].'/profile.php'; + $r->time_created = readable_time($r->time_created); + if ((USER_IS_LOGGED_IN && $r->op != USER_NICK) || !USER_IS_LOGGED_IN) + { + $r->op_profile_link .= '?u=' . $r->op; + } + + $options = array(); + + $base_cat_id = $cat->get_base_cat($r->cat_id); + $is_moderator = $user_db->is_moderator($sess_info['user_id'], $base_cat_id); + + if (USER_IS_LOGGED_IN && (USER_IS_ADMIN || $is_moderator)) + { + $options['Delete'] = $config['http_host'].'/form/delete_thread.php?id='.$r->thread_id.'&ref='; + + // This will be used to redirect back to this page. + $ref = 'search.php'.urlencode('?').'w='.urlencode($_GET['w'] . '&').'q='.urlencode($_GET['q']); + if (($page = $pagination->get_current_page()) > 1) + { + $ref .= urlencode("&page=$page"); + } + + $options['Delete'] .= $ref; + } + + if ($r->replies > $num_replies_per_page) { + $options['Last »'] = $r->thread_link . '&page=l'; + } + + $theme->thread_item($r, $options); + } + elseif ($_GET['w'] == 'posts') + { + $r = $results[$i]; + $r->topic = htmlspecialchars($r->topic); + $r->reply = add_span_tag(htmlspecialchars($r->reply), htmlspecialchars($_GET['q'])); + $r->time_replied = readable_time($r->time_replied); + + $r->thread_link = $config['http_host'].'/view.php?id=' . $r->thread_id; + $thread_page = calc_page($replies_db, $r->thread_id, $r->reply_id, $num_replies_per_page); + if ($thread_page > 1) + { + $r->thread_link .= '&page='.$thread_page; + } + $r->thread_link .= '#r'.$r->reply_id; + + $r->num_attachments = get_num_attachments($r->thread_id, $r->reply_id); + + $theme->search_result_reply_item($r); + } + } + echo '
    '; + + $pages = $pagination->get_number_of_pages(); + if ($pages > 1) + { + $current_page = $pagination->get_current_page(); + $page_link = $config['http_host'] . '/search.php?w='.$_GET['w'].'&q='.$_GET['q']; + + $theme->pages($pages, $current_page, $page_link); + } + } + else + { + echo '

    No result found for

    '; + echo '
    '.htmlspecialchars($_GET['q']).'
    '; + } + } else { + echo '

    An error occurred

    '; + } + ?> +
    + +
    +
    + diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..96811df --- /dev/null +++ b/settings.php @@ -0,0 +1,166 @@ + +
    +
    +

    Settings

    +
    + array( + 'Change Email' => "$config[http_host]/settings.php?a=ch_email", + 'Change Password' => "$config[http_host]/settings.php?a=ch_password", + ), + 'Threads' => array( + 'Pagination' => "$config[http_host]/settings.php?a=thr_pagination", + ), + ); + $theme->side_nav($side_nav, $active); + ?> +
    + +
    " method="POST" accept-charset="UTF-8"> +
    +
    + field_value_exists('ch_email', 'current_email')) + { + echo ' value="' . $error->get_field_value('ch_email', 'current_email') . '"'; + } + ?> /> +
    +
    +
    + field_value_exists('ch_email', 'new_email')) + { + echo ' value="' . $error->get_field_value('ch_email', 'new_email') . '"'; + } + ?> /> +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    " method="POST" accept-charset="UTF-8"> + + + + + + + + + +
    + +
    + +
    +
    +
    +
    + +
    +
    + \ No newline at end of file diff --git a/signup.php b/signup.php new file mode 100644 index 0000000..765e2d3 --- /dev/null +++ b/signup.php @@ -0,0 +1,87 @@ + +
    +
    +

    Sign Up for

    +
    +
    +
    + field_value_exists('signup', 'username')) + { + echo ' value="' . $error->get_field_value('signup', 'username') . '"'; + } + ?> class="textfield" /> +
    +
    +
    + field_value_exists('signup', 'email')) + { + echo ' value="' . $error->get_field_value('signup', 'email') . '"'; + } + ?> class="textfield" /> +
    +
    +
    + field_value_exists('signup', 'password')) + { + echo ' value="' . $error->get_field_value('signup', 'password') . '"'; + } + ?> class="textfield" /> +
    +
    +
    + field_value_exists('signup', 're_password')) + { + echo ' value="' . $error->get_field_value('signup', 're_password') . '"'; + } + ?> class="textfield" /> +
    +
    + + + exists('signup', 'terms_acceptance')) + { + echo '

    ' . $error->get_message('signup', 'terms_acceptance') . '

    '; + } + ?> +
    +
    +
    +
    + get_categories(0); + include($config['includes_path'] . '/right-sections.php'); + ?> +
    +
    +clear('signup'); +include($config['includes_path'] . '/footer.php'); +?> \ No newline at end of file diff --git a/threads.php b/threads.php new file mode 100644 index 0000000..cf76f1a --- /dev/null +++ b/threads.php @@ -0,0 +1,160 @@ +get_cat_by_id($_GET['cat_id']); + if ($meta !== null) { + define('CURRENT_CATEGORY_ID', $meta['id']); + define('CURRENT_CATEGORY_NAME', $meta['name']); + + define('CURRENT_CATEGORY_THREAD_COUNT', $meta['threads'] === null ? 0 : $meta['threads']); + + $base_cat = $cat->get_base_cat(CURRENT_CATEGORY_ID); + define('USER_IS_MODERATOR', $user_db->is_moderator($sess_info['user_id'], $base_cat)); + } +} +else +{ + header('Location: ' . $config['http_host']); die(); +} + +$pagination = null; +$title = ' – ' . $config['site_name']; + +$user_settings_db = null; +if (defined('CURRENT_CATEGORY_ID')) { + // Get number of threads to display per page. + $num_threads_per_page = $config['default_num_threads_per_page']; + if (USER_IS_LOGGED_IN) + { + // If user is logged in, load from user's settings. + require($config['database_path'] . '/users_settings_database.php'); + $user_settings_db = new Users_settings_database($sess_info['user_id']); + $num_threads_per_page = $user_settings_db->get_num_threads_per_page(); + } + + require($config['__'] . '/pagination.php'); + $pagination = new Pagination(CURRENT_CATEGORY_THREAD_COUNT, $num_threads_per_page); + + $title = CURRENT_CATEGORY_NAME.$title; +} else { + $title = 'Page not found'.$title; +} + +$css_paths[] = 'css/threads.css'; + +$theme_css[] = 'thread_item.css'; +$theme_css[] = 'side_sections_right.css'; +$theme_css[] = 'threads_toolbar.css'; + +require( $config['includes_path'] . '/header.php' ); +?> +
    +
    + +
    + +

    + '; + echo 'Create Thread'; + + if (USER_IS_ADMIN || USER_IS_MODERATOR) + { + echo 'Create Subcategory'; + } + + echo '
    '; + } + ?> +
    +
    + get_categories(CURRENT_CATEGORY_ID); + foreach ($categories as $c) + { + $c['category'] = htmlspecialchars($c['category']); + $c['category_link'] = $config['http_host'] . '/threads.php?cat_id=' . $c['cat_id']; + $c['description'] = htmlspecialchars($c['description']); + + $options = array(); + if (USER_IS_LOGGED_IN && (USER_IS_ADMIN || USER_IS_MODERATOR)) { + $options['Edit'] = $config['http_host'] . '/admin/edit_subcategory.php?cat_id=' . $c['cat_id']; + $options['Delete'] = $config['http_host'] . '/admin/form/subcategory.php?a=delete&cat_id=' . $c['cat_id']; + } + $theme->sub_category_item($c, $options); + } + + require($config['database_path'] . '/threads_database.php'); + $threads_db = new Threads_Database(); + + $offset = $pagination->get_start_index($pagination->get_current_page()); + $threads = $threads_db->get_threads_by_cat_id(CURRENT_CATEGORY_ID, $offset, $pagination->get_items_per_page()); + + if ($threads !== null) + { + // Number of replies to display per page in view.php + $num_replies_per_page = $config['default_num_replies_per_page']; + if (USER_IS_LOGGED_IN) + { + $num_replies_per_page = $user_settings_db->get_num_replies_per_page(); + } + + require $config['includes_path'] . '/time.php'; + + foreach ($threads as $t) + { + $t->thread_link = $config['http_host'] . '/view.php?id=' . $t->thread_id; + $t->op_profile_link = $config['http_host'] . '/profile.php'; + if (USER_IS_LOGGED_IN && $t->op != USER_NICK) { + $t->op_profile_link .= '?u=' . $t->op; + } + $t->topic = htmlspecialchars($t->topic); + + $t->time_created = readable_time($t->time_created); + + $links = array(); + if (USER_IS_LOGGED_IN && (USER_IS_ADMIN || USER_IS_MODERATOR)) { + $links['Delete'] = $config['http_host'] . '/form/delete_thread.php?id=' . $t->thread_id; + } + + if ($t->replies > $num_replies_per_page) + { + $links['Last »'] = $config['http_host'] . '/view.php?id=' . $t->thread_id . '&page=l'; + } + + $theme->thread_item($t, $links); + } + } + else + { + echo '
    No threads under ' . CURRENT_CATEGORY_NAME . '
    '; + } + ?> +
    + get_number_of_pages(); + if ($pages > 1) { + $current_page = $pagination->get_current_page(); + $page_link = $config['http_host'] . '/threads.php?cat_id=' . CURRENT_CATEGORY_ID; + + $theme->pages($pages, $current_page, $page_link); + } + ?> + +

    Page not found

    +

    The page you requested was not found.

    + +
    + +
    + + \ No newline at end of file diff --git a/view.php b/view.php new file mode 100644 index 0000000..7848339 --- /dev/null +++ b/view.php @@ -0,0 +1,262 @@ +get_thread_meta(CURRENT_THREAD_ID); +if ($current_thread !== NULL) +{ + $base_cat_id = $cat->get_base_cat($current_thread['cat_id']); + define('USER_IS_MODERATOR', $user_db->is_moderator($sess_info['user_id'], $base_cat_id)); + + $current_thread['topic'] = htmlspecialchars($current_thread['topic']); + + $current_user_id = USER_IS_LOGGED_IN ? $sess_info['user_id'] : 0; + if ($views_db->user_has_viewed(CURRENT_THREAD_ID, $current_user_id)) + { + if (!isset($_COOKIE['current_thread']) || (isset($_COOKIE['current_thread']) && $_COOKIE['current_thread'] != CURRENT_THREAD_ID)) + { + $views_db->increase_view(CURRENT_THREAD_ID, $current_user_id); + } + elseif (isset($_COOKIE['current_thread']) && $_COOKIE['current_thread'] == CURRENT_THREAD_ID) + { + $views_db->update_last_viewed(CURRENT_THREAD_ID, $current_user_id); + } + } + else + { + $views_db->add_new_view(CURRENT_THREAD_ID, $current_user_id); + } + + // Cookie is used the track the current thread the user is viewing. + // It is used to prevent a page from getting multiple view count when a user + // navigates through different pages in the same thread. + setcookie('current_thread', CURRENT_THREAD_ID, 0); + + // Pagination + $num_replies_per_page = $config['default_num_replies_per_page']; + // If user is logged in, load from user's settings. + if (USER_IS_LOGGED_IN) + { + require($config['database_path'] . '/users_settings_database.php'); + $user_setting_db = new Users_settings_database($sess_info['user_id']); + $num_replies_per_page = $user_setting_db->get_num_replies_per_page(); + } + require($config['__'] . '/pagination.php'); + $pagination = new Pagination($current_thread['replies'], $num_replies_per_page); + + // Title + $title = $current_thread['topic'].$title; + + // Theme styles + $theme_css[] = 'side_topics.css'; + $theme_css[] = 'thread_reply.css'; + $theme_css[] = 'button.css'; + $theme_css[] = 'currently_viewing.css'; +} +else +{ + $title = 'Not found'.$title; +} + +include($config['includes_path'] . '/header.php'); +?> +
    +
    + + get_cat_by_id($current_thread['cat_id']); + get_category_hierarchy($current_cat_meta['id'], $current_cat_meta['name']); + ?> +

    +
    By on .
    +
    +
    + get_recent_threads($current_thread['cat_id'], CURRENT_THREAD_ID, 6); + if ($recent_threads !== null) { + for ($i = 0; $i < count($recent_threads); $i++) { + $recent_threads[ $i ]->thread_link = $config['http_host'] . '/view.php?id='.$recent_threads[ $i ]->thread_id; + unset($recent_threads[ $i ]->thread_id); + + $recent_threads[ $i ]->topic = htmlspecialchars( $recent_threads[ $i ]->topic ); + } + $theme->side_topics( 'Recent Threads', $recent_threads ); + } else { + echo '

    No recent threads

    '; + } + ?> +
    +
    +
    + get_start_index($pagination->get_current_page()); + $replies = $replies_db->get_replies(CURRENT_THREAD_ID, $offset, $pagination->get_items_per_page()); + if ($replies !== NULL) + { + require($config['__'] . '/file.php'); + require($config['includes_path'] . '/functions/time.php'); + + foreach ($replies as $r) + { + $r->time_replied = readable_time($r->time_replied); + $r->reply = htmlspecialchars($r->reply); + + // Get attachments + $attachments = array(); + + $relpath = 'thread' . CURRENT_THREAD_ID . '/reply' . $r->reply_id; + $path = $config['attachment_path'] . '/' . $relpath; + if (file_exists($path)) + { + $files = array_slice(scandir($path), 2); + if (count($files) > 0) + { + foreach ($files as $f) + { + $a = array( + 'name' => $f, + 'size' => convert_bytes(filesize($path . '/' . $f)), + 'link' => $config['http_host'].'/file.php?p=' . urlencode($relpath . '/' . $f), + ); + $a['download_link'] = $a['link'].'&download'; + $attachments[] = $a; + } + } + } + + $options = array(); + if (USER_IS_LOGGED_IN) + { + $options['Quote'] = '#" id="'.$r->reply_id.'" class="quote_reply'; + + if ($sess_info['user_id'] == $r->user_id) + { + $options['Edit'] = $config['http_host'] . '/edit_reply.php?id=' . $r->reply_id; + } + if ((USER_IS_ADMIN || USER_IS_MODERATOR) && $r->first_post !== 1) + { + $options['Delete'] = $config['http_host'] . '/form/reply_action.php?a=delete&id=' . $r->reply_id; + } + } + + $theme->thread_reply($r, $attachments, $options); + } + } + ?> +
    + + get_number_of_pages(); + if ($pages > 1) { + $page_link = $config['http_host'] . '/view.php?id='.CURRENT_THREAD_ID; + $current_page = $pagination->get_current_page(); + + $theme->pages($pages, $current_page, $page_link); + } + ?> + + +
    +
    +
    +
    +
    + +
    + +
    + + +
    Log in to reply to thread
    + +
    +
    +
    + get_current_viewers(CURRENT_THREAD_ID, USER_IS_LOGGED_IN ? $sess_info['user_id'] : NULL); + $guests_count = $views_db->get_number_of_guests(CURRENT_THREAD_ID); + + if (USER_IS_LOGGED_IN) { + if ($current_viewers == null) { + $current_viewers = array('You'); + } else { + array_unshift($current_viewers, 'You'); + } + } + + if ($guests_count > 0 || $current_viewers !== NULL) + { + $theme->currently_viewing_thread($current_viewers, $guests_count); + } + ?> + +

    Not found

    +

    The thread does not exists or has been deleted.

    + +
    + +
    +
    + \ No newline at end of file From 759ed16d14a4fcb5b0db034eb7748d8d07b2423a Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:43:37 +0100 Subject: [PATCH 11/12] Add files via upload --- admin/setup.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/admin/setup.php b/admin/setup.php index e0f871f..5b4ca7f 100644 --- a/admin/setup.php +++ b/admin/setup.php @@ -5,6 +5,11 @@ define('PAGE_URL', $config['http_host'].'/admin/setup.php'); +if (!file_exists(TMP_DIR)) +{ + mkdir(TMP_DIR); +} + // used to track user's current steps and stores error messages. $setup = null; if (isset($_COOKIE['setup']) && file_exists(TMP_DIR.'/'.$_COOKIE['setup'].'.txt')) From 5c1cd059bebbdd55b2a2b5e4c2e70611692f0645 Mon Sep 17 00:00:00 2001 From: Daniel Austin <31685243+danprocoder@users.noreply.github.com> Date: Sun, 17 Sep 2017 00:45:25 +0100 Subject: [PATCH 12/12] Add files via upload --- admin/form/setup.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/admin/form/setup.php b/admin/form/setup.php index e6b14a3..5084b57 100644 --- a/admin/form/setup.php +++ b/admin/form/setup.php @@ -5,6 +5,11 @@ define('SETUP_TOTAL_STEPS', 4); +if (!file_exists(TMP_DIR)) +{ + mkdir(TMP_DIR); +} + if (!(isset($_COOKIE['setup']) && file_exists(TMP_DIR.'/'.$_COOKIE['setup'].'.txt'))) { header('Location: ' . ADMIN_URL . '/setup.php');
    '; + echo ''.$thread->topic.''; + + if (!empty($options)) + { + echo '
    '; + $anchors = array(); + foreach ($options as $n => $u) + { + $anchors[] = "$n"; + } + echo implode(' | ', $anchors); + echo '
    '; + } + echo '