diff --git a/README.markdown b/README.markdown
index 23dec0ee..853a37ee 100644
--- a/README.markdown
+++ b/README.markdown
@@ -100,10 +100,10 @@ permissions like so:
```php
new UserClass("anonymous", "base", [
- "create_comment" => True,
- "edit_image_tag" => True,
- "edit_image_source" => True,
- "create_image_report" => True,
+ Permissions::CREATE_COMMENT => True,
+ Permissions::EDIT_IMAGE_TAG => True,
+ Permissions::EDIT_IMAGE_SOURCE => True,
+ Permissions::CREATE_IMAGE_REPORT => True,
]);
```
@@ -111,12 +111,12 @@ For a moderator class, being a regular user who can delete images and comments:
```php
new UserClass("moderator", "user", [
- "delete_image" => True,
- "delete_comment" => True,
+ Permissions::DELETE_IMAGE => True,
+ Permissions::DELETE_COMMENT => True,
]);
```
-For a list of permissions, see `core/userclass.php`
+For a list of permissions, see `core/permissions.php`
# Development Info
diff --git a/core/_bootstrap.php b/core/_bootstrap.php
index 6dffbf14..a003ed6e 100644
--- a/core/_bootstrap.php
+++ b/core/_bootstrap.php
@@ -15,6 +15,11 @@ require_once "vendor/autoload.php";
_version_check();
_sanitise_environment();
+// The trace system has a certain amount of memory consumption every time it is used,
+// so to prevent running out of memory during complex operations code that uses it should
+// check if tracer output is enabled before making use of it.
+$tracer_enabled = constant('TRACE_FILE')!==null;
+
// load base files
$_tracer->begin("Bootstrap");
$_tracer->begin("Opening files");
diff --git a/core/database.php b/core/database.php
index 003816d4..2cbf077f 100644
--- a/core/database.php
+++ b/core/database.php
@@ -190,10 +190,12 @@ class Database
private function count_time(string $method, float $start, string $query, ?array $args): void
{
- global $_tracer;
+ global $_tracer, $tracer_enabled;
$dur = microtime(true) - $start;
- $query = trim(preg_replace('/^[\t ]+/m', '', $query)); // trim leading whitespace
- $_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query"=>$query, "args"=>$args, "method"=>$method]);
+ if($tracer_enabled) {
+ $query = trim(preg_replace('/^[\t ]+/m', '', $query)); // trim leading whitespace
+ $_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query"=>$query, "args"=>$args, "method"=>$method]);
+ }
$this->query_count++;
$this->dbtime += $dur;
}
diff --git a/core/event.php b/core/event.php
index 349a6ce0..c9064c3e 100644
--- a/core/event.php
+++ b/core/event.php
@@ -60,7 +60,7 @@ class PageRequestEvent extends Event
// if path is not specified, use the default front page
if (empty($path)) { /* empty is faster than strlen */
- $path = $config->get_string('front_page');
+ $path = $config->get_string(SetupConfig::FRONT_PAGE);
}
// break the path into parts
diff --git a/core/imageboard/image.php b/core/imageboard/image.php
index 90ab9e6a..598a2ab7 100644
--- a/core/imageboard/image.php
+++ b/core/imageboard/image.php
@@ -129,7 +129,7 @@ class Image
}
if (SPEED_HAX) {
- if (!$user->can("big_search") and count($tags) > 3) {
+ if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) {
throw new SCoreException("Anonymous users may only search for up to 3 tags at a time");
}
}
@@ -813,7 +813,7 @@ class Image
$tmpl = str_replace('$size', "{$this->width}x{$this->height}", $tmpl);
$tmpl = str_replace('$filesize', to_shorthand_int($this->filesize), $tmpl);
$tmpl = str_replace('$filename', $_escape($base_fname), $tmpl);
- $tmpl = str_replace('$title', $_escape($config->get_string("title")), $tmpl);
+ $tmpl = str_replace('$title', $_escape($config->get_string(SetupConfig::TITLE)), $tmpl);
$tmpl = str_replace('$date', $_escape(autodate($this->posted, false)), $tmpl);
// nothing seems to use this, sending the event out to 50 exts is a lot of overhead
diff --git a/core/page.php b/core/page.php
index e31ed7d3..7efa2833 100644
--- a/core/page.php
+++ b/core/page.php
@@ -299,9 +299,55 @@ class Page
$this->add_cookie("flash_message", "", -1, "/");
}
usort($this->blocks, "blockcmp");
+ $pnbe = new PageNavBuildingEvent();
+ send_event($pnbe);
+
+ $nav_links = $pnbe->links;
+
+ $active_link = null;
+ // To save on event calls, we check if one of the top-level links has already been marked as active
+ foreach ($nav_links as $link) {
+ if($link->active===true) {
+ $active_link = $link;
+ break;
+ }
+ }
+ $sub_links = null;
+ // If one is, we just query for sub-menu options under that one tab
+ if($active_link!==null) {
+ $psnbe = new PageSubNavBuildingEvent($active_link->name);
+ send_event($psnbe);
+ $sub_links = $psnbe->links;
+ } else {
+ // Otherwise we query for the sub-items under each of the tabs
+ foreach ($nav_links as $link) {
+ $psnbe = new PageSubNavBuildingEvent($link->name);
+ send_event($psnbe);
+
+ // Now we check for a current link so we can identify the sub-links to show
+ foreach ($psnbe->links as $sub_link) {
+ if($sub_link->active===true) {
+ $sub_links = $psnbe->links;
+ break;
+ }
+ }
+ // If the active link has been detected, we break out
+ if($sub_links!==null) {
+ $link->active = true;
+ break;
+ }
+ }
+ }
+
+
+
+ $sub_links = $sub_links??[];
+ usort($nav_links, "sort_nav_links");
+ usort($sub_links, "sort_nav_links");
+
$this->add_auto_html_headers();
$layout = new Layout();
- $layout->display_page($page);
+ $layout->display_page($page, $nav_links, $sub_links);
break;
case PageMode::DATA:
header("Content-Length: " . strlen($this->data));
@@ -405,7 +451,7 @@ class Page
global $config;
$data_href = get_base_href();
- $theme_name = $config->get_string('theme', 'default');
+ $theme_name = $config->get_string(SetupConfig::THEME, 'default');
$this->add_html_header("", 40);
@@ -471,3 +517,99 @@ class Page
$this->add_html_header("", 44);
}
}
+
+class PageNavBuildingEvent extends Event
+{
+ public $links = [];
+
+ public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
+ {
+ $this->links[] = new NavLink($name, $link, $desc, $active, $order);
+ }
+}
+
+class PageSubNavBuildingEvent extends Event
+{
+ public $parent;
+
+ public $links = [];
+
+ public function __construct(string $parent)
+ {
+ $this->parent= $parent;
+ }
+
+ public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
+ {
+ $this->links[] = new NavLink($name, $link, $desc, $active,$order);
+ }
+}
+
+class NavLink
+{
+ public $name;
+ public $link;
+ public $description;
+ public $order;
+ public $active = false;
+
+ public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50)
+ {
+ global $config;
+
+ $this->name = $name;
+ $this->link = $link;
+ $this->description = $description;
+ $this->order = $order;
+ if($active==null) {
+ $query = ltrim(_get_query(), "/");
+ if ($query === "") {
+ // This indicates the front page, so we check what's set as the front page
+ $front_page = trim($config->get_string(SetupConfig::FRONT_PAGE), "/");
+
+ if ($front_page === $link->page) {
+ $this->active = true;
+ } else {
+ $this->active = self::is_active([$link->page], $front_page);
+ }
+ } elseif($query===$link->page) {
+ $this->active = true;
+ }else {
+ $this->active = self::is_active([$link->page]);
+ }
+ } else {
+ $this->active = $active;
+ }
+
+ }
+
+ public static function is_active(array $pages_matched, string $url = null): bool
+ {
+ /**
+ * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.)
+ */
+ $url = $url??ltrim(_get_query(), "/");
+
+ $re1='.*?';
+ $re2='((?:[a-z][a-z_]+))';
+
+ if (preg_match_all("/".$re1.$re2."/is", $url, $matches)) {
+ $url=$matches[1][0];
+ }
+
+ $count_pages_matched = count($pages_matched);
+
+ for ($i=0; $i < $count_pages_matched; $i++) {
+ if ($url == $pages_matched[$i]) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+function sort_nav_links(NavLink $a, NavLink $b)
+{
+ return $a->order - $b->order;
+}
diff --git a/core/permissions.php b/core/permissions.php
new file mode 100644
index 00000000..4378a9c0
--- /dev/null
+++ b/core/permissions.php
@@ -0,0 +1,70 @@
+begin(get_class($event));
// SHIT: http://bugs.php.net/bug.php?id=35106
$my_event_listeners = $_shm_event_listeners[get_class($event)];
diff --git a/core/sys_config.php b/core/sys_config.php
index 9ce0d8d0..5dc80459 100644
--- a/core/sys_config.php
+++ b/core/sys_config.php
@@ -40,7 +40,7 @@ _d("SEARCH_ACCEL", false); // boolean use search accelerator
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
_d("VERSION", '2.7-beta'); // string shimmie version
_d("TIMEZONE", null); // string timezone
-_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor,media"); // extensions to always enable
+_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor,media,help_pages,system"); // extensions to always enable
_d("EXTRA_EXTS", ""); // string optional extra extensions
_d("BASE_URL", null); // string force a specific base URL (default is auto-detect)
_d("MIN_PHP_VERSION", '7.1');// string minimum supported PHP version
diff --git a/core/urls.php b/core/urls.php
index e4fc0977..457bfe1b 100644
--- a/core/urls.php
+++ b/core/urls.php
@@ -3,6 +3,23 @@
* HTML Generation *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+class Link
+{
+ public $page;
+ public $query;
+
+ public function __construct(?string $page=null, ?string $query=null)
+ {
+ $this->page = $page;
+ $this->query = $query;
+ }
+
+ public function make_link(): string
+ {
+ return make_link($this->page, $this->query);
+ }
+}
+
/**
* Figure out the correct way to link to a page, taking into account
* things like the nice URLs setting.
@@ -14,7 +31,7 @@ function make_link(?string $page=null, ?string $query=null): string
global $config;
if (is_null($page)) {
- $page = $config->get_string('main_page');
+ $page = $config->get_string(SetupConfig::MAIN_PAGE);
}
if (!is_null(BASE_URL)) {
diff --git a/core/userclass.php b/core/userclass.php
index bcd05c36..a66ad6f7 100644
--- a/core/userclass.php
+++ b/core/userclass.php
@@ -72,134 +72,138 @@ class UserClass
// action = create / view / edit / delete
// object = image / user / tag / setting
new UserClass("base", null, [
- "change_setting" => false, # modify web-level settings, eg the config table
- "override_config" => false, # modify sys-level settings, eg shimmie.conf.php
- "big_search" => false, # search for more than 3 tags at once (speed mode only)
+ Permissions::CHANGE_SETTING => false, # modify web-level settings, eg the config table
+ Permissions::OVERRIDE_CONFIG => false, # modify sys-level settings, eg shimmie.conf.php
+ Permissions::BIG_SEARCH => false, # search for more than 3 tags at once (speed mode only)
- "manage_extension_list" => false,
- "manage_alias_list" => false,
- "mass_tag_edit" => false,
+ Permissions::MANAGE_EXTENSION_LIST => false,
+ Permissions::MANAGE_ALIAS_LIST => false,
+ Permissions::MASS_TAG_EDIT => false,
- "view_ip" => false, # view IP addresses associated with things
- "ban_ip" => false,
+ Permissions::VIEW_IP => false, # view IP addresses associated with things
+ Permissions::BAN_IP => false,
- "edit_user_name" => false,
- "edit_user_password" => false,
- "edit_user_info" => false, # email address, etc
- "edit_user_class" => false,
- "delete_user" => false,
+ Permissions::EDIT_USER_NAME => false,
+ Permissions::EDIT_USER_PASSWORD => false,
+ Permissions::EDIT_USER_INFO => false, # email address, etc
+ Permissions::EDIT_USER_CLASS => false,
+ Permissions::DELETE_USER => false,
- "create_comment" => false,
- "delete_comment" => false,
- "bypass_comment_checks" => false, # spam etc
+ Permissions::CREATE_COMMENT => false,
+ Permissions::DELETE_COMMENT => false,
+ Permissions::BYPASS_COMMENT_CHECKS => false, # spam etc
- "replace_image" => false,
- "create_image" => false,
- "edit_image_tag" => false,
- "edit_image_source" => false,
- "edit_image_owner" => false,
- "edit_image_lock" => false,
- "bulk_edit_image_tag" => false,
- "bulk_edit_image_source" => false,
- "delete_image" => false,
+ Permissions::REPLACE_IMAGE => false,
+ Permissions::CREATE_IMAGE => false,
+ Permissions::EDIT_IMAGE_TAG => false,
+ Permissions::EDIT_IMAGE_SOURCE => false,
+ Permissions::EDIT_IMAGE_OWNER => false,
+ Permissions::EDIT_IMAGE_LOCK => false,
+ Permissions::EDIT_IMAGE_TITLE => false,
+ Permissions::BULK_EDIT_IMAGE_TAG => false,
+ Permissions::BULK_EDIT_IMAGE_SOURCE => false,
+ Permissions::DELETE_IMAGE => false,
- "ban_image" => false,
+ Permissions::BAN_IMAGE => false,
- "view_eventlog" => false,
- "ignore_downtime" => false,
+ Permissions::VIEW_EVENTLOG => false,
+ Permissions::IGNORE_DOWNTIME => false,
- "create_image_report" => false,
- "view_image_report" => false, # deal with reported images
+ Permissions::CREATE_IMAGE_REPORT => false,
+ Permissions::VIEW_IMAGE_REPORT => false, # deal with reported images
- "edit_wiki_page" => false,
- "delete_wiki_page" => false,
+ Permissions::EDIT_WIKI_PAGE => false,
+ Permissions::DELETE_WIKI_PAGE => false,
- "manage_blocks" => false,
+ Permissions::MANAGE_BLOCKS => false,
- "manage_admintools" => false,
+ Permissions::MANAGE_ADMINTOOLS => false,
- "view_other_pms" => false,
- "edit_feature" => false,
- "bulk_edit_vote" => false,
- "edit_other_vote" => false,
- "view_sysinfo" => false,
+ Permissions::VIEW_OTHER_PMS => false,
+ Permissions::EDIT_FEATURE => false,
+ Permissions::BULK_EDIT_VOTE => false,
+ Permissions::EDIT_OTHER_VOTE => false,
+ Permissions::VIEW_SYSINTO => false,
- "hellbanned" => false,
- "view_hellbanned" => false,
+ Permissions::HELLBANNED => false,
+ Permissions::VIEW_HELLBANNED => false,
- "protected" => false, # only admins can modify protected users (stops a moderator changing an admin's password)
+ Permissions::PROTECTED => false, # only admins can modify protected users (stops a moderator changing an admin's password)
- "edit_image_rating" => false,
- "bulk_edit_image_rating" => false,
+ Permissions::EDIT_IMAGE_RATING => false,
+ Permissions::BULK_EDIT_IMAGE_RATING => false,
- "view_trash" => false,
- "perform_bulk_actions" => false,
+ Permissions::VIEW_TRASH => false,
+
+ Permissions::PERFORM_BULK_ACTIONS => false,
]);
new UserClass("anonymous", "base", [
]);
new UserClass("user", "base", [
- "big_search" => true,
- "create_image" => true,
- "create_comment" => true,
- "edit_image_tag" => true,
- "edit_image_source" => true,
- "create_image_report" => true,
- "edit_image_rating" => true,
+ Permissions::BIG_SEARCH => true,
+ Permissions::CREATE_IMAGE => true,
+ Permissions::CREATE_COMMENT => true,
+ Permissions::EDIT_IMAGE_TAG => true,
+ Permissions::EDIT_IMAGE_SOURCE => true,
+ Permissions::EDIT_IMAGE_TITLE => true,
+ Permissions::CREATE_IMAGE_REPORT => true,
+ Permissions::EDIT_IMAGE_RATING => true,
]);
new UserClass("admin", "base", [
- "change_setting" => true,
- "override_config" => true,
- "big_search" => true,
- "edit_image_lock" => true,
- "view_ip" => true,
- "ban_ip" => true,
- "edit_user_name" => true,
- "edit_user_password" => true,
- "edit_user_info" => true,
- "edit_user_class" => true,
- "delete_user" => true,
- "create_image" => true,
- "delete_image" => true,
- "ban_image" => true,
- "create_comment" => true,
- "delete_comment" => true,
- "bypass_comment_checks" => true,
- "replace_image" => true,
- "manage_extension_list" => true,
- "manage_alias_list" => true,
- "edit_image_tag" => true,
- "edit_image_source" => true,
- "edit_image_owner" => true,
- "bulk_edit_image_tag" => true,
- "bulk_edit_image_source" => true,
- "mass_tag_edit" => true,
- "create_image_report" => true,
- "view_image_report" => true,
- "edit_wiki_page" => true,
- "delete_wiki_page" => true,
- "view_eventlog" => true,
- "manage_blocks" => true,
- "manage_admintools" => true,
- "ignore_downtime" => true,
- "view_other_pms" => true,
- "edit_feature" => true,
- "bulk_edit_vote" => true,
- "edit_other_vote" => true,
- "view_sysinfo" => true,
- "view_hellbanned" => true,
- "protected" => true,
- "edit_image_rating" => true,
- "bulk_edit_image_rating" => true,
- "view_trash" => true,
- "perform_bulk_actions" => true,
+ Permissions::CHANGE_SETTING => true,
+ Permissions::OVERRIDE_CONFIG => true,
+ Permissions::BIG_SEARCH => true,
+ Permissions::EDIT_IMAGE_LOCK => true,
+ Permissions::VIEW_IP => true,
+ Permissions::BAN_IP => true,
+ Permissions::EDIT_USER_NAME => true,
+ Permissions::EDIT_USER_PASSWORD => true,
+ Permissions::EDIT_USER_INFO => true,
+ Permissions::EDIT_USER_CLASS => true,
+ Permissions::DELETE_USER => true,
+ Permissions::CREATE_IMAGE => true,
+ Permissions::DELETE_IMAGE => true,
+ Permissions::BAN_IMAGE => true,
+ Permissions::CREATE_COMMENT => true,
+ Permissions::DELETE_COMMENT => true,
+ Permissions::BYPASS_COMMENT_CHECKS => true,
+ Permissions::REPLACE_IMAGE => true,
+ Permissions::MANAGE_EXTENSION_LIST => true,
+ Permissions::MANAGE_ALIAS_LIST => true,
+ Permissions::EDIT_IMAGE_TAG => true,
+ Permissions::EDIT_IMAGE_SOURCE => true,
+ Permissions::EDIT_IMAGE_OWNER => true,
+ Permissions::EDIT_IMAGE_TITLE => true,
+ Permissions::BULK_EDIT_IMAGE_TAG => true,
+ Permissions::BULK_EDIT_IMAGE_SOURCE => true,
+ Permissions::MASS_TAG_EDIT => true,
+ Permissions::CREATE_IMAGE_REPORT => true,
+ Permissions::VIEW_IMAGE_REPORT => true,
+ Permissions::EDIT_WIKI_PAGE => true,
+ Permissions::DELETE_WIKI_PAGE => true,
+ Permissions::VIEW_EVENTLOG => true,
+ Permissions::MANAGE_BLOCKS => true,
+ Permissions::MANAGE_ADMINTOOLS => true,
+ Permissions::IGNORE_DOWNTIME => true,
+ Permissions::VIEW_OTHER_PMS => true,
+ Permissions::EDIT_FEATURE => true,
+ Permissions::BULK_EDIT_VOTE => true,
+ Permissions::EDIT_OTHER_VOTE => true,
+ Permissions::VIEW_SYSINTO => true,
+ Permissions::VIEW_HELLBANNED => true,
+ Permissions::PROTECTED => true,
+ Permissions::EDIT_IMAGE_RATING => true,
+ Permissions::BULK_EDIT_IMAGE_RATING => true,
+ Permissions::VIEW_TRASH => true,
+ Permissions::PERFORM_BULK_ACTIONS => true,
]);
new UserClass("hellbanned", "user", [
- "hellbanned" => true,
+ Permissions::HELLBANNED => true,
]);
@include_once "data/config/user-classes.conf.php";
diff --git a/core/util.php b/core/util.php
index 91e467ff..7017078d 100644
--- a/core/util.php
+++ b/core/util.php
@@ -16,7 +16,7 @@ function mtimefile(string $file): string
function get_theme(): string
{
global $config;
- $theme = $config->get_string("theme", "default");
+ $theme = $config->get_string(SetupConfig::THEME, "default");
if (!file_exists("themes/$theme")) {
$theme = "default";
}
@@ -588,8 +588,8 @@ function show_ip(string $ip, string $ban_reason): string
global $user;
$u_reason = url_escape($ban_reason);
$u_end = url_escape("+1 week");
- $ban = $user->can("ban_ip") ? ", Ban" : "";
- $ip = $user->can("view_ip") ? $ip.$ban : "";
+ $ban = $user->can(Permissions::BAN_IP) ? ", Ban" : "";
+ $ip = $user->can(Permissions::VIEW_IP) ? $ip.$ban : "";
return $ip;
}
diff --git a/ext/admin/main.php b/ext/admin/main.php
index 4ebae35e..423460c8 100644
--- a/ext/admin/main.php
+++ b/ext/admin/main.php
@@ -54,7 +54,7 @@ class AdminPage extends Extension
global $page, $user;
if ($event->page_matches("admin")) {
- if (!$user->can("manage_admintools")) {
+ if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) {
$this->theme->display_permission_denied();
} else {
if ($event->count_args() == 0) {
@@ -108,10 +108,20 @@ class AdminPage extends Extension
$this->theme->display_form();
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
+ $event->add_nav_link("admin", new Link('admin'), "Board Admin");
+ }
+ }
+ }
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("manage_admintools")) {
+ if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
$event->add_link("Board Admin", make_link("admin"));
}
}
diff --git a/ext/alias_editor/main.php b/ext/alias_editor/main.php
index 07f9f289..36edbfbb 100644
--- a/ext/alias_editor/main.php
+++ b/ext/alias_editor/main.php
@@ -36,7 +36,7 @@ class AliasEditor extends Extension
if ($event->page_matches("alias")) {
if ($event->get_arg(0) == "add") {
- if ($user->can("manage_alias_list")) {
+ if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
if (isset($_POST['oldtag']) && isset($_POST['newtag'])) {
try {
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
@@ -49,7 +49,7 @@ class AliasEditor extends Extension
}
}
} elseif ($event->get_arg(0) == "remove") {
- if ($user->can("manage_alias_list")) {
+ if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
if (isset($_POST['oldtag'])) {
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $_POST['oldtag']]);
log_info("alias_editor", "Deleted alias for ".$_POST['oldtag'], "Deleted alias");
@@ -85,7 +85,7 @@ class AliasEditor extends Extension
$page->set_filename("aliases.csv");
$page->set_data($this->get_alias_csv($database));
} elseif ($event->get_arg(0) == "import") {
- if ($user->can("manage_alias_list")) {
+ if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
if (count($_FILES) > 0) {
$tmp = $_FILES['alias_file']['tmp_name'];
$contents = file_get_contents($tmp);
@@ -117,10 +117,17 @@ class AliasEditor extends Extension
}
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ if($event->parent=="tags") {
+ $event->add_nav_link("aliases", new Link('alias/list'), "Aliases", NavLink::is_active(["alias"]));
+ }
+ }
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("manage_alias_list")) {
+ if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
$event->add_link("Alias Editor", make_link("alias/list"));
}
}
diff --git a/ext/alias_editor/theme.php b/ext/alias_editor/theme.php
index ec12348e..732139d4 100644
--- a/ext/alias_editor/theme.php
+++ b/ext/alias_editor/theme.php
@@ -11,7 +11,7 @@ class AliasEditorTheme extends Themelet
{
global $page, $user;
- $can_manage = $user->can("manage_alias_list");
+ $can_manage = $user->can(Permissions::MANAGE_ALIAS_LIST);
if ($can_manage) {
$h_action = "
Action
";
$h_add = "
diff --git a/ext/artists/main.php b/ext/artists/main.php
index 568933de..b1f6efcc 100644
--- a/ext/artists/main.php
+++ b/ext/artists/main.php
@@ -47,12 +47,23 @@ class Artists extends Extension
public function onSearchTermParse(SearchTermParseEvent $event)
{
$matches = [];
- if (preg_match("/^author[=|:](.*)$/i", $event->term, $matches)) {
+ if (preg_match("/^(author|artist)[=|:](.*)$/i", $event->term, $matches)) {
$char = $matches[1];
$event->add_querylet(new Querylet("Author = :author_char", ["author_char"=>$char]));
}
}
+ public function onHelpPageBuilding(HelpPageBuildingEvent $event)
+ {
+ if($event->key===HelpPages::SEARCH) {
+ $block = new Block();
+ $block->header = "Artist";
+ $block->body = $this->theme->get_help_html();
+ $event->add_block($block);
+ }
+ }
+
+
public function onInitExt(InitExtEvent $event)
{
global $config, $database;
diff --git a/ext/artists/theme.php b/ext/artists/theme.php
index aebd2757..1e5e5afb 100644
--- a/ext/artists/theme.php
+++ b/ext/artists/theme.php
@@ -545,4 +545,14 @@ class ArtistsTheme extends Themelet
}
return $html;
}
+
+ public function get_help_html()
+ {
+ return '
Search for images with a particular artist.
+
+
artist=leonardo
+
Returns images with the artist "leonardo".
+
+ ';
+ }
}
diff --git a/ext/ban_words/main.php b/ext/ban_words/main.php
index c668e514..5e0761b3 100644
--- a/ext/ban_words/main.php
+++ b/ext/ban_words/main.php
@@ -58,7 +58,7 @@ xanax
public function onCommentPosting(CommentPostingEvent $event)
{
global $user;
- if (!$user->can("bypass_comment_checks")) {
+ if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) {
$this->test_text($event->comment, new CommentPostingException("Comment contains banned terms"));
}
}
diff --git a/ext/blocks/main.php b/ext/blocks/main.php
index cb9c375c..197b5d9f 100644
--- a/ext/blocks/main.php
+++ b/ext/blocks/main.php
@@ -26,10 +26,20 @@ class Blocks extends Extension
}
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->can(Permissions::MANAGE_BLOCKS)) {
+ $event->add_nav_link("blocks", new Link('blocks/list'), "Blocks Editor");
+ }
+ }
+ }
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("manage_blocks")) {
+ if ($user->can(Permissions::MANAGE_BLOCKS)) {
$event->add_link("Blocks Editor", make_link("blocks/list"));
}
}
@@ -52,7 +62,7 @@ class Blocks extends Extension
}
}
- if ($event->page_matches("blocks") && $user->can("manage_blocks")) {
+ if ($event->page_matches("blocks") && $user->can(Permissions::MANAGE_BLOCKS)) {
if ($event->get_arg(0) == "add") {
if ($user->check_auth_token()) {
$database->execute("
diff --git a/ext/blotter/main.php b/ext/blotter/main.php
index cb88490b..3e18b1a9 100644
--- a/ext/blotter/main.php
+++ b/ext/blotter/main.php
@@ -56,6 +56,17 @@ class Blotter extends Extension
$event->panel->add_block($sb);
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->is_admin()) {
+ $event->add_nav_link("blotter", new Link('blotter/editor'), "Blotter Editor");
+ }
+ }
+ }
+
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
diff --git a/ext/browser_search/main.php b/ext/browser_search/main.php
index 7c1fcc82..301ca0db 100644
--- a/ext/browser_search/main.php
+++ b/ext/browser_search/main.php
@@ -27,14 +27,14 @@ class BrowserSearch extends Extension
// Add in header code to let the browser know that the search plugin exists
// We need to build the data for the header
- $search_title = $config->get_string('title');
+ $search_title = $config->get_string(SetupConfig::TITLE);
$search_file_url = make_link('browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml');
$page->add_html_header("");
// The search.xml file that is generated on the fly
if ($event->page_matches("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml")) {
// First, we need to build all the variables we'll need
- $search_title = $config->get_string('title');
+ $search_title = $config->get_string(SetupConfig::TITLE);
$search_form_url = make_link('post/list/{searchTerms}');
$suggenton_url = make_link('browser_search/')."{searchTerms}";
$icon_b64 = base64_encode(file_get_contents("ext/handle_static/static/favicon.ico"));
diff --git a/ext/bulk_actions/main.php b/ext/bulk_actions/main.php
index d8dde6d9..0bfaf1da 100644
--- a/ext/bulk_actions/main.php
+++ b/ext/bulk_actions/main.php
@@ -85,11 +85,11 @@ class BulkActions extends Extension
{
global $user;
- if ($user->can("delete_image")) {
+ if ($user->can(Permissions::DELETE_IMAGE)) {
$event->add_action("bulk_delete", "(D)elete", "d", "Delete selected images?", $this->theme->render_ban_reason_input(), 10);
}
- if ($user->can("bulk_edit_image_tag")) {
+ if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
$event->add_action(
"bulk_tag",
@@ -100,7 +100,7 @@ class BulkActions extends Extension
10);
}
- if ($user->can("bulk_edit_image_source")) {
+ if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
$event->add_action("bulk_source", "Set (S)ource", "s","", $this->theme->render_source_input(), 10);
}
}
@@ -111,7 +111,7 @@ class BulkActions extends Extension
switch ($event->action) {
case "bulk_delete":
- if ($user->can("delete_image")) {
+ if ($user->can(Permissions::DELETE_IMAGE)) {
$i = $this->delete_items($event->items);
flash_message("Deleted $i items");
}
@@ -120,7 +120,7 @@ class BulkActions extends Extension
if (!isset($_POST['bulk_tags'])) {
return;
}
- if ($user->can("bulk_edit_image_tag")) {
+ if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
$tags = $_POST['bulk_tags'];
$replace = false;
if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") {
@@ -135,7 +135,7 @@ class BulkActions extends Extension
if (!isset($_POST['bulk_source'])) {
return;
}
- if ($user->can("bulk_edit_image_source")) {
+ if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
$source = $_POST['bulk_source'];
$i = $this->set_source($event->items, $source);
flash_message("Set source for $i items");
@@ -147,7 +147,7 @@ class BulkActions extends Extension
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
- if ($event->page_matches("bulk_action") && $user->can("perform_bulk_actions")) {
+ if ($event->page_matches("bulk_action") && $user->can(Permissions::PERFORM_BULK_ACTIONS)) {
if (!isset($_POST['bulk_action'])) {
return;
}
diff --git a/ext/comment/main.php b/ext/comment/main.php
index 1dfdc03b..f58a3076 100644
--- a/ext/comment/main.php
+++ b/ext/comment/main.php
@@ -157,6 +157,21 @@ class CommentList extends Extension
}
}
+
+ public function onPageNavBuilding(PageNavBuildingEvent $event)
+ {
+ $event->add_nav_link("comment", new Link('comment/list'), "Comments");
+ }
+
+
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ if($event->parent=="comment") {
+ $event->add_nav_link("comment_list", new Link('comment/list'), "All");
+ $event->add_nav_link("comment_help", new Link('ext_doc/comment'), "Help");
+ }
+ }
+
public function onPageRequest(PageRequestEvent $event)
{
if ($event->page_matches("comment")) {
@@ -189,7 +204,7 @@ class CommentList extends Extension
private function onPageRequest_delete(PageRequestEvent $event)
{
global $user, $page;
- if ($user->can("delete_comment")) {
+ if ($user->can(Permissions::DELETE_COMMENT)) {
// FIXME: post, not args
if ($event->count_args() === 3) {
send_event(new CommentDeletionEvent($event->get_arg(1)));
@@ -209,7 +224,7 @@ class CommentList extends Extension
private function onPageRequest_bulk_delete()
{
global $user, $database, $page;
- if ($user->can("delete_comment") && !empty($_POST["ip"])) {
+ if ($user->can(Permissions::DELETE_COMMENT) && !empty($_POST["ip"])) {
$ip = $_POST['ip'];
$comment_ids = $database->get_col("
@@ -288,7 +303,7 @@ class CommentList extends Extension
$this->theme->display_image_comments(
$event->image,
$this->get_comments($event->image->id),
- $user->can("create_comment")
+ $user->can(Permissions::CREATE_COMMENT)
);
}
@@ -351,6 +366,16 @@ class CommentList extends Extension
}
}
+ public function onHelpPageBuilding(HelpPageBuildingEvent $event)
+ {
+ if($event->key===HelpPages::SEARCH) {
+ $block = new Block();
+ $block->header = "Comments";
+ $block->body = $this->theme->get_help_html();
+ $event->add_block($block);
+ }
+ }
+
// page building {{{
private function build_page(int $current_page)
{
@@ -399,7 +424,7 @@ class CommentList extends Extension
}
}
- $this->theme->display_comment_list($images, $current_page, $total_pages, $user->can("create_comment"));
+ $this->theme->display_comment_list($images, $current_page, $total_pages, $user->can(Permissions::CREATE_COMMENT));
}
// }}}
@@ -574,7 +599,7 @@ class CommentList extends Extension
{
global $database, $page;
- if (!$user->can("bypass_comment_checks")) {
+ if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) {
// will raise an exception if anything is wrong
$this->comment_checks($image_id, $user, $comment);
}
@@ -600,7 +625,7 @@ class CommentList extends Extension
global $config, $page;
// basic sanity checks
- if (!$user->can("create_comment")) {
+ if (!$user->can(Permissions::CREATE_COMMENT)) {
throw new CommentPostingException("Anonymous posting has been disabled");
} elseif (is_null(Image::by_id($image_id))) {
throw new CommentPostingException("The image does not exist");
diff --git a/ext/comment/theme.php b/ext/comment/theme.php
index a17131af..7e62da6b 100644
--- a/ext/comment/theme.php
+++ b/ext/comment/theme.php
@@ -218,9 +218,9 @@ class CommentListTheme extends Themelet
if (!array_key_exists($comment->poster_ip, $this->anon_map)) {
$this->anon_map[$comment->poster_ip] = $this->anon_id;
}
- #if($user->can("view_ip")) {
+ #if($user->can(UserAbilities::VIEW_IP)) {
#$style = " style='color: ".$this->get_anon_colour($comment->poster_ip).";'";
- if ($user->can("view_ip") || $config->get_bool("comment_samefags_public", false)) {
+ if ($user->can(Permissions::VIEW_IP) || $config->get_bool("comment_samefags_public", false)) {
if ($this->anon_map[$comment->poster_ip] != $this->anon_id) {
$anoncode2 = '('.$this->anon_map[$comment->poster_ip].')';
}
@@ -248,9 +248,9 @@ class CommentListTheme extends Themelet
$h_avatar = " ";
}
$h_reply = " - Reply";
- $h_ip = $user->can("view_ip") ? " ".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : "";
+ $h_ip = $user->can(Permissions::VIEW_IP) ? " ".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : "";
$h_del = "";
- if ($user->can("delete_comment")) {
+ if ($user->can(Permissions::DELETE_COMMENT)) {
$comment_preview = substr(html_unescape($tfe->stripped), 0, 50);
$j_delete_confirm_message = json_encode("Delete comment by {$comment->owner_name}:\n$comment_preview");
$h_delete_script = html_escape("return confirm($j_delete_confirm_message);");
@@ -290,4 +290,28 @@ class CommentListTheme extends Themelet
';
}
+
+ public function get_help_html()
+ {
+ return '
Search for images containing a certain number of comments, or comments by a particular individual.
+
+
comments=1
+
Returns images with exactly 1 comment.
+
+
+
comments>0
+
Returns images with 1 or more comments.
+
+
Can use <, <=, >, >=, or =.
+
+
commented_by:username
+
Returns images that have been commented on by "username".
+
+
+
commented_by_userno:123
+
Returns images that have been commented on by user 123.
+
+ ';
+
+ }
}
diff --git a/ext/custom_html_headers/main.php b/ext/custom_html_headers/main.php
index 3125f3b1..58715e9b 100644
--- a/ext/custom_html_headers/main.php
+++ b/ext/custom_html_headers/main.php
@@ -65,7 +65,7 @@ class custom_html_headers extends Extension
global $config, $page;
// get config values
- $site_title = $config->get_string("title");
+ $site_title = $config->get_string(SetupConfig::TITLE);
$sitename_in_title = $config->get_int("sitename_in_title");
// if feature is enabled & sitename isn't already in title
diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php
index ce13295b..cb55766f 100644
--- a/ext/danbooru_api/main.php
+++ b/ext/danbooru_api/main.php
@@ -297,7 +297,7 @@ class DanbooruApi extends Extension
// Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload
- if (!$user->can("create_image")) {
+ if (!$user->can(Permissions::CREATE_IMAGE)) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: authentication error");
return;
diff --git a/ext/downtime/main.php b/ext/downtime/main.php
index 891d87c3..97f8682e 100644
--- a/ext/downtime/main.php
+++ b/ext/downtime/main.php
@@ -32,7 +32,7 @@ class Downtime extends Extension
global $config, $page, $user;
if ($config->get_bool("downtime")) {
- if (!$user->can("ignore_downtime") && !$this->is_safe_page($event)) {
+ if (!$user->can(Permissions::IGNORE_DOWNTIME) && !$this->is_safe_page($event)) {
$msg = $config->get_string("downtime_message");
$this->theme->display_message($msg);
if (!defined("UNITTEST")) { // hax D:
diff --git a/ext/downtime/theme.php b/ext/downtime/theme.php
index caedcfda..99c4cffc 100644
--- a/ext/downtime/theme.php
+++ b/ext/downtime/theme.php
@@ -21,7 +21,7 @@ class DowntimeTheme extends Themelet
public function display_message(string $message)
{
global $config, $user, $page;
- $theme_name = $config->get_string('theme');
+ $theme_name = $config->get_string(SetupConfig::THEME);
$data_href = get_base_href();
$login_link = make_link("user_admin/login");
$auth = $user->get_auth_html();
diff --git a/ext/et/main.php b/ext/et/main.php
index e3e9b9c7..576765dd 100644
--- a/ext/et/main.php
+++ b/ext/et/main.php
@@ -18,16 +18,28 @@ class ET extends Extension
{
global $user;
if ($event->page_matches("system_info")) {
- if ($user->can("view_sysinfo")) {
+ if ($user->can(Permissions::VIEW_SYSINTO)) {
$this->theme->display_info_page($this->get_info());
}
}
}
+
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->can(Permissions::VIEW_SYSINTO)) {
+ $event->add_nav_link("system_info", new Link('system_info'), "System Info", null, 10);
+ }
+ }
+ }
+
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("view_sysinfo")) {
+ if ($user->can(Permissions::VIEW_SYSINTO)) {
$event->add_link("System Info", make_link("system_info"));
}
}
@@ -40,8 +52,8 @@ class ET extends Extension
global $config, $database;
$info = [];
- $info['site_title'] = $config->get_string("title");
- $info['site_theme'] = $config->get_string("theme");
+ $info['site_title'] = $config->get_string(SetupConfig::TITLE);
+ $info['site_theme'] = $config->get_string(SetupConfig::THEME);
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
$info['sys_shimmie'] = VERSION;
diff --git a/ext/ext_manager/main.php b/ext/ext_manager/main.php
index 03ee3bb1..39c7f572 100644
--- a/ext/ext_manager/main.php
+++ b/ext/ext_manager/main.php
@@ -118,7 +118,7 @@ class ExtManager extends Extension
{
global $page, $user;
if ($event->page_matches("ext_manager")) {
- if ($user->can("manage_extension_list")) {
+ if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
if (is_writable("data/config")) {
$this->set_things($_POST);
@@ -162,11 +162,22 @@ class ExtManager extends Extension
}
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
+ $event->add_nav_link("ext_manager", new Link('ext_manager'), "Extension Manager");
+ } else {
+ $event->add_nav_link("ext_doc", new Link('ext_doc'), "Board Help");
+ }
+ }
+ }
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("manage_extension_list")) {
+ if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
$event->add_link("Extension Manager", make_link("ext_manager"));
} else {
$event->add_link("Help", make_link("ext_doc"));
diff --git a/ext/favorites/main.php b/ext/favorites/main.php
index e8b7f6fe..950d6178 100644
--- a/ext/favorites/main.php
+++ b/ext/favorites/main.php
@@ -155,6 +155,30 @@ class Favorites extends Extension
}
}
+ public function onHelpPageBuilding(HelpPageBuildingEvent $event)
+ {
+ if($event->key===HelpPages::SEARCH) {
+ $block = new Block();
+ $block->header = "Favorites";
+ $block->body = $this->theme->get_help_html();
+ $event->add_block($block);
+ }
+ }
+
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent=="posts") {
+ $event->add_nav_link("posts_favorites", new Link("post/list/favorited_by={$user->name}/1"), "My Favorites");
+ }
+
+ if($event->parent==="user") {
+ if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
+ $username = url_escape($user->name);
+ $event->add_nav_link("favorites", new Link("post/list/favorited_by=$username/1"), "My Favorites");
+ }
+ }
+ }
private function install()
{
diff --git a/ext/favorites/theme.php b/ext/favorites/theme.php
index 89509ce2..22367347 100644
--- a/ext/favorites/theme.php
+++ b/ext/favorites/theme.php
@@ -34,4 +34,28 @@ class FavoritesTheme extends Themelet
$page->add_block(new Block("Favorited By", $html, "left", 25));
}
+
+ public function get_help_html()
+ {
+ return '
Search for images that have been favorited a certain number of times, or favorited by a particular individual.
+
+
favorites=1
+
Returns images that have been favorited once.
+
+
+
favorites>0
+
Returns images that have been favorited 1 or more times
+
+
Can use <, <=, >, >=, or =.
+
+
favorited_by:username
+
Returns images that have been favorited by "username".
+
+
+
favorited_by_userno:123
+
Returns images that have been favorited by user 123.
+
+ ';
+
+ }
}
diff --git a/ext/featured/main.php b/ext/featured/main.php
index 4b713424..ae5946dd 100644
--- a/ext/featured/main.php
+++ b/ext/featured/main.php
@@ -32,7 +32,7 @@ class Featured extends Extension
global $config, $page, $user;
if ($event->page_matches("featured_image")) {
if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
- if ($user->can("edit_feature") && isset($_POST['image_id'])) {
+ if ($user->can(Permissions::EDIT_FEATURE) && isset($_POST['image_id'])) {
$id = int_escape($_POST['image_id']);
if ($id > 0) {
$config->set_int("featured_id", $id);
@@ -86,7 +86,7 @@ class Featured extends Extension
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
{
global $user;
- if ($user->can("edit_feature")) {
+ if ($user->can(Permissions::EDIT_FEATURE)) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
}
}
diff --git a/ext/handle_static/main.php b/ext/handle_static/main.php
index fb20dd59..69918964 100644
--- a/ext/handle_static/main.php
+++ b/ext/handle_static/main.php
@@ -17,7 +17,7 @@ class HandleStatic extends Extension
if ($page->mode == PageMode::PAGE && (!isset($page->blocks) || $this->count_main($page->blocks) == 0)) {
$h_pagename = html_escape(implode('/', $event->args));
$f_pagename = preg_replace("/[^a-z_\-\.]+/", "_", $h_pagename);
- $theme_name = $config->get_string("theme", "default");
+ $theme_name = $config->get_string(SetupConfig::THEME, "default");
$theme_file = "themes/$theme_name/static/$f_pagename";
$static_file = "ext/handle_static/static/$f_pagename";
diff --git a/ext/hellban/main.php b/ext/hellban/main.php
index 42bfec27..541f7620 100644
--- a/ext/hellban/main.php
+++ b/ext/hellban/main.php
@@ -9,9 +9,9 @@ class HellBan extends Extension
{
global $page, $user;
- if ($user->can("hellbanned")) {
+ if ($user->can(Permissions::HELLBANNED)) {
$s = "";
- } elseif ($user->can("view_hellbanned")) {
+ } elseif ($user->can(Permissions::VIEW_HELLBANNED)) {
$s = "DIV.hb, TR.hb TD {border: 1px solid red !important;}";
} else {
$s = ".hb {display: none !important;}";
diff --git a/ext/help_pages/baseline_help_outline_black_18dp.png b/ext/help_pages/baseline_help_outline_black_18dp.png
new file mode 100644
index 00000000..b6126844
Binary files /dev/null and b/ext/help_pages/baseline_help_outline_black_18dp.png differ
diff --git a/ext/help_pages/main.php b/ext/help_pages/main.php
new file mode 100644
index 00000000..d2821b00
--- /dev/null
+++ b/ext/help_pages/main.php
@@ -0,0 +1,94 @@
+
+ * License: MIT
+ * Description: Provides documentation screens
+ */
+
+class HelpPageListBuildingEvent extends Event
+{
+ public $pages = [];
+
+ public function add_page(string $key, string $name)
+ {
+ $this->pages[$key] = $name;
+ }
+
+}
+
+class HelpPageBuildingEvent extends Event
+{
+ public $key;
+ public $blocks = [];
+
+ public function __construct(string $key)
+ {
+ $this->key = $key;
+ }
+
+ function add_block(Block $block, int $position = 50)
+ {
+ if(!array_key_exists("$position",$this->blocks))
+ {
+ $this->blocks["$position"] = [];
+ }
+ $this->blocks["$position"][] = $block;
+ }
+}
+
+class HelpPages extends Extension
+{
+ public const SEARCH = "search";
+
+ public function onPageRequest(PageRequestEvent $event)
+ {
+ global $page, $user;
+
+ if ($event->page_matches("help")) {
+ $e = new HelpPageListBuildingEvent();
+ send_event($e);
+ $page->set_mode(PageMode::PAGE);
+
+ if ($event->count_args() == 0) {
+ $this->theme->display_list_page($e->pages);
+ } else {
+ $name = $event->get_arg(0);
+ $title = $name;
+ if(array_key_exists($name, $e->pages)) {
+ $title = $e->pages[$name];
+ }
+
+ $this->theme->display_help_page($title);
+
+ $hpbe = new HelpPageBuildingEvent($name);
+ send_event($hpbe);
+ asort($hpbe->blocks);
+
+ foreach ($hpbe->blocks as $key=>$value) {
+ foreach($value as $block) {
+ $page->add_block($block);
+ }
+ }
+ }
+ }
+ }
+
+ public function onHelpPageListBuilding(HelpPageListBuildingEvent $event)
+ {
+ $event->add_page("search", "Searching");
+ }
+
+ public function onPageNavBuilding(PageNavBuildingEvent $event)
+ {
+ $event->add_nav_link("help", new Link('help'), "Help");
+ }
+
+ public function onUserBlockBuilding(UserBlockBuildingEvent $event)
+ {
+ global $user;
+ $event->add_link("Help", make_link("help"));
+ }
+
+
+}
diff --git a/ext/help_pages/style.css b/ext/help_pages/style.css
new file mode 100644
index 00000000..85a40f5b
--- /dev/null
+++ b/ext/help_pages/style.css
@@ -0,0 +1,13 @@
+.command_example {
+ margin: 12pt;
+ padding-left: 16pt;
+}
+
+.command_example pre {
+ padding:4pt;
+ border: dashed 2px black;
+}
+
+.command_example p {
+ padding-left: 16pt;
+}
\ No newline at end of file
diff --git a/ext/help_pages/theme.php b/ext/help_pages/theme.php
new file mode 100644
index 00000000..2b3b7818
--- /dev/null
+++ b/ext/help_pages/theme.php
@@ -0,0 +1,31 @@
+set_title("Help Pages");
+ $page->set_heading("Help Pages");
+
+ $nav_block = new Block("Help", "", "left", 0);
+ foreach ($pages as $link=>$desc) {
+ $link = make_link("help/{$link}");
+ $nav_block->body .= "".html_escape($desc)." ";
+ }
+
+ $page->add_block($nav_block);
+ $page->add_block(new Block("Help Pages", "See list of pages to left"));
+ }
+
+ public function display_help_page(String $title)
+ {
+ global $page;
+
+ $page->set_title("Help - $title");
+ $page->set_heading("Help - $title");
+ }
+
+}
diff --git a/ext/home/main.php b/ext/home/main.php
index 34b9422f..156035c6 100644
--- a/ext/home/main.php
+++ b/ext/home/main.php
@@ -22,8 +22,8 @@ class Home extends Extension
global $config, $page;
if ($event->page_matches("home")) {
$base_href = get_base_href();
- $sitename = $config->get_string('title');
- $theme_name = $config->get_string('theme');
+ $sitename = $config->get_string(SetupConfig::TITLE);
+ $theme_name = $config->get_string(SetupConfig::THEME);
$body = $this->get_body();
@@ -52,7 +52,7 @@ class Home extends Extension
// returns just the contents of the body
global $config;
$base_href = get_base_href();
- $sitename = $config->get_string('title');
+ $sitename = $config->get_string(SetupConfig::TITLE);
$contact_link = contact_link();
if (is_null($contact_link)) {
$contact_link = "";
diff --git a/ext/image/main.php b/ext/image/main.php
index 099bdd65..9969c3e8 100644
--- a/ext/image/main.php
+++ b/ext/image/main.php
@@ -73,7 +73,7 @@ class ImageIO extends Extension
{
if ($event->page_matches("image/delete")) {
global $page, $user;
- if ($user->can("delete_image") && isset($_POST['image_id']) && $user->check_auth_token()) {
+ if ($user->can(Permissions::DELETE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
$image = Image::by_id($_POST['image_id']);
if ($image) {
send_event(new ImageDeletionEvent($image));
@@ -87,7 +87,7 @@ class ImageIO extends Extension
}
} elseif ($event->page_matches("image/replace")) {
global $page, $user;
- if ($user->can("replace_image") && isset($_POST['image_id']) && $user->check_auth_token()) {
+ if ($user->can(Permissions::REPLACE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
$image = Image::by_id($_POST['image_id']);
if ($image) {
$page->set_mode(PageMode::REDIRECT);
@@ -110,11 +110,11 @@ class ImageIO extends Extension
{
global $user;
- if ($user->can("delete_image")) {
+ if ($user->can(Permissions::DELETE_IMAGE)) {
$event->add_part($this->theme->get_deleter_html($event->image->id));
}
/* In the future, could perhaps allow users to replace images that they own as well... */
- if ($user->can("replace_image")) {
+ if ($user->can(Permissions::REPLACE_IMAGE)) {
$event->add_part($this->theme->get_replace_html($event->image->id));
}
}
diff --git a/ext/image_hash_ban/main.php b/ext/image_hash_ban/main.php
index c2e3ec3a..cc3a7ca1 100644
--- a/ext/image_hash_ban/main.php
+++ b/ext/image_hash_ban/main.php
@@ -64,7 +64,7 @@ class ImageBan extends Extension
global $database, $page, $user;
if ($event->page_matches("image_hash_ban")) {
- if ($user->can("ban_image")) {
+ if ($user->can(Permissions::BAN_IMAGE)) {
if ($event->get_arg(0) == "add") {
$image = isset($_POST['image_id']) ? Image::by_id(int_escape($_POST['image_id'])) : null;
$hash = isset($_POST["hash"]) ? $_POST["hash"] : $image->hash;
@@ -103,10 +103,21 @@ class ImageBan extends Extension
}
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->can(Permissions::BAN_IMAGE)) {
+ $event->add_nav_link("image_bans", new Link('image_hash_ban/list/1'), "Image Bans", NavLink::is_active(["image_hash_ban"]));
+ }
+ }
+ }
+
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("ban_image")) {
+ if ($user->can(Permissions::BAN_IMAGE)) {
$event->add_link("Image Bans", make_link("image_hash_ban/list/1"));
}
}
@@ -130,7 +141,7 @@ class ImageBan extends Extension
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
{
global $user;
- if ($user->can("ban_image")) {
+ if ($user->can(Permissions::BAN_IMAGE)) {
$event->add_part($this->theme->get_buttons_html($event->image));
}
}
diff --git a/ext/index/main.php b/ext/index/main.php
index eab9d7b3..9bf22091 100644
--- a/ext/index/main.php
+++ b/ext/index/main.php
@@ -332,6 +332,29 @@ class Index extends Extension
}
}
+ public function onPageNavBuilding(PageNavBuildingEvent $event)
+ {
+ $event->add_nav_link("posts", new Link('post/list'), "Posts", NavLink::is_active(["post","view"]),20);
+ }
+
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ if($event->parent=="posts") {
+ $event->add_nav_link("posts_all", new Link('post/list'), "All");
+ }
+ }
+
+ public function onHelpPageBuilding(HelpPageBuildingEvent $event)
+ {
+ if($event->key===HelpPages::SEARCH) {
+ $block = new Block();
+ $block->header = "General";
+ $block->body = $this->theme->get_help_html();
+ $event->add_block($block, 0);
+ }
+ }
+
+
public function onSearchTermParse(SearchTermParseEvent $event)
{
$matches = [];
@@ -380,6 +403,7 @@ class Index extends Extension
$event->add_querylet(new Querylet('images.source LIKE :src', ["src"=>"%$source%"]));
}
} elseif (preg_match("/^posted([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])([0-9-]*)$/i", $event->term, $matches)) {
+ // TODO Make this able to search = without needing a time component.
$cmp = ltrim($matches[1], ":") ?: "=";
$val = $matches[2];
$event->add_querylet(new Querylet("images.posted $cmp :posted{$this->stpen}", ["posted{$this->stpen}"=>$val]));
diff --git a/ext/index/theme.php b/ext/index/theme.php
index 5b58d627..e216ea06 100644
--- a/ext/index/theme.php
+++ b/ext/index/theme.php
@@ -110,7 +110,7 @@ and of course start organising your images :-)
global $config;
if (count($this->search_terms) == 0) {
- $page_title = $config->get_string('title');
+ $page_title = $config->get_string(SetupConfig::TITLE);
} else {
$search_string = implode(' ', $this->search_terms);
$page_title = html_escape($search_string);
@@ -144,4 +144,201 @@ and of course start organising your images :-)
$this->display_paginator($page, "post/list", null, $this->page_number, $this->total_pages, true);
}
}
+
+ public function get_help_html()
+ {
+ return '
Searching is largely based on tags, with a number of special keywords available that allow searching based on properties of the images.
+
+
+
tagname
+
Returns images that are tagged with "tagname".
+
+
+
+
tagname othertagname
+
Returns images that are tagged with "tagname" and "othertagname".
+
+
+
Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for images that do not match something.
+
+
+
-tagname
+
Returns images that are not tagged with "tagname".
+
+
+
+
-tagname -othertagname
+
Returns images that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as images with "tagname" or "othertagname" can still be returned as long as the other one is not present.
+
+
+
+
tagname -othertagname
+
Returns images that are tagged with "tagname", but are not tagged with "othertagname".
+
+
+
Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one".
+
+
+
tagn*
+
Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".
+
+
+
+
tagn?me
+
Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".
+
+
+
+
tags=1
+
Returns images with exactly 1 tag.
+
+
+
+
tags>0
+
Returns images with 1 or more tags.
+
+
+
Can use <, <=, >, >=, or =.
+
+
+
+
Search for images by aspect ratio
+
+
+
ratio=4:3
+
Returns images with an aspect ratio of 4:3.
+
+
+
+
ratio>16:9
+
Returns images with an aspect ratio greater than 16:9.
+
+
+
Can use <, <=, >, >=, or =. The relation is calculated by dividing width by height.
+
+
+
+
Search for images by file size
+
+
+
filesize=1
+
Returns images exactly 1 byte in size.
+
+
+
+
filesize>100mb
+
Returns images greater than 100 megabytes in size.
+
+
+
Can use <, <=, >, >=, or =. Supported suffixes are kb, mb, and gb. Uses multiples of 1024.
+
+
+
+
Search for images by MD5 hash
+
+
+
hash=0D3512CAA964B2BA5D7851AF5951F33B
+
Returns image with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B.
+
+
+
+
+
Search for images by file type
+
+
+
filetype=jpg
+
Returns images that are of type "jpg".
+
+
+
+
+
Search for images by file name
+
+
+
filename=picasso.jpg
+
Returns images that are named "picasso.jpg".
+
+
+
+
+
Search for images by source
+
+
+
source=http://google.com/
+
Returns images with a source of "http://google.com/".
+
+
+
+
source=any
+
Returns images with a source set.
+
+
+
+
source=none
+
Returns images without a source set.
+
+
+
+
+
Search for images by date posted.
+
+
+
posted>=07-19-2019
+
Returns images posted on or after 07-19-2019.
+
+
+
Can use <, <=, >, >=, or =. Date format is mm-dd-yyyy. Date posted includes time component, so = will not work unless the time is exact.
+
+
+
+
Search for images by image dimensions
+
+
+
size=640x480
+
Returns images exactly 640 pixels wide by 480 pixels high.
+
+
+
+
size>1920x1080
+
Returns images with a width larger than 1920 and a height larger than 1080.
+
+
+
+
width=1000
+
Returns images exactly 1000 pixels wide.
+
+
+
+
height=1000
+
Returns images exactly 1000 pixels high.
+
+
+
Can use <, <=, >, >=, or =.
+
+
+
+
Sorting search results can be done using the pattern order:field_direction. _direction can be either _asc or _desc, indicating ascending (123) or descending (321) order.
+
+
+
order:id_asc
+
Returns images sorted by ID, smallest first.
+
+
+
+
order:width_desc
+
Returns images sorted by width, largest first.
+
+
+
These fields are supported:
+
+
id
+
width
+
height
+
filesize
+
filename
+
+
+ ';
+
+ }
}
diff --git a/ext/ipban/main.php b/ext/ipban/main.php
index d6feb092..90eb198a 100644
--- a/ext/ipban/main.php
+++ b/ext/ipban/main.php
@@ -66,7 +66,7 @@ class IPBan extends Extension
{
if ($event->page_matches("ip_ban")) {
global $page, $user;
- if ($user->can("ban_ip")) {
+ if ($user->can(Permissions::BAN_IP)) {
if ($event->get_arg(0) == "add" && $user->check_auth_token()) {
if (isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) {
if (empty($_POST['end'])) {
@@ -105,10 +105,20 @@ class IPBan extends Extension
$event->panel->add_block($sb);
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->can(Permissions::BAN_IP)) {
+ $event->add_nav_link("ip_bans", new Link('ip_ban/list'), "IP Bans", NavLink::is_active(["ip_ban"]));
+ }
+ }
+ }
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("ban_ip")) {
+ if ($user->can(Permissions::BAN_IP)) {
$event->add_link("IP Bans", make_link("ip_ban/list"));
}
}
diff --git a/ext/log_db/main.php b/ext/log_db/main.php
index 2f1d761a..4002b68c 100644
--- a/ext/log_db/main.php
+++ b/ext/log_db/main.php
@@ -48,7 +48,7 @@ class LogDatabase extends Extension
{
global $database, $user;
if ($event->page_matches("log/view")) {
- if ($user->can("view_eventlog")) {
+ if ($user->can(Permissions::VIEW_EVENTLOG)) {
$wheres = [];
$args = [];
$page_num = int_escape($event->get_arg(0));
@@ -120,10 +120,20 @@ class LogDatabase extends Extension
}
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="system") {
+ if ($user->can(Permissions::VIEW_EVENTLOG)) {
+ $event->add_nav_link("event_log", new Link('log/view'), "Event Log");
+ }
+ }
+ }
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("view_eventlog")) {
+ if ($user->can(Permissions::VIEW_EVENTLOG)) {
$event->add_link("Event Log", make_link("log/view"));
}
}
diff --git a/ext/media/main.php b/ext/media/main.php
index a4fba3ba..012507b3 100644
--- a/ext/media/main.php
+++ b/ext/media/main.php
@@ -316,7 +316,7 @@ class Media extends Extension
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
{
global $user;
- if ($user->can("delete_image")) {
+ if ($user->can(Permissions::DELETE_IMAGE)) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
}
}
@@ -419,6 +419,17 @@ class Media extends Extension
}
}
+ public function onHelpPageBuilding(HelpPageBuildingEvent $event)
+ {
+ if($event->key===HelpPages::SEARCH) {
+ $block = new Block();
+ $block->header = "Media";
+ $block->body = $this->theme->get_help_html();
+ $event->add_block($block);
+ }
+ }
+
+
public function onTagTermParse(TagTermParseEvent $event)
{
$matches = [];
diff --git a/ext/media/theme.php b/ext/media/theme.php
index 12d77f32..7871ee69 100644
--- a/ext/media/theme.php
+++ b/ext/media/theme.php
@@ -28,4 +28,20 @@ class MediaTheme extends Themelet
";
}
+
+ public function get_help_html()
+ {
+ return '
Search for items based on the type of media.
+
+
content:audio
+
Returns items that contain audio, including videos and audio files.
+
+
+
content:video
+
Returns items that contain video, including animated GIFs.
+
+
These search terms depend on the items being scanned for media content. Automatic scanning was implemented in mid-2019, so items uploaded before, or items uploaded on a system without ffmpeg, will require additional scanning before this will work.
+ ';
+
+ }
}
diff --git a/ext/not_a_tag/main.php b/ext/not_a_tag/main.php
index 18486e9c..369ab0ff 100644
--- a/ext/not_a_tag/main.php
+++ b/ext/not_a_tag/main.php
@@ -58,10 +58,20 @@ class NotATag extends Extension
}
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ global $user;
+ if($event->parent==="tags") {
+ if ($user->can(Permissions::BAN_IMAGE)) {
+ $event->add_nav_link("untags", new Link('untag/list/1'), "UnTags");
+ }
+ }
+ }
+
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
- if ($user->can("ban_image")) {
+ if ($user->can(Permissions::BAN_IMAGE)) {
$event->add_link("UnTags", make_link("untag/list/1"));
}
}
@@ -71,7 +81,7 @@ class NotATag extends Extension
global $database, $page, $user;
if ($event->page_matches("untag")) {
- if ($user->can("ban_image")) {
+ if ($user->can(Permissions::BAN_IMAGE)) {
if ($event->get_arg(0) == "add") {
$tag = $_POST["tag"];
$redirect = isset($_POST['redirect']) ? $_POST['redirect'] : "DNP";
diff --git a/ext/notes/main.php b/ext/notes/main.php
index aafa928f..18c45f82 100644
--- a/ext/notes/main.php
+++ b/ext/notes/main.php
@@ -210,12 +210,22 @@ class Notes extends Extension
}
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
- } elseif (preg_match("/^notes_by_userno[=|:](\d+)$/i", $event->term, $matches)) {
- $user_id = int_escape($matches[1]);
+ } elseif (preg_match("/^(notes_by_userno|notes_by_user_id)[=|:](\d+)$/i", $event->term, $matches)) {
+ $user_id = int_escape($matches[2]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
}
}
+ public function onHelpPageBuilding(HelpPageBuildingEvent $event)
+ {
+ if($event->key===HelpPages::SEARCH) {
+ $block = new Block();
+ $block->header = "Notes";
+ $block->body = $this->theme->get_help_html();
+ $event->add_block($block);
+ }
+ }
+
/**
* HERE WE GET ALL NOTES FOR DISPLAYED IMAGE.
diff --git a/ext/notes/theme.php b/ext/notes/theme.php
index 912ec615..6878c9c2 100644
--- a/ext/notes/theme.php
+++ b/ext/notes/theme.php
@@ -247,4 +247,29 @@ class NotesTheme extends Themelet
$this->display_paginator($page, "note/updated", null, $pageNumber, $totalPages);
}
+
+ public function get_help_html()
+ {
+ return '
Search for images with notes.
+
+
note=noted
+
Returns images with a note matching "noted".
+
+
+
notes>0
+
Returns images with 1 or more notes.
+
+
Can use <, <=, >, >=, or =.
+
+
notes_by=username
+
Returns images with note(s) by "username".
+
+
+
notes_by_user_id=123
+
Returns images with note(s) by user 123.
+
+ ';
+
+ }
+
}
diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php
index 5275dfa7..0b93c369 100644
--- a/ext/numeric_score/main.php
+++ b/ext/numeric_score/main.php
@@ -45,7 +45,7 @@ class NumericScore extends Extension
public function onUserPageBuilding(UserPageBuildingEvent $event)
{
global $user;
- if ($user->can("edit_other_vote")) {
+ if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
$this->theme->get_nuller($event->display_user);
}
@@ -98,7 +98,7 @@ class NumericScore extends Extension
$page->set_redirect(make_link("post/view/$image_id"));
}
} elseif ($event->page_matches("numeric_score/remove_votes_on") && $user->check_auth_token()) {
- if ($user->can("edit_other_vote")) {
+ if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
$image_id = int_escape($_POST['image_id']);
$database->execute(
"DELETE FROM numeric_score_votes WHERE image_id=?",
@@ -112,7 +112,7 @@ class NumericScore extends Extension
$page->set_redirect(make_link("post/view/$image_id"));
}
} elseif ($event->page_matches("numeric_score/remove_votes_by") && $user->check_auth_token()) {
- if ($user->can("edit_other_vote")) {
+ if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
$this->delete_votes_by(int_escape($_POST['user_id']));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link());
@@ -228,6 +228,16 @@ class NumericScore extends Extension
$event->replace('$score', $event->image->numeric_score);
}
+ public function onHelpPageBuilding(HelpPageBuildingEvent $event)
+ {
+ if($event->key===HelpPages::SEARCH) {
+ $block = new Block();
+ $block->header = "Numeric Score";
+ $block->body = $this->theme->get_help_html();
+ $event->add_block($block);
+ }
+ }
+
public function onSearchTermParse(SearchTermParseEvent $event)
{
$matches = [];
@@ -294,6 +304,16 @@ class NumericScore extends Extension
}
}
+ public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
+ {
+ if($event->parent=="posts") {
+ $event->add_nav_link("numeric_score_day", new Link('popular_by_day'), "Popular by Day");
+ $event->add_nav_link("numeric_score_month", new Link('popular_by_month'), "Popular by Month");
+ $event->add_nav_link("numeric_score_year", new Link('popular_by_year'), "Popular by Year");
+
+ }
+ }
+
private function install()
{
global $database;
diff --git a/ext/numeric_score/theme.php b/ext/numeric_score/theme.php
index c2dc31c7..e6dbe7fa 100644
--- a/ext/numeric_score/theme.php
+++ b/ext/numeric_score/theme.php
@@ -32,7 +32,7 @@ class NumericScoreTheme extends Themelet
";
- if ($user->can("edit_other_vote")) {
+ if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
$html .= "