just fix all extensions except home just so we can maintain the characters on the post counter

This commit is contained in:
2025-10-04 22:54:41 -05:00
parent be92690927
commit ddc4f2bd67
303 changed files with 4717 additions and 3833 deletions

View File

@@ -4,6 +4,10 @@ declare(strict_types=1);
namespace Shimmie2;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\{InputInterface,InputArgument};
use Symfony\Component\Console\Output\OutputInterface;
/**
* Sent when the admin page is ready to be added to
*/
@@ -41,7 +45,7 @@ class AdminPage extends Extension
/** @var AdminPageTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $database, $page, $user;
@@ -64,7 +68,7 @@ class AdminPage extends Extension
}
}
public function onCommand(CommandEvent $event)
public function onCliGen(CliGenEvent $event): void
{
$event->app->register('page:get')
->addArgument('query', InputArgument::REQUIRED)
@@ -151,17 +155,17 @@ class AdminPage extends Extension
$this->theme->display_page();
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
global $user;
if ($event->parent==="system") {
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)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class AdminPageTest extends ShimmiePHPUnitTestCase
{
public function testAuth()
public function testAuth(): void
{
$this->log_out();
$this->assertException(PermissionDenied::class, function () {

View File

@@ -9,7 +9,7 @@ class AdminPageTheme extends Themelet
/*
* Show the basics of a page, for other extensions to add to
*/
public function display_page()
public function display_page(): void
{
global $page;

View File

@@ -19,12 +19,12 @@ class AliasTable extends Table
$this->size = 100;
$this->limit = 1000000;
$this->set_columns([
new TextColumn("oldtag", "Old Tag"),
new TextColumn("newtag", "New Tag"),
new AutoCompleteColumn("oldtag", "Old Tag"),
new AutoCompleteColumn("newtag", "New Tag"),
new ActionColumn("oldtag"),
]);
$this->order_by = ["oldtag"];
$this->table_attrs = ["class" => "zebra"];
$this->table_attrs = ["class" => "zebra form"];
}
}
@@ -61,7 +61,7 @@ class AliasEditor extends Extension
/** @var AliasEditorTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $config, $database, $page, $user;
@@ -108,13 +108,13 @@ class AliasEditor extends Extension
}
}
public function onAddAlias(AddAliasEvent $event)
public function onAddAlias(AddAliasEvent $event): void
{
global $database;
$row = $database->get_row(
"SELECT * FROM aliases WHERE lower(oldtag)=lower(:oldtag)",
["oldtag"=>$event->oldtag]
["oldtag" => $event->oldtag]
);
if ($row) {
throw new AddAliasException("{$row['oldtag']} is already an alias for {$row['newtag']}");
@@ -135,21 +135,21 @@ class AliasEditor extends Extension
log_info("alias_editor", "Added alias for {$event->oldtag} -> {$event->newtag}", "Added alias");
}
public function onDeleteAlias(DeleteAliasEvent $event)
public function onDeleteAlias(DeleteAliasEvent $event): void
{
global $database;
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $event->oldtag]);
log_info("alias_editor", "Deleted alias for {$event->oldtag}", "Deleted alias");
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
if ($event->parent=="tags") {
if ($event->parent == "tags") {
$event->add_nav_link("aliases", new Link('alias/list'), "Aliases", NavLink::is_active(["alias"]));
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
@@ -162,6 +162,7 @@ class AliasEditor extends Extension
$csv = "";
$aliases = $database->get_pairs("SELECT oldtag, newtag FROM aliases ORDER BY newtag");
foreach ($aliases as $old => $new) {
assert(is_string($new));
$csv .= "\"$old\",\"$new\"\n";
}
return $csv;

View File

@@ -6,14 +6,14 @@ namespace Shimmie2;
class AliasEditorTest extends ShimmiePHPUnitTestCase
{
public function testAliasList()
public function testAliasList(): void
{
$this->get_page('alias/list');
$this->assert_response(200);
$this->assert_title("Alias List");
}
public function testAliasListReadOnly()
public function testAliasListReadOnly(): void
{
$this->log_in_as_user();
$this->get_page('alias/list');
@@ -26,7 +26,7 @@ class AliasEditorTest extends ShimmiePHPUnitTestCase
$this->assert_no_text("Add");
}
public function testAliasOneToOne()
public function testAliasOneToOne(): void
{
$this->log_in_as_admin();
@@ -54,7 +54,7 @@ class AliasEditorTest extends ShimmiePHPUnitTestCase
$this->assert_no_text("test1");
}
public function testAliasOneToMany()
public function testAliasOneToMany(): void
{
$this->log_in_as_admin();

View File

@@ -18,11 +18,11 @@ class AliasEditorTheme extends Themelet
{
global $page, $user;
$html = emptyHTML($table, BR(), $paginator, BR(), SHM_A("alias/export/aliases.csv", "Download as CSV", args: ["download"=>"aliases.csv"]));
$html = emptyHTML($table, BR(), $paginator, BR(), SHM_A("alias/export/aliases.csv", "Download as CSV", args: ["download" => "aliases.csv"]));
$bulk_form = SHM_FORM("alias/import", multipart: true);
$bulk_form->appendChild(
INPUT(["type"=>"file", "name"=>"alias_file"]),
INPUT(["type" => "file", "name" => "alias_file"]),
SHM_SUBMIT("Upload List")
);
$bulk_html = emptyHTML($bulk_form);

View File

@@ -10,7 +10,7 @@ class ApprovalInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Approval";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public array $authors = ["Matthew Barbour" => "matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public ExtensionCategory $category = ExtensionCategory::MODERATION;
public string $description = "Adds an approval step to the upload/import process.";

View File

@@ -16,17 +16,18 @@ class Approval extends Extension
/** @var ApprovalTheme */
protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_bool(ApprovalConfig::IMAGES, false);
$config->set_default_bool(ApprovalConfig::COMMENTS, false);
Image::$bool_props[] = "approved";
Image::$prop_types["approved"] = ImagePropType::BOOL;
Image::$prop_types["approved_by_id"] = ImagePropType::INT;
}
public function onImageAddition(ImageAdditionEvent $event)
public function onImageAddition(ImageAdditionEvent $event): void
{
global $user, $config;
@@ -35,7 +36,7 @@ class Approval extends Extension
}
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user;
@@ -54,18 +55,18 @@ class Approval extends Extension
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Approval");
$sb->add_bool_option(ApprovalConfig::IMAGES, "Posts: ");
}
public function onAdminBuilding(AdminBuildingEvent $event)
public function onAdminBuilding(AdminBuildingEvent $event): void
{
$this->theme->display_admin_form();
}
public function onAdminAction(AdminActionEvent $event)
public function onAdminAction(AdminActionEvent $event): void
{
global $database, $user;
@@ -78,14 +79,14 @@ class Approval extends Extension
$database->set_timeout(null); // These updates can take a little bit
$database->execute(
"UPDATE images SET approved = :true, approved_by_id = :approved_by_id WHERE approved = :false",
["approved_by_id"=>$user->id, "true"=>true, "false"=>false]
["approved_by_id" => $user->id, "true" => true, "false" => false]
);
break;
case "disapprove_all":
$database->set_timeout(null); // These updates can take a little bit
$database->execute(
"UPDATE images SET approved = :false, approved_by_id = NULL WHERE approved = :true",
["true"=>true, "false"=>false]
["true" => true, "false" => false]
);
break;
default:
@@ -94,36 +95,36 @@ class Approval extends Extension
}
}
public function onDisplayingImage(DisplayingImageEvent $event)
public function onDisplayingImage(DisplayingImageEvent $event): void
{
global $page;
if (!$this->check_permissions($event->image)) {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/list"));
$page->set_redirect(make_link());
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
global $user;
if ($event->parent=="posts") {
if ($event->parent == "posts") {
if ($user->can(Permissions::APPROVE_IMAGE)) {
$event->add_nav_link("posts_unapproved", new Link('/post/list/approved%3Ano/1'), "Pending Approval", null, 60);
}
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::APPROVE_IMAGE)) {
$event->add_link("Pending Approval", make_link("/post/list/approved%3Ano/1"), 60);
$event->add_link("Pending Approval", search_link(["approved:no"]), 60);
}
}
public const SEARCH_REGEXP = "/^approved:(yes|no)/";
public function onSearchTermParse(SearchTermParseEvent $event)
public function onSearchTermParse(SearchTermParseEvent $event): void
{
global $user, $config;
@@ -131,7 +132,7 @@ class Approval extends Extension
$matches = [];
if (is_null($event->term) && $this->no_approval_query($event->context)) {
$event->add_querylet(new Querylet("approved = :true", ["true"=>true]));
$event->add_querylet(new Querylet("approved = :true", ["true" => true]));
}
if (is_null($event->term)) {
@@ -139,25 +140,27 @@ class Approval extends Extension
}
if (preg_match(self::SEARCH_REGEXP, strtolower($event->term), $matches)) {
if ($user->can(Permissions::APPROVE_IMAGE) && $matches[1] == "no") {
$event->add_querylet(new Querylet("approved != :true", ["true"=>true]));
$event->add_querylet(new Querylet("approved != :true", ["true" => true]));
} else {
$event->add_querylet(new Querylet("approved = :true", ["true"=>true]));
$event->add_querylet(new Querylet("approved = :true", ["true" => true]));
}
}
}
}
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
public function onHelpPageBuilding(HelpPageBuildingEvent $event): void
{
global $user, $config;
if ($event->key===HelpPages::SEARCH) {
if ($event->key === HelpPages::SEARCH) {
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
$event->add_block(new Block("Approval", $this->theme->get_help_html()));
}
}
}
/**
* @param string[] $context
*/
private function no_approval_query(array $context): bool
{
foreach ($context as $term) {
@@ -168,23 +171,23 @@ class Approval extends Extension
return true;
}
public static function approve_image($image_id)
public static function approve_image(int $image_id): void
{
global $database, $user;
$database->execute(
"UPDATE images SET approved = :true, approved_by_id = :approved_by_id WHERE id = :id AND approved = :false",
["approved_by_id"=>$user->id, "id"=>$image_id, "true"=>true, "false"=>false]
["approved_by_id" => $user->id, "id" => $image_id, "true" => true, "false" => false]
);
}
public static function disapprove_image($image_id)
public static function disapprove_image(int $image_id): void
{
global $database;
$database->execute(
"UPDATE images SET approved = :false, approved_by_id = NULL WHERE id = :id AND approved = :true",
["id"=>$image_id, "true"=>true, "false"=>false]
["id" => $image_id, "true" => true, "false" => false]
);
}
@@ -192,13 +195,13 @@ class Approval extends Extension
{
global $user, $config;
if ($config->get_bool(ApprovalConfig::IMAGES) && $image->approved===false && !$user->can(Permissions::APPROVE_IMAGE) && $user->id!==$image->owner_id) {
if ($config->get_bool(ApprovalConfig::IMAGES) && $image['approved'] === false && !$user->can(Permissions::APPROVE_IMAGE) && $user->id !== $image->owner_id) {
return false;
}
return true;
}
public function onImageDownloading(ImageDownloadingEvent $event)
public function onImageDownloading(ImageDownloadingEvent $event): void
{
/**
* Deny images upon insufficient permissions.
@@ -208,7 +211,7 @@ class Approval extends Extension
}
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event): void
{
global $user, $config;
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
@@ -221,11 +224,11 @@ class Approval extends Extension
}
}
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event): void
{
global $user, $config;
if ($user->can(Permissions::APPROVE_IMAGE)&& $config->get_bool(ApprovalConfig::IMAGES)) {
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
if (in_array("approved:no", $event->search_terms)) {
$event->add_action("bulk_approve_image", "Approve", "a");
} else {
@@ -234,7 +237,7 @@ class Approval extends Extension
}
}
public function onBulkAction(BulkActionEvent $event)
public function onBulkAction(BulkActionEvent $event): void
{
global $page, $user;
@@ -262,7 +265,7 @@ class Approval extends Extension
}
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $database;

View File

@@ -26,9 +26,9 @@ class ApprovalTheme extends Themelet
$form = SHM_SIMPLE_FORM(
"admin/approval",
BUTTON(["name"=>'approval_action', "value"=>'approve_all'], "Approve All Posts"),
BUTTON(["name" => 'approval_action', "value" => 'approve_all'], "Approve All Posts"),
" ",
BUTTON(["name"=>'approval_action', "value"=>'disapprove_all'], "Disapprove All Posts"),
BUTTON(["name" => 'approval_action', "value" => 'disapprove_all'], "Disapprove All Posts"),
);
$page->add_block(new Block("Approval", $form));

View File

@@ -11,7 +11,7 @@ class ArtistsInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Artists System";
public string $url = self::SHIMMIE_URL;
public array $authors = ["Sein Kraft"=>"mail@seinkraft.info","Alpha"=>"alpha@furries.com.ar"];
public array $authors = ["Sein Kraft" => "mail@seinkraft.info","Alpha" => "alpha@furries.com.ar"];
public string $license = self::LICENSE_GPLV2;
public ExtensionCategory $category = ExtensionCategory::METADATA;
public string $description = "Simple artists extension";

View File

@@ -19,12 +19,23 @@ class AuthorSetEvent extends Event
}
}
/**
* @phpstan-type ArtistArtist array{id:int,artist_id:int,user_name:string,name:string,notes:string,type:string,posts:int}
* @phpstan-type ArtistAlias array{id:int,alias_id:int,alias_name:string,alias:string}
* @phpstan-type ArtistMember array{id:int,name:string}
* @phpstan-type ArtistUrl array{id:int,url:string}
*/
class Artists extends Extension
{
/** @var ArtistsTheme */
protected Themelet $theme;
public function onImageInfoSet(ImageInfoSetEvent $event)
public function onInitExt(InitExtEvent $event): void
{
Image::$prop_types["author"] = ImagePropType::STRING;
}
public function onImageInfoSet(ImageInfoSetEvent $event): void
{
global $user;
$author = $event->get_param("author");
@@ -33,7 +44,7 @@ class Artists extends Extension
}
}
public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event)
public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event): void
{
global $user;
$artistName = $this->get_artistName_by_imageID($event->image->id);
@@ -42,7 +53,7 @@ class Artists extends Extension
}
}
public function onSearchTermParse(SearchTermParseEvent $event)
public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
@@ -51,18 +62,18 @@ class Artists extends Extension
$matches = [];
if (preg_match("/^(author|artist)[=|:](.*)$/i", $event->term, $matches)) {
$char = $matches[2];
$event->add_querylet(new Querylet("author = :author_char", ["author_char"=>$char]));
$event->add_querylet(new Querylet("author = :author_char", ["author_char" => $char]));
}
}
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
public function onHelpPageBuilding(HelpPageBuildingEvent $event): void
{
if ($event->key===HelpPages::SEARCH) {
if ($event->key === HelpPages::SEARCH) {
$event->add_block(new Block("Artist", $this->theme->get_help_html()));
}
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $config, $database;
@@ -114,7 +125,7 @@ class Artists extends Extension
}
}
public function onAuthorSet(AuthorSetEvent $event)
public function onAuthorSet(AuthorSetEvent $event): void
{
global $database;
@@ -151,11 +162,11 @@ class Artists extends Extension
$database->execute(
"UPDATE images SET author = :author WHERE id = :id",
['author'=>$artistName, 'id'=>$event->image->id]
['author' => $artistName, 'id' => $event->image->id]
);
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user;
@@ -340,28 +351,28 @@ class Artists extends Extension
private function get_artistName_by_imageID(int $imageID): string
{
global $database;
$result = $database->get_row("SELECT author FROM images WHERE id = :id", ['id'=>$imageID]);
$result = $database->get_row("SELECT author FROM images WHERE id = :id", ['id' => $imageID]);
return $result['author'] ?? "";
}
private function url_exists_by_url(string $url): bool
{
global $database;
$result = $database->get_one("SELECT COUNT(1) FROM artist_urls WHERE url = :url", ['url'=>$url]);
$result = $database->get_one("SELECT COUNT(1) FROM artist_urls WHERE url = :url", ['url' => $url]);
return ($result != 0);
}
private function member_exists_by_name(string $member): bool
{
global $database;
$result = $database->get_one("SELECT COUNT(1) FROM artist_members WHERE name = :name", ['name'=>$member]);
$result = $database->get_one("SELECT COUNT(1) FROM artist_members WHERE name = :name", ['name' => $member]);
return ($result != 0);
}
private function alias_exists_by_name(string $alias): bool
{
global $database;
$result = $database->get_one("SELECT COUNT(1) FROM artist_alias WHERE alias = :alias", ['alias'=>$alias]);
$result = $database->get_one("SELECT COUNT(1) FROM artist_alias WHERE alias = :alias", ['alias' => $alias]);
return ($result != 0);
}
@@ -370,7 +381,7 @@ class Artists extends Extension
global $database;
$result = $database->get_one(
"SELECT COUNT(1) FROM artist_alias WHERE artist_id = :artist_id AND alias = :alias",
['artist_id'=>$artistID, 'alias'=>$alias]
['artist_id' => $artistID, 'alias' => $alias]
);
return ($result != 0);
}
@@ -411,43 +422,52 @@ class Artists extends Extension
return (int) $database->get_one("SELECT artist_id FROM artist_urls WHERE id = :id", ['id' => $urlID]);
}
private function delete_alias(int $aliasID)
private function delete_alias(int $aliasID): void
{
global $database;
$database->execute("DELETE FROM artist_alias WHERE id = :id", ['id'=>$aliasID]);
$database->execute("DELETE FROM artist_alias WHERE id = :id", ['id' => $aliasID]);
}
private function delete_url(int $urlID)
private function delete_url(int $urlID): void
{
global $database;
$database->execute("DELETE FROM artist_urls WHERE id = :id", ['id'=>$urlID]);
$database->execute("DELETE FROM artist_urls WHERE id = :id", ['id' => $urlID]);
}
private function delete_member(int $memberID)
private function delete_member(int $memberID): void
{
global $database;
$database->execute("DELETE FROM artist_members WHERE id = :id", ['id'=>$memberID]);
$database->execute("DELETE FROM artist_members WHERE id = :id", ['id' => $memberID]);
}
/**
* @return ArtistAlias
*/
private function get_alias_by_id(int $aliasID): array
{
global $database;
return $database->get_row("SELECT * FROM artist_alias WHERE id = :id", ['id'=>$aliasID]);
return $database->get_row("SELECT * FROM artist_alias WHERE id = :id", ['id' => $aliasID]);
}
/**
* @return ArtistUrl
*/
private function get_url_by_id(int $urlID): array
{
global $database;
return $database->get_row("SELECT * FROM artist_urls WHERE id = :id", ['id'=>$urlID]);
return $database->get_row("SELECT * FROM artist_urls WHERE id = :id", ['id' => $urlID]);
}
/**
* @return ArtistMember
*/
private function get_member_by_id(int $memberID): array
{
global $database;
return $database->get_row("SELECT * FROM artist_members WHERE id = :id", ['id'=>$memberID]);
return $database->get_row("SELECT * FROM artist_members WHERE id = :id", ['id' => $memberID]);
}
private function update_artist()
private function update_artist(): void
{
global $user;
$inputs = validate_input([
@@ -479,13 +499,13 @@ class Artists extends Extension
global $database;
$database->execute(
"UPDATE artists SET name = :name, notes = :notes, updated = now(), user_id = :user_id WHERE id = :id",
['name'=>$name, 'notes'=>$notes, 'user_id'=>$userID, 'id'=>$artistID]
['name' => $name, 'notes' => $notes, 'user_id' => $userID, 'id' => $artistID]
);
// ALIAS MATCHING SECTION
$i = 0;
$aliasesAsArray = is_null($aliasesAsString) ? [] : explode(" ", $aliasesAsString);
$aliasesIDsAsArray = is_null($aliasesIDsAsString) ? [] : explode(" ", $aliasesIDsAsString);
$aliasesIDsAsArray = is_null($aliasesIDsAsString) ? [] : array_map(fn ($n) => int_escape($n), explode(" ", $aliasesIDsAsString));
while ($i < count($aliasesAsArray)) {
// if an alias was updated
if ($i < count($aliasesIDsAsArray)) {
@@ -505,7 +525,7 @@ class Artists extends Extension
// MEMBERS MATCHING SECTION
$i = 0;
$membersAsArray = is_null($membersAsString) ? [] : explode(" ", $membersAsString);
$membersIDsAsArray = is_null($membersIDsAsString) ? [] : explode(" ", $membersIDsAsString);
$membersIDsAsArray = is_null($membersIDsAsString) ? [] : array_map(fn ($n) => int_escape($n), explode(" ", $membersIDsAsString));
while ($i < count($membersAsArray)) {
// if a member was updated
if ($i < count($membersIDsAsArray)) {
@@ -524,10 +544,11 @@ class Artists extends Extension
// URLS MATCHING SECTION
$i = 0;
assert(is_string($urlsAsString));
$urlsAsString = str_replace("\r\n", "\n", $urlsAsString);
$urlsAsString = str_replace("\n\r", "\n", $urlsAsString);
$urlsAsArray = is_null($urlsAsString) ? [] : explode("\n", $urlsAsString);
$urlsIDsAsArray = is_null($urlsIDsAsString) ? [] : explode(" ", $urlsIDsAsString);
$urlsAsArray = empty($urlsAsString) ? [] : explode("\n", $urlsAsString);
$urlsIDsAsArray = is_null($urlsIDsAsString) ? [] : array_map(fn ($n) => int_escape($n), explode(" ", $urlsIDsAsString));
while ($i < count($urlsAsArray)) {
// if an URL was updated
if ($i < count($urlsIDsAsArray)) {
@@ -545,7 +566,7 @@ class Artists extends Extension
}
}
private function update_alias()
private function update_alias(): void
{
global $user;
$inputs = validate_input([
@@ -555,16 +576,16 @@ class Artists extends Extension
$this->save_existing_alias($inputs['aliasID'], $inputs['alias'], $user->id);
}
private function save_existing_alias(int $aliasID, string $alias, int $userID)
private function save_existing_alias(int $aliasID, string $alias, int $userID): void
{
global $database;
$database->execute(
"UPDATE artist_alias SET alias = :alias, updated = now(), user_id = :user_id WHERE id = :id",
['alias'=>$alias, 'user_id'=>$userID, 'id'=>$aliasID]
['alias' => $alias, 'user_id' => $userID, 'id' => $aliasID]
);
}
private function update_url()
private function update_url(): void
{
global $user;
$inputs = validate_input([
@@ -574,16 +595,16 @@ class Artists extends Extension
$this->save_existing_url($inputs['urlID'], $inputs['url'], $user->id);
}
private function save_existing_url(int $urlID, string $url, int $userID)
private function save_existing_url(int $urlID, string $url, int $userID): void
{
global $database;
$database->execute(
"UPDATE artist_urls SET url = :url, updated = now(), user_id = :user_id WHERE id = :id",
['url'=>$url, 'user_id'=>$userID, 'id'=>$urlID]
['url' => $url, 'user_id' => $userID, 'id' => $urlID]
);
}
private function update_member()
private function update_member(): void
{
global $user;
$inputs = validate_input([
@@ -593,12 +614,12 @@ class Artists extends Extension
$this->save_existing_member($inputs['memberID'], $inputs['name'], $user->id);
}
private function save_existing_member(int $memberID, string $memberName, int $userID)
private function save_existing_member(int $memberID, string $memberName, int $userID): void
{
global $database;
$database->execute(
"UPDATE artist_members SET name = :name, updated = now(), user_id = :user_id WHERE id = :id",
['name'=>$memberName, 'user_id'=>$userID, 'id'=>$memberID]
['name' => $memberName, 'user_id' => $userID, 'id' => $memberID]
);
}
@@ -654,6 +675,7 @@ class Artists extends Extension
}
if (!is_null($urls)) {
assert(is_string($urls));
//delete double "separators"
$urls = str_replace("\r\n", "\n", $urls);
$urls = str_replace("\n\r", "\n", $urls);
@@ -674,7 +696,7 @@ class Artists extends Extension
$database->execute("
INSERT INTO artists (user_id, name, notes, created, updated)
VALUES (:user_id, :name, :notes, now(), now())
", ['user_id'=>$user->id, 'name'=>$name, 'notes'=>$notes]);
", ['user_id' => $user->id, 'name' => $name, 'notes' => $notes]);
return $database->get_last_insert_id('artists_id_seq');
}
@@ -683,17 +705,20 @@ class Artists extends Extension
global $database;
$result = $database->get_one(
"SELECT COUNT(1) FROM artists WHERE name = :name",
['name'=>$name]
['name' => $name]
);
return ($result != 0);
}
/**
* @return ArtistArtist
*/
private function get_artist(int $artistID): array
{
global $database;
$result = $database->get_row(
"SELECT * FROM artists WHERE id = :id",
['id'=>$artistID]
['id' => $artistID]
);
$result["name"] = stripslashes($result["name"]);
@@ -702,12 +727,15 @@ class Artists extends Extension
return $result;
}
/**
* @return ArtistMember[]
*/
private function get_members(int $artistID): array
{
global $database;
$result = $database->get_all(
"SELECT * FROM artist_members WHERE artist_id = :artist_id",
['artist_id'=>$artistID]
['artist_id' => $artistID]
);
$num = count($result);
@@ -718,12 +746,15 @@ class Artists extends Extension
return $result;
}
/**
* @return ArtistUrl[]
*/
private function get_urls(int $artistID): array
{
global $database;
$result = $database->get_all(
"SELECT id, url FROM artist_urls WHERE artist_id = :artist_id",
['artist_id'=>$artistID]
['artist_id' => $artistID]
);
$num = count($result);
@@ -739,7 +770,7 @@ class Artists extends Extension
global $database;
return (int) $database->get_one(
"SELECT id FROM artists WHERE name = :name",
['name'=>$name]
['name' => $name]
);
}
@@ -749,16 +780,16 @@ class Artists extends Extension
return (int) $database->get_one(
"SELECT artist_id FROM artist_alias WHERE alias = :alias",
['alias'=>$alias]
['alias' => $alias]
);
}
private function delete_artist(int $artistID)
private function delete_artist(int $artistID): void
{
global $database;
$database->execute(
"DELETE FROM artists WHERE id = :id",
['id'=>$artistID]
['id' => $artistID]
);
}
@@ -820,8 +851,8 @@ class Artists extends Extension
LIMIT :offset, :limit
",
[
"offset"=>$pageNumber * $artistsPerPage,
"limit"=>$artistsPerPage
"offset" => $pageNumber * $artistsPerPage,
"limit" => $artistsPerPage
]
);
@@ -867,17 +898,17 @@ class Artists extends Extension
}
}
private function save_new_url(int $artistID, string $url, int $userID)
private function save_new_url(int $artistID, string $url, int $userID): void
{
global $database;
$database->execute(
"INSERT INTO artist_urls (artist_id, created, updated, url, user_id) VALUES (:artist_id, now(), now(), :url, :user_id)",
['artist'=>$artistID, 'url'=>$url, 'user_id'=>$userID]
['artist' => $artistID, 'url' => $url, 'user_id' => $userID]
);
}
private function add_alias()
private function add_alias(): void
{
global $user;
$inputs = validate_input([
@@ -894,17 +925,17 @@ class Artists extends Extension
}
}
private function save_new_alias(int $artistID, string $alias, int $userID)
private function save_new_alias(int $artistID, string $alias, int $userID): void
{
global $database;
$database->execute(
"INSERT INTO artist_alias (artist_id, created, updated, alias, user_id) VALUES (:artist_id, now(), now(), :alias, :user_id)",
['artist_id'=>$artistID, 'alias'=>$alias, 'user_id'=>$userID]
['artist_id' => $artistID, 'alias' => $alias, 'user_id' => $userID]
);
}
private function add_members()
private function add_members(): void
{
global $user;
$inputs = validate_input([
@@ -921,13 +952,13 @@ class Artists extends Extension
}
}
private function save_new_member(int $artistID, string $member, int $userID)
private function save_new_member(int $artistID, string $member, int $userID): void
{
global $database;
$database->execute(
"INSERT INTO artist_members (artist_id, name, created, updated, user_id) VALUES (:artist_id, :name, now(), now(), :user_id)",
['artist'=>$artistID, 'name'=>$member, 'user_id'=>$userID]
['artist' => $artistID, 'name' => $member, 'user_id' => $userID]
);
}
@@ -937,7 +968,7 @@ class Artists extends Extension
$result = $database->get_one(
"SELECT COUNT(1) FROM artist_members WHERE artist_id = :artist_id AND name = :name",
['artist_id'=>$artistID, 'name'=>$member]
['artist_id' => $artistID, 'name' => $member]
);
return ($result != 0);
}
@@ -948,13 +979,15 @@ class Artists extends Extension
$result = $database->get_one(
"SELECT COUNT(1) FROM artist_urls WHERE artist_id = :artist_id AND url = :url",
['artist_id'=>$artistID, 'url'=>$url]
['artist_id' => $artistID, 'url' => $url]
);
return ($result != 0);
}
/**
* HERE WE GET THE INFO OF THE ALIAS
*
* @return array<string, mixed>
*/
private function get_alias(int $artistID): array
{
@@ -965,7 +998,7 @@ class Artists extends Extension
FROM artist_alias
WHERE artist_id = :artist_id
ORDER BY alias ASC
", ['artist_id'=>$artistID]);
", ['artist_id' => $artistID]);
$rc = count($result);
for ($i = 0; $i < $rc; $i++) {

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class ArtistsTest extends ShimmiePHPUnitTestCase
{
public function testSearch()
public function testSearch(): void
{
global $user;
$this->log_in_as_user();

View File

@@ -7,11 +7,17 @@ namespace Shimmie2;
use MicroHTML\HTMLElement;
use function MicroHTML\emptyHTML;
use function MicroHTML\{INPUT,P,SPAN,TD,TH,TR};
use function MicroHTML\{INPUT,P};
/**
* @phpstan-type ArtistArtist array{id:int,artist_id:int,user_name:string,name:string,notes:string,type:string,posts:int}
* @phpstan-type ArtistAlias array{id:int,alias_id:int,alias_name:string,alias:string}
* @phpstan-type ArtistMember array{id:int,name:string}
* @phpstan-type ArtistUrl array{id:int,url:string}
*/
class ArtistsTheme extends Themelet
{
public function get_author_editor_html(string $author): string
public function get_author_editor_html(string $author): HTMLElement
{
return SHM_POST_INFO(
"Author",
@@ -20,7 +26,7 @@ class ArtistsTheme extends Themelet
);
}
public function sidebar_options(string $mode, ?int $artistID=null, $is_admin=false): void
public function sidebar_options(string $mode, ?int $artistID = null, bool $is_admin = false): void
{
global $page, $user;
@@ -70,7 +76,13 @@ class ArtistsTheme extends Themelet
}
}
public function show_artist_editor($artist, $aliases, $members, $urls)
/**
* @param ArtistArtist $artist
* @param ArtistAlias[] $aliases
* @param ArtistMember[] $members
* @param ArtistUrl[] $urls
*/
public function show_artist_editor(array $artist, array $aliases, array $members, array $urls): void
{
global $user;
@@ -105,7 +117,7 @@ class ArtistsTheme extends Themelet
$urlsString .= $url["url"]."\n";
$urlsIDsString .= $url["id"]." ";
}
$urlsString = substr($urlsString, 0, strlen($urlsString) -1);
$urlsString = substr($urlsString, 0, strlen($urlsString) - 1);
$urlsIDsString = rtrim($urlsIDsString);
$html = make_form(make_link("artist/edited/".$artist['id'])).'
@@ -128,7 +140,7 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Edit artist", $html, "main", 10));
}
public function new_artist_composer()
public function new_artist_composer(): void
{
global $page, $user;
@@ -148,7 +160,10 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Artists", $html, "main", 10));
}
public function list_artists($artists, $pageNumber, $totalPages)
/**
* @param ArtistArtist[] $artists
*/
public function list_artists(array $artists, int $pageNumber, int $totalPages): void
{
global $user, $page;
@@ -227,7 +242,7 @@ class ArtistsTheme extends Themelet
$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
}
public function show_new_alias_composer($artistID)
public function show_new_alias_composer(int $artistID): void
{
global $user;
@@ -244,7 +259,7 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Artist Aliases", $html, "main", 20));
}
public function show_new_member_composer($artistID)
public function show_new_member_composer(int $artistID): void
{
global $user;
@@ -261,7 +276,7 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Artist members", $html, "main", 30));
}
public function show_new_url_composer($artistID)
public function show_new_url_composer(int $artistID): void
{
global $user;
@@ -278,7 +293,10 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Artist URLs", $html, "main", 40));
}
public function show_alias_editor($alias)
/**
* @param ArtistAlias $alias
*/
public function show_alias_editor(array $alias): void
{
global $user;
@@ -294,7 +312,10 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Edit Alias", $html, "main", 10));
}
public function show_url_editor($url)
/**
* @param ArtistUrl $url
*/
public function show_url_editor(array $url): void
{
global $user;
@@ -310,7 +331,10 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Edit URL", $html, "main", 10));
}
public function show_member_editor($member)
/**
* @param ArtistMember $member
*/
public function show_member_editor(array $member): void
{
global $user;
@@ -326,11 +350,18 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Edit Member", $html, "main", 10));
}
public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin)
/**
* @param ArtistArtist $artist
* @param ArtistAlias[] $aliases
* @param ArtistMember[] $members
* @param ArtistUrl[] $urls
* @param Image[] $images
*/
public function show_artist(array $artist, array $aliases, array $members, array $urls, array $images, bool $userIsLogged, bool $userIsAdmin): void
{
global $page;
$artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>";
$artist_link = "<a href='".search_link([$artist['name']])."'>".str_replace("_", " ", $artist['name'])."</a>";
$html = "<table id='poolsList' class='zebra'>
<thead>
@@ -394,6 +425,9 @@ class ArtistsTheme extends Themelet
$page->add_block(new Block("Artist Posts", $artist_images, "main", 20));
}
/**
* @param ArtistAlias[] $aliases
*/
private function render_aliases(array $aliases, bool $userIsLogged, bool $userIsAdmin): string
{
$html = "";
@@ -440,6 +474,9 @@ class ArtistsTheme extends Themelet
return $html;
}
/**
* @param ArtistMember[] $members
*/
private function render_members(array $members, bool $userIsLogged, bool $userIsAdmin): string
{
$html = "";
@@ -484,6 +521,9 @@ class ArtistsTheme extends Themelet
return $html;
}
/**
* @param ArtistUrl[] $urls
*/
private function render_urls(array $urls, bool $userIsLogged, bool $userIsAdmin): string
{
$html = "";

View File

@@ -10,7 +10,7 @@ class AutoTaggerInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Auto-Tagger";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public array $authors = ["Matthew Barbour" => "matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public ExtensionCategory $category = ExtensionCategory::METADATA;
public string $description = "Provides several automatic tagging functions";

View File

@@ -21,12 +21,12 @@ class AutoTaggerTable extends Table
$this->size = 100;
$this->limit = 1000000;
$this->set_columns([
new TextColumn("tag", "Tag"),
new TextColumn("additional_tags", "Additional Tags"),
new AutoCompleteColumn("tag", "Tag"),
new AutoCompleteColumn("additional_tags", "Additional Tags"),
new ActionColumn("tag"),
]);
$this->order_by = ["tag"];
$this->table_attrs = ["class" => "zebra"];
$this->table_attrs = ["class" => "zebra form"];
}
}
@@ -67,7 +67,7 @@ class AutoTagger extends Extension
/** @var AutoTaggerTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $config, $database, $page, $user;
@@ -114,14 +114,14 @@ class AutoTagger extends Extension
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
if ($event->parent=="tags") {
if ($event->parent == "tags") {
$event->add_nav_link("auto_tag", new Link('auto_tag/list'), "Auto-Tag", NavLink::is_active(["auto_tag"]));
}
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $database;
@@ -141,27 +141,27 @@ class AutoTagger extends Extension
}
}
public function onTagSet(TagSetEvent $event)
public function onTagSet(TagSetEvent $event): void
{
$results = $this->apply_auto_tags($event->tags);
$results = $this->apply_auto_tags($event->new_tags);
if (!empty($results)) {
$event->tags = $results;
$event->new_tags = $results;
}
}
public function onAddAutoTag(AddAutoTagEvent $event)
public function onAddAutoTag(AddAutoTagEvent $event): void
{
global $page;
$this->add_auto_tag($event->tag, $event->additional_tags);
$page->flash("Added Auto-Tag");
}
public function onDeleteAutoTag(DeleteAutoTagEvent $event)
public function onDeleteAutoTag(DeleteAutoTagEvent $event): void
{
$this->remove_auto_tag($event->tag);
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::MANAGE_AUTO_TAG)) {
@@ -174,6 +174,7 @@ class AutoTagger extends Extension
$csv = "";
$pairs = $database->get_pairs("SELECT tag, additional_tags FROM auto_tag ORDER BY tag");
foreach ($pairs as $old => $new) {
assert(is_string($new));
$csv .= "\"$old\",\"$new\"\n";
}
return $csv;
@@ -193,10 +194,10 @@ class AutoTagger extends Extension
return $i;
}
private function add_auto_tag(string $tag, string $additional_tags)
private function add_auto_tag(string $tag, string $additional_tags): void
{
global $database;
$existing_tags = $database->get_one("SELECT additional_tags FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag"=>$tag]);
$existing_tags = $database->get_one("SELECT additional_tags FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag" => $tag]);
if (!is_null($existing_tags)) {
// Auto Tags already exist, so we will append new tags to the existing one
$tag = Tag::sanitize($tag);
@@ -210,7 +211,7 @@ class AutoTagger extends Extension
$database->execute(
"UPDATE auto_tag set additional_tags=:existing_tags where tag=:tag",
["tag"=>$tag, "existing_tags"=>Tag::implode($existing_tags)]
["tag" => $tag, "existing_tags" => Tag::implode($existing_tags)]
);
log_info(
AutoTaggerInfo::KEY,
@@ -222,7 +223,7 @@ class AutoTagger extends Extension
$database->execute(
"INSERT INTO auto_tag(tag, additional_tags) VALUES(:tag, :additional_tags)",
["tag"=>$tag, "additional_tags"=>Tag::implode($additional_tags)]
["tag" => $tag, "additional_tags" => Tag::implode($additional_tags)]
);
log_info(
@@ -234,12 +235,12 @@ class AutoTagger extends Extension
$this->apply_new_auto_tag($tag);
}
private function apply_new_auto_tag(string $tag)
private function apply_new_auto_tag(string $tag): void
{
global $database;
$tag_id = $database->get_one("SELECT id FROM tags WHERE LOWER(tag) = LOWER(:tag)", ["tag"=>$tag]);
$tag_id = $database->get_one("SELECT id FROM tags WHERE LOWER(tag) = LOWER(:tag)", ["tag" => $tag]);
if (!empty($tag_id)) {
$image_ids = $database->get_col_iterable("SELECT image_id FROM image_tags WHERE tag_id = :tag_id", ["tag_id"=>$tag_id]);
$image_ids = $database->get_col_iterable("SELECT image_id FROM image_tags WHERE tag_id = :tag_id", ["tag_id" => $tag_id]);
foreach ($image_ids as $image_id) {
$image_id = (int) $image_id;
$image = Image::by_id_ex($image_id);
@@ -248,7 +249,7 @@ class AutoTagger extends Extension
}
}
private function remove_auto_tag(String $tag)
private function remove_auto_tag(string $tag): void
{
global $database;
@@ -256,9 +257,10 @@ class AutoTagger extends Extension
}
/**
* #param string[] $tags_mixed
* @param string[] $tags_mixed
* @return string[]
*/
private function apply_auto_tags(array $tags_mixed): ?array
private function apply_auto_tags(array $tags_mixed): array
{
global $database;

View File

@@ -6,14 +6,14 @@ namespace Shimmie2;
class AutoTaggerTest extends ShimmiePHPUnitTestCase
{
public function testAutoTaggerList()
public function testAutoTaggerList(): void
{
$this->get_page('auto_tag/list');
$this->assert_response(200);
$this->assert_title("Auto-Tag");
}
public function testAutoTaggerListReadOnly()
public function testAutoTaggerListReadOnly(): void
{
$this->log_in_as_user();
$this->get_page('auto_tag/list');
@@ -26,7 +26,7 @@ class AutoTaggerTest extends ShimmiePHPUnitTestCase
$this->assert_no_text("value=\"Add\"");
}
public function testAutoTagger()
public function testAutoTagger(): void
{
$this->log_in_as_admin();

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Shimmie2;
use MicroHTML\HTMLElement;
class AutoTaggerTheme extends Themelet
{
/**
@@ -11,7 +13,7 @@ class AutoTaggerTheme extends Themelet
*
* Note: $can_manage = whether things like "add new alias" should be shown
*/
public function display_auto_tagtable($table, $paginator): void
public function display_auto_tagtable(HTMLElement $table, HTMLElement $paginator): void
{
global $page, $user;

View File

@@ -10,6 +10,6 @@ class AutoCompleteInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Autocomplete";
public array $authors = ["Daku"=>"admin@codeanimu.net"];
public array $authors = ["Daku" => "admin@codeanimu.net"];
public string $description = "Adds autocomplete to search & tagging.";
}

View File

@@ -6,15 +6,12 @@ namespace Shimmie2;
class AutoComplete extends Extension
{
/** @var AutoCompleteTheme */
protected Themelet $theme;
public function get_priority(): int
{
return 30;
} // before Home
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page;
@@ -28,8 +25,6 @@ class AutoComplete extends Extension
$page->set_mime(MimeType::JSON);
$page->set_data(\Safe\json_encode($res));
}
$this->theme->build_autocomplete($page);
}
/**
@@ -39,10 +34,6 @@ class AutoComplete extends Extension
{
global $cache, $database;
if (!$search) {
return [];
}
$search = strtolower($search);
if (
$search == '' ||
@@ -54,15 +45,17 @@ class AutoComplete extends Extension
}
# memcache keys can't contain spaces
$cache_key = "autocomplete:" . md5($search);
$cache_key = "autocomplete:$limit:" . md5($search);
$limitSQL = "";
$search = str_replace('_', '\_', $search);
$search = str_replace('%', '\%', $search);
$SQLarr = ["search"=>"$search%"]; #, "cat_search"=>"%:$search%"];
$SQLarr = [
"search" => "$search%",
"cat_search" => Extension::is_enabled(TagCategoriesInfo::KEY) ? "%:$search%" : "",
];
if ($limit !== 0) {
$limitSQL = "LIMIT :limit";
$SQLarr['limit'] = $limit;
$cache_key .= "-" . $limit;
}
return cache_get_or_set($cache_key, function () use ($database, $limitSQL, $SQLarr) {

View File

@@ -201,37 +201,36 @@ function setCompletion(element, new_word) {
}
document.addEventListener('DOMContentLoaded', () => {
var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename', 'order:favorites'];
// Find all elements with class 'autocomplete_tags'
document.querySelectorAll('.autocomplete_tags').forEach((element) => {
// set metadata
element.completions = {};
element.selected_completion = -1;
element.completer_timeout = null;
$('.autocomplete_tags').tagit({
singleFieldDelimiter: ' ',
beforeTagAdded: function(event, ui) {
if(metatags.indexOf(ui.tagLabel) !== -1) {
ui.tag.addClass('tag-metatag');
} else {
console.log(ui.tagLabel);
// give special class to negative tags
if(ui.tagLabel[0] === '-') {
ui.tag.addClass('tag-negative');
}else{
ui.tag.addClass('tag-positive');
}
// disable built-in autocomplete
element.setAttribute('autocomplete', 'off');
// safari treats spellcheck as a form of autocomplete
element.setAttribute('spellcheck', 'off');
// when element is focused, add completion block
element.addEventListener('focus', () => {
updateCompletions(element);
});
// when element is blurred, remove completion block
element.addEventListener('blur', () => {
hideCompletions();
});
// when cursor is moved, change current completion
document.addEventListener('selectionchange', () => {
// if element is focused
if(document.activeElement === element) {
updateCompletions(element);
}
},
autocomplete : ({
source: function (request, response) {
var ac_metatags = $.map(
$.grep(metatags, function(s) {
// Only show metatags for strings longer than one character
return (request.term.length > 1 && s.indexOf(request.term) === 0);
}),
function(item) {
return {
label : item + ' [metatag]',
value : item
};
}
);
});
element.addEventListener('keydown', (event) => {
// up / down should select previous / next completion
@@ -256,57 +255,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
$('#tag_editor,[name="bulk_tags"]').tagit({
singleFieldDelimiter: ' ',
autocomplete : ({
source: function (request, response) {
$.ajax({
url: base_href + '/api/internal/autocomplete',
data: {'s': request.term},
dataType : 'json',
type : 'GET',
success : function (data) {
response(
$.map(data, function (count, item) {
return {
label : item + ' ('+count+')',
value : item
};
})
);
},
error : function (request, status, error) {
console.log(error);
}
});
},
minLength: 1
})
});
$('.ui-autocomplete-input').keydown(function(e) {
var keyCode = e.keyCode || e.which;
//Stop tags containing space.
if(keyCode === 32) {
e.preventDefault();
var el = $('.ui-widget-content:focus');
//Find the correct element in a page with multiple tagit input boxes.
$('.autocomplete_tags').each(function(_,n){
if (n.parentNode.contains(el[0])){
$(n.parentNode).find('.autocomplete_tags').tagit('createTag', el.val());
}
});
$(this).autocomplete('close');
} else if (keyCode === 9) {
e.preventDefault();
var tag = $('.tagit-autocomplete[style*=\"display: block\"] > li:focus, .tagit-autocomplete[style*=\"display: block\"] > li:first').first();
if(tag.length){
$(tag).click();
$('.ui-autocomplete-input').val(''); //If tag already exists, make sure to remove duplicate.
}
}
// on change, update completions
element.addEventListener('input', () => {
updateCompletions(element);
});
});
});

View File

@@ -1,9 +1,22 @@
#Navigationleft .blockbody { overflow: visible; }
.tagit { background: white !important; border: 1px solid grey !important; cursor: text; }
.tagit-choice { cursor: initial; }
input[name=search] ~ input[type=submit] { display: inline-block !important; }
.tag-negative { background: #ff8080 !important; }
.tag-positive { background: #40bf40 !important; }
.tag-metatag { background: #eaa338 !important; }
.autocomplete_completions {
position: absolute;
z-index: 1000;
border: 1px solid #ccc;
color: #000;
background-color: #fff;
padding: 5px;
list-style: none;
margin: 0;
padding: 0;
font-size: 1em;
white-space: nowrap;
overflow: hidden;
text-align: left;
}
.autocomplete_completions LI {
padding: 0.15em;
}
.autocomplete_completions .selected {
background-color: #ccc;
outline: none;
}

View File

@@ -6,14 +6,14 @@ namespace Shimmie2;
class AutoCompleteTest extends ShimmiePHPUnitTestCase
{
public function testAuth()
public function testAuth(): void
{
$this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "link");
send_event(new AddAliasEvent("prince_zelda", "link"));
send_event(new UserLoginEvent(User::by_name(self::$anon_name)));
$page = $this->get_page('api/internal/autocomplete', ["s"=>"not-a-tag"]);
$page = $this->get_page('api/internal/autocomplete', ["s" => "not-a-tag"]);
$this->assertEquals(200, $page->code);
$this->assertEquals(PageMode::DATA, $page->mode);
$this->assertEquals("[]", $page->data);

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class BanWords extends Extension
{
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_string('banned_words', "
@@ -39,7 +39,7 @@ xanax
");
}
public function onCommentPosting(CommentPostingEvent $event)
public function onCommentPosting(CommentPostingEvent $event): void
{
global $user;
if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) {
@@ -47,17 +47,17 @@ xanax
}
}
public function onSourceSet(SourceSetEvent $event)
public function onSourceSet(SourceSetEvent $event): void
{
$this->test_text($event->source, new UserError("Source contains banned terms"));
}
public function onTagSet(TagSetEvent $event)
public function onTagSet(TagSetEvent $event): void
{
$this->test_text(Tag::implode($event->new_tags), new UserError("Tags contain banned terms"));
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Banned Phrases");
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
@@ -97,6 +97,9 @@ xanax
}
}
/**
* @return string[]
*/
private function get_words(): array
{
global $config;

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class BanWordsTest extends ShimmiePHPUnitTestCase
{
public function check_blocked($image_id, $words)
public function check_blocked(int $image_id, string $words): void
{
global $user;
try {
@@ -17,7 +17,7 @@ class BanWordsTest extends ShimmiePHPUnitTestCase
}
}
public function testWordBan()
public function testWordBan(): void
{
global $config;
$config->set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//");

View File

@@ -7,6 +7,12 @@ namespace Shimmie2;
class BBCode extends FormatterExtension
{
public function format(string $text): string
{
$text = $this->_format($text);
return "<span class='bbcode'>$text</span>";
}
public function _format(string $text): string
{
$text = $this->extract_code($text);
foreach ([
@@ -97,8 +103,8 @@ class BBCode extends FormatterExtension
}
$beginning = substr($text, 0, $start);
$middle = str_rot13(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$middle = str_rot13(substr($text, $start + $l1, ($end - $start - $l1)));
$ending = substr($text, $end + $l2, (strlen($text) - $end + $l2));
$text = $beginning . $middle . $ending;
}
@@ -131,8 +137,8 @@ class BBCode extends FormatterExtension
}
$beginning = substr($text, 0, $start);
$middle = base64_encode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$middle = base64_encode(substr($text, $start + $l1, ($end - $start - $l1)));
$ending = substr($text, $end + $l2, (strlen($text) - $end + $l2));
$text = $beginning . "[code!]" . $middle . "[/code!]" . $ending;
}
@@ -155,10 +161,10 @@ class BBCode extends FormatterExtension
}
$beginning = substr($text, 0, $start);
$middle = base64_decode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$middle = base64_decode(substr($text, $start + $l1, ($end - $start - $l1)));
$ending = substr($text, $end + $l2, (strlen($text) - $end + $l2));
$text = $beginning . "<pre>" . $middle . "</pre>" . $ending;
$text = $beginning . "<pre class='code'>" . $middle . "</pre>" . $ending;
}
return $text;
}

View File

@@ -1,18 +1,20 @@
document.addEventListener('DOMContentLoaded', () => {
$(".shm-clink").each(function(idx, elm) {
var target_id = $(elm).data("clink-sel");
if(target_id && $(target_id).length > 0) {
document.querySelectorAll(".shm-clink").forEach(function(el) {
var target_id = el.getAttribute("data-clink-sel");
if(target_id && document.querySelectorAll(target_id).length > 0) {
// if the target comment is already on this page, don't bother
// switching pages
$(elm).attr("href", target_id);
el.setAttribute("href", target_id);
// highlight it when clicked
$(elm).click(function(e) {
el.addEventListener("click", function(e) {
// This needs jQuery UI
$(target_id).highlight();
});
// vanilla target name should already be in the URL tag, but this
// will include the anon ID as displayed on screen
$(elm).html("@"+$(target_id+" .username").html());
el.innerHTML = "@"+document.querySelector(target_id+" .username").innerHTML;
}
});
});

View File

@@ -1,16 +1,15 @@
CODE {
background: #DEDEDE;
font-size: 0.8em;
.bbcode PRE.code {
background: #DEDEDE;
font-size: 0.9rem;
}
BLOCKQUOTE {
.bbcode BLOCKQUOTE {
border: 1px solid black;
padding: 8px;
background: #DDD;
}
.anchor A.alink {
.bbcode .anchor A.alink {
visibility: hidden;
}
.anchor:hover A.alink {
.bbcode .anchor:hover A.alink {
visibility: visible;
}

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class BBCodeTest extends ShimmiePHPUnitTestCase
{
public function testBasics()
public function testBasics(): void
{
$this->assertEquals(
"<b>bold</b><i>italic</i>",
@@ -14,7 +14,7 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
public function testStacking()
public function testStacking(): void
{
$this->assertEquals(
"<b>B</b><i>I</i><b>B</b>",
@@ -26,7 +26,7 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
public function testFailure()
public function testFailure(): void
{
$this->assertEquals(
"[b]bold[i]italic",
@@ -34,15 +34,15 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
public function testCode()
public function testCode(): void
{
$this->assertEquals(
"<pre>[b]bold[/b]</pre>",
"<pre class='code'>[b]bold[/b]</pre>",
$this->filter("[code][b]bold[/b][/code]")
);
}
public function testNestedList()
public function testNestedList(): void
{
$this->assertEquals(
"<ul><li>a<ul><li>a<li>b</ul><li>b</ul>",
@@ -54,7 +54,7 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
public function testSpoiler()
public function testSpoiler(): void
{
$this->assertEquals(
"<span style=\"background-color:#000; color:#000;\">ShishNet</span>",
@@ -69,7 +69,7 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
# "[spoiler]ShishNet");
}
public function testURL()
public function testURL(): void
{
$this->assertEquals(
"<a href=\"https://shishnet.org\">https://shishnet.org</a>",
@@ -85,7 +85,7 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
public function testEmailURL()
public function testEmailURL(): void
{
$this->assertEquals(
"<a href=\"mailto:spam@shishnet.org\">spam@shishnet.org</a>",
@@ -93,7 +93,7 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
public function testAnchor()
public function testAnchor(): void
{
$this->assertEquals(
'<span class="anchor">Rules <a class="alink" href="#bb-rules" name="bb-rules" title="link to this anchor"> ¶ </a></span>',
@@ -101,19 +101,19 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
private function filter($in): string
private function filter(string $in): string
{
$bb = new BBCode();
return $bb->format($in);
return $bb->_format($in);
}
private function strip($in): string
private function strip(string $in): string
{
$bb = new BBCode();
return $bb->strip($in);
}
public function testSiteLinks()
public function testSiteLinks(): void
{
$this->assertEquals(
'<a class="shm-clink" data-clink-sel="" href="/test/post/view/123">&gt;&gt;123</a>',

View File

@@ -9,7 +9,7 @@ class Biography extends Extension
/** @var BiographyTheme */
protected Themelet $theme;
public function onUserPageBuilding(UserPageBuildingEvent $event)
public function onUserPageBuilding(UserPageBuildingEvent $event): void
{
global $page, $user;
$duser = $event->display_user;
@@ -23,7 +23,7 @@ class Biography extends Extension
}
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user, $user_config;
if ($event->page_matches("biography", method: "POST")) {

View File

@@ -6,10 +6,10 @@ namespace Shimmie2;
class BiographyTest extends ShimmiePHPUnitTestCase
{
public function testBio()
public function testBio(): void
{
$this->log_in_as_user();
$this->post_page("biography", ["biography"=>"My bio goes here"]);
$this->post_page("biography", ["biography" => "My bio goes here"]);
$this->get_page("user/" . self::$user_name);
$this->assert_text("My bio goes here");

View File

@@ -8,12 +8,12 @@ use function MicroHTML\TEXTAREA;
class BiographyTheme extends Themelet
{
public function display_biography(Page $page, string $bio)
public function display_biography(Page $page, string $bio): void
{
$page->add_block(new Block("About Me", format_text($bio), "main", 30, "about-me"));
}
public function display_composer(Page $page, string $bio)
public function display_composer(Page $page, string $bio): void
{
$html = SHM_SIMPLE_FORM(
"biography",

View File

@@ -9,7 +9,7 @@ class Blocks extends Extension
/** @var BlocksTheme */
protected Themelet $theme;
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $database;
if ($this->get_version("ext_blocks_version") < 1) {
@@ -31,17 +31,17 @@ class Blocks extends Extension
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
global $user;
if ($event->parent==="system") {
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)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::MANAGE_BLOCKS)) {
@@ -49,15 +49,11 @@ class Blocks extends Extension
}
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $cache, $database, $page, $user;
$blocks = $cache->get("blocks");
if (is_null($blocks)) {
$blocks = $database->get_all("SELECT * FROM blocks");
$cache->set("blocks", $blocks, 600);
}
$blocks = cache_get_or_set("blocks", fn () => $database->get_all("SELECT * FROM blocks"), 600);
foreach ($blocks as $block) {
$path = implode("/", $event->args);
if (strlen($path) < 4000 && fnmatch($block['pages'], $path)) {

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class BlocksTest extends ShimmiePHPUnitTestCase
{
public function testBlocks()
public function testBlocks(): void
{
$this->log_in_as_admin();
$this->get_page("blocks/list");

View File

@@ -16,35 +16,38 @@ use function MicroHTML\OPTION;
class BlocksTheme extends Themelet
{
public function display_blocks($blocks)
/**
* @param array<array{id:int,title:string,area:string,priority:int,userclass:string,pages:string,content:string}> $blocks
*/
public function display_blocks(array $blocks): void
{
global $page;
$html = TABLE(["class"=>"form", "style"=>"width: 100%;"]);
$html = TABLE(["class" => "form", "style" => "width: 100%;"]);
foreach ($blocks as $block) {
$html->appendChild(SHM_SIMPLE_FORM(
"blocks/update",
TR(
INPUT(["type"=>"hidden", "name"=>"id", "value"=>$block['id']]),
INPUT(["type" => "hidden", "name" => "id", "value" => $block['id']]),
TH("Title"),
TD(INPUT(["type"=>"text", "name"=>"title", "value"=>$block['title']])),
TD(INPUT(["type" => "text", "name" => "title", "value" => $block['title']])),
TH("Area"),
TD(INPUT(["type"=>"text", "name"=>"area", "value"=>$block['area']])),
TD(INPUT(["type" => "text", "name" => "area", "value" => $block['area']])),
TH("Priority"),
TD(INPUT(["type"=>"text", "name"=>"priority", "value"=>$block['priority']])),
TD(INPUT(["type" => "text", "name" => "priority", "value" => $block['priority']])),
TH("User Class"),
TD(INPUT(["type"=>"text", "name"=>"userclass", "value"=>$block['userclass']])),
TD(INPUT(["type" => "text", "name" => "userclass", "value" => $block['userclass']])),
TH("Pages"),
TD(INPUT(["type"=>"text", "name"=>"pages", "value"=>$block['pages']])),
TD(INPUT(["type" => "text", "name" => "pages", "value" => $block['pages']])),
TH("Delete"),
TD(INPUT(["type"=>"checkbox", "name"=>"delete"])),
TD(INPUT(["type"=>"submit", "value"=>"Save"]))
TD(INPUT(["type" => "checkbox", "name" => "delete"])),
TD(INPUT(["type" => "submit", "value" => "Save"]))
),
TR(
TD(["colspan"=>"13"], TEXTAREA(["rows"=>"5", "name"=>"content"], $block['content']))
TD(["colspan" => "13"], TEXTAREA(["rows" => "5", "name" => "content"], $block['content']))
),
TR(
TD(["colspan"=>"13"], rawHTML("&nbsp;"))
TD(["colspan" => "13"], rawHTML("&nbsp;"))
),
));
}
@@ -53,19 +56,19 @@ class BlocksTheme extends Themelet
"blocks/add",
TR(
TH("Title"),
TD(INPUT(["type"=>"text", "name"=>"title", "value"=>""])),
TD(INPUT(["type" => "text", "name" => "title", "value" => ""])),
TH("Area"),
TD(SELECT(["name"=>"area"], OPTION("left"), OPTION("main"))),
TD(SELECT(["name" => "area"], OPTION("left"), OPTION("main"))),
TH("Priority"),
TD(INPUT(["type"=>"text", "name"=>"priority", "value"=>'50'])),
TD(INPUT(["type" => "text", "name" => "priority", "value" => '50'])),
TH("User Class"),
TD(INPUT(["type"=>"text", "name"=>"userclass", "value"=>""])),
TD(INPUT(["type" => "text", "name" => "userclass", "value" => ""])),
TH("Pages"),
TD(INPUT(["type"=>"text", "name"=>"pages", "value"=>'post/list*'])),
TD(["colspan"=>'3'], INPUT(["type"=>"submit", "value"=>"Add"]))
TD(INPUT(["type" => "text", "name" => "pages", "value" => 'post/list*'])),
TD(["colspan" => '3'], INPUT(["type" => "submit", "value" => "Add"]))
),
TR(
TD(["colspan"=>"13"], TEXTAREA(["rows"=>"5", "name"=>"content"]))
TD(["colspan" => "13"], TEXTAREA(["rows" => "5", "name" => "content"]))
),
));

View File

@@ -11,10 +11,8 @@ class BlotterInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Blotter";
public string $url = "http://seemslegit.com/";
public array $authors = ["Zach Hall"=>"zach@sosguy.net"];
public array $authors = ["Zach Hall" => "zach@sosguy.net"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Displays brief updates about whatever you want on every page.
Colors and positioning can be configured to match your site's design.
Development TODO at https://github.com/zshall/shimmie2/issues";
public string $description = "Displays brief updates about whatever you want on every page.";
public ?string $documentation = "Colors and positioning can be configured to match your site's design.<p>Development TODO at https://github.com/zshall/shimmie2/issues";
}

View File

@@ -9,7 +9,7 @@ class Blotter extends Extension
/** @var BlotterTheme */
protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_int("blotter_recent", 5);
@@ -17,7 +17,7 @@ class Blotter extends Extension
$config->set_default_string("blotter_position", "subheading");
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $database;
@@ -31,7 +31,7 @@ class Blotter extends Extension
// Insert sample data:
$database->execute(
"INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), :text, :important)",
["text"=>"Installed the blotter extension!", "important"=>true]
["text" => "Installed the blotter extension!", "important" => true]
);
log_info("blotter", "Installed tables for blotter extension.");
$this->set_version("blotter_version", 2);
@@ -42,7 +42,7 @@ class Blotter extends Extension
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Blotter");
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: ");
@@ -50,10 +50,10 @@ class Blotter extends Extension
$sb->add_choice_option("blotter_position", ["Top of page" => "subheading", "In navigation bar" => "left"], "<br>Position: ");
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
global $user;
if ($event->parent==="system") {
if ($event->parent === "system") {
if ($user->can(Permissions::BLOTTER_ADMIN)) {
$event->add_nav_link("blotter", new Link('blotter/editor'), "Blotter Editor");
}
@@ -61,7 +61,7 @@ class Blotter extends Extension
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::BLOTTER_ADMIN)) {
@@ -69,7 +69,7 @@ class Blotter extends Extension
}
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $database, $user;
if ($event->page_matches("blotter/editor", method: "GET", permission: Permissions::BLOTTER_ADMIN)) {
@@ -105,7 +105,7 @@ class Blotter extends Extension
$this->display_blotter();
}
private function display_blotter()
private function display_blotter(): void
{
global $database, $config;
$entries = $database->get_all(

View File

@@ -4,14 +4,14 @@ document.addEventListener('DOMContentLoaded', () => {
$(".shm-blotter2-toggle").click(function() {
$(".shm-blotter2").slideToggle("slow", function() {
if($(".shm-blotter2").is(":hidden")) {
Cookies.set("ui-blotter2-hidden", 'true');
shm_cookie_set("ui-blotter2-hidden", 'true');
}
else {
Cookies.set("ui-blotter2-hidden", 'false');
shm_cookie_set("ui-blotter2-hidden", 'false');
}
});
});
if(Cookies.get("ui-blotter2-hidden") === 'true') {
if(shm_cookie_get("ui-blotter2-hidden") === 'true') {
$(".shm-blotter2").hide();
}
});

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class BlotterTest extends ShimmiePHPUnitTestCase
{
public function testDenial()
public function testDenial(): void
{
$this->assertException(PermissionDenied::class, function () {
$this->get_page("blotter/editor");
@@ -19,7 +19,7 @@ class BlotterTest extends ShimmiePHPUnitTestCase
});
}
public function testAddViewRemove()
public function testAddViewRemove(): void
{
$this->log_in_as_admin();

View File

@@ -4,9 +4,15 @@ declare(strict_types=1);
namespace Shimmie2;
/**
* @phpstan-type BlotterEntry array{id:int,entry_date:string,entry_text:string,important:bool}
*/
class BlotterTheme extends Themelet
{
public function display_editor($entries)
/**
* @param BlotterEntry[] $entries
*/
public function display_editor(array $entries): void
{
global $page;
$html = $this->get_html_for_blotter_editor($entries);
@@ -16,7 +22,10 @@ class BlotterTheme extends Themelet
$page->add_block(new Block("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0));
}
public function display_blotter_page($entries)
/**
* @param BlotterEntry[] $entries
*/
public function display_blotter_page(array $entries): void
{
global $page;
$html = $this->get_html_for_blotter_page($entries);
@@ -25,6 +34,9 @@ class BlotterTheme extends Themelet
$page->add_block(new Block("Blotter Entries", $html, "main", 10));
}
/**
* @param BlotterEntry[] $entries
*/
public function display_blotter(array $entries): void
{
global $page, $config;
@@ -33,6 +45,9 @@ class BlotterTheme extends Themelet
$page->add_block(new Block(null, $html, $position, 20));
}
/**
* @param BlotterEntry[] $entries
*/
private function get_html_for_blotter_editor(array $entries): string
{
global $user;
@@ -103,6 +118,9 @@ class BlotterTheme extends Themelet
return $html;
}
/**
* @param BlotterEntry[] $entries
*/
private function get_html_for_blotter_page(array $entries): string
{
/**
@@ -126,7 +144,7 @@ class BlotterTheme extends Themelet
$entry_text = $entries[$i]['entry_text'];
if ($entries[$i]['important'] == 'Y') {
$i_open = "<span style='color: #$i_color;'>";
$i_close="</span>";
$i_close = "</span>";
}
$html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}<br /><br />";
}
@@ -134,6 +152,9 @@ class BlotterTheme extends Themelet
return $html;
}
/**
* @param BlotterEntry[] $entries
*/
private function get_html_for_blotter(array $entries): string
{
global $config;
@@ -153,7 +174,7 @@ class BlotterTheme extends Themelet
$entry_text = $entry['entry_text'];
if ($entry['important'] == 'Y') {
$i_open = "<span style='color: #$i_color'>";
$i_close="</span>";
$i_close = "</span>";
}
$entries_list .= "<li>{$i_open}{$clean_date} - {$entry_text}{$i_close}</li>";
}

View File

@@ -11,7 +11,7 @@ class BrowserSearchInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Browser Search";
public string $url = "http://atravelinggeek.com/";
public array $authors = ["ATravelingGeek"=>"atg@atravelinggeek.com"];
public array $authors = ["ATravelingGeek" => "atg@atravelinggeek.com"];
public string $license = self::LICENSE_GPLV2;
public ExtensionCategory $category = ExtensionCategory::INTEGRATION;
public string $description = "Allows the user to add a browser 'plugin' to search the site with real-time suggestions";

View File

@@ -6,13 +6,13 @@ namespace Shimmie2;
class BrowserSearch extends Extension
{
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_string("search_suggestions_results_order", 'a');
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $config, $database, $page;
@@ -26,7 +26,7 @@ class BrowserSearch extends Extension
if ($event->page_matches("browser_search.xml")) {
// First, we need to build all the variables we'll need
$search_title = $config->get_string(SetupConfig::TITLE);
$search_form_url = make_link('post/list/{searchTerms}');
$search_form_url = search_link(['{searchTerms}']);
$suggenton_url = make_link('browser_search/')."{searchTerms}";
$icon_b64 = base64_encode(\Safe\file_get_contents("ext/static_files/static/favicon.ico"));
@@ -65,7 +65,7 @@ class BrowserSearch extends Extension
}
$tags = $database->get_col(
"SELECT tag FROM tags WHERE tag LIKE :tag AND count > 0 ORDER BY $order LIMIT 30",
['tag'=>$tag_search."%"]
['tag' => $tag_search."%"]
);
// And to do stuff with it. We want our output to look like:
@@ -75,7 +75,7 @@ class BrowserSearch extends Extension
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sort_by = [];
$sort_by['Alphabetical'] = 'a';

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class BrowserSearchTest extends ShimmiePHPUnitTestCase
{
public function testBasic()
public function testBasic(): void
{
$page = $this->get_page("browser_search.xml");
$this->assertEquals(200, $page->code);

View File

@@ -10,7 +10,7 @@ class BulkActionsInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Bulk Actions";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public array $authors = ["Matthew Barbour" => "matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "Provides query and selection-based bulk action support";
public ?string $documentation = "Provides bulk action section in list view. Allows performing actions against a set of posts based on query or manual selection. Based on Mass Tagger by <a href='mailto:walde.christian@googlemail.com'>Christian Walde</a>, contributions by Shish and Agasa.

View File

@@ -10,13 +10,17 @@ use Symfony\Component\Console\Output\OutputInterface;
class BulkActionBlockBuildingEvent extends Event
{
/**
* @var array<array{block:string,access_key:?string,confirmation_message:string,action:string,button_text:string,position:int}>
*/
public array $actions = [];
/** @var string[] */
public array $search_terms = [];
public function add_action(String $action, string $button_text, string $access_key = null, string $confirmation_message = "", string $block = "", int $position = 40)
public function add_action(string $action, string $button_text, string $access_key = null, string $confirmation_message = "", string $block = "", int $position = 40): void
{
if (!empty($access_key)) {
assert(strlen($access_key)==1);
assert(strlen($access_key) == 1);
foreach ($this->actions as $existing) {
if ($existing["access_key"] == $access_key) {
throw new UserError("Access key $access_key is already in use");
@@ -24,14 +28,14 @@ class BulkActionBlockBuildingEvent extends Event
}
}
$this->actions[] =[
"block" => $block,
"access_key" => $access_key,
"confirmation_message" => $confirmation_message,
"action" => $action,
"button_text" => $button_text,
"position" => $position
];
$this->actions[] = [
"block" => $block,
"access_key" => $access_key,
"confirmation_message" => $confirmation_message,
"action" => $action,
"button_text" => $button_text,
"position" => $position
];
}
}
@@ -60,7 +64,7 @@ class BulkActions extends Extension
/** @var BulkActionsTheme */
protected Themelet $theme;
public function onPostListBuilding(PostListBuildingEvent $event)
public function onPostListBuilding(PostListBuildingEvent $event): void
{
global $page, $user;
@@ -80,7 +84,7 @@ class BulkActions extends Extension
}
}
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event): void
{
global $user;
@@ -104,7 +108,7 @@ class BulkActions extends Extension
}
}
public function onCommand(CommandEvent $event)
public function onCliGen(CliGenEvent $event): void
{
$event->app->register('bulk-action')
->addArgument('action', InputArgument::REQUIRED)
@@ -120,7 +124,7 @@ class BulkActions extends Extension
});
}
public function onBulkAction(BulkActionEvent $event)
public function onBulkAction(BulkActionEvent $event): void
{
global $page, $user;
@@ -142,7 +146,7 @@ class BulkActions extends Extension
$replace = true;
}
$i= $this->tag_items($event->items, $tags, $replace);
$i = $this->tag_items($event->items, $tags, $replace);
$page->flash("Tagged $i items");
}
break;
@@ -159,7 +163,7 @@ class BulkActions extends Extension
}
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user;
if ($event->page_matches("bulk_action", method: "POST", permission: Permissions::PERFORM_BULK_ACTIONS)) {
@@ -188,29 +192,42 @@ class BulkActions extends Extension
}
}
/**
* @param int[] $data
* @return \Generator<Image>
*/
private function yield_items(array $data): \Generator
{
foreach ($data as $id) {
if (is_numeric($id)) {
$image = Image::by_id($id);
if ($image!=null) {
yield $image;
}
$image = Image::by_id($id);
if ($image != null) {
yield $image;
}
}
}
/**
* @return \Generator<Image>
*/
private function yield_search_results(string $query): \Generator
{
$tags = Tag::explode($query);
return Image::find_images_iterable(0, null, $tags);
return Search::find_images_iterable(0, null, $tags);
}
private function sort_blocks($a, $b)
/**
* @param array{position: int} $a
* @param array{position: int} $b
*/
private function sort_blocks(array $a, array $b): int
{
return $a["position"] - $b["position"];
}
/**
* @param iterable<Image> $posts
* @return array{0: int, 1: int}
*/
private function delete_posts(iterable $posts): array
{
global $page;
@@ -218,7 +235,7 @@ class BulkActions extends Extension
$size = 0;
foreach ($posts as $post) {
try {
if (class_exists("Shimmie2\ImageBan") && isset($_POST['bulk_ban_reason'])) {
if (Extension::is_enabled(ImageBanInfo::KEY) && isset($_POST['bulk_ban_reason'])) {
$reason = $_POST['bulk_ban_reason'];
if ($reason) {
send_event(new AddImageHashBanEvent($post->hash, $reason));
@@ -234,6 +251,9 @@ class BulkActions extends Extension
return [$total, $size];
}
/**
* @param iterable<Image> $items
*/
private function tag_items(iterable $items, string $tags, bool $replace): int
{
$tags = Tag::explode($tags);
@@ -274,7 +294,10 @@ class BulkActions extends Extension
return $total;
}
private function set_source(iterable $items, String $source): int
/**
* @param iterable<Image> $items
*/
private function set_source(iterable $items, string $source): int
{
global $page;
$total = 0;

View File

@@ -68,11 +68,11 @@ function get_selected_items() {
}
function set_selected_items(items) {
$(".shm-thumb").removeClass('selected');
$(".shm-thumb").removeClass('bulk_selected');
$(items).each(
function(index,item) {
$('.shm-thumb[data-post-id="' + item + '"]').addClass('selected');
$('.shm-thumb[data-post-id="' + item + '"]').addClass('bulk_selected');
}
);

View File

@@ -1,4 +1,4 @@
.selected {
.bulk_selected {
outline: 3px solid blue;
}

View File

@@ -6,7 +6,10 @@ namespace Shimmie2;
class BulkActionsTheme extends Themelet
{
public function display_selector(Page $page, array $actions, string $query)
/**
* @param array<array{block:string,access_key:?string,confirmation_message:string,action:string,button_text:string,position:int}> $actions
*/
public function display_selector(Page $page, array $actions, string $query): void
{
$body = "<input type='hidden' name='bulk_selected_ids' id='bulk_selected_ids' />
<input id='bulk_selector_activate' type='button' onclick='activate_bulk_selector();' value='Activate (M)anual Select' accesskey='m'/>
@@ -51,7 +54,7 @@ class BulkActionsTheme extends Themelet
public function render_ban_reason_input(): string
{
if (class_exists("Shimmie2\ImageBan")) {
if (Extension::is_enabled(ImageBanInfo::KEY)) {
return "<input type='text' name='bulk_ban_reason' placeholder='Ban reason (leave blank to not ban)' />";
} else {
return "";

View File

@@ -4,9 +4,14 @@ declare(strict_types=1);
namespace Shimmie2;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\{InputInterface,InputArgument};
use Symfony\Component\Console\Output\OutputInterface;
class BulkAddEvent extends Event
{
public string $dir;
/** @var UploadResult[] */
public array $results;
public function __construct(string $dir)
@@ -22,7 +27,7 @@ class BulkAdd extends Extension
/** @var BulkAddTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user;
if ($event->page_matches("bulk_add", method: "POST", permission: Permissions::BULK_ADD)) {
@@ -33,7 +38,7 @@ class BulkAdd extends Extension
}
}
public function onCommand(CommandEvent $event)
public function onCliGen(CliGenEvent $event): void
{
$event->app->register('bulk-add')
->addArgument('directory', InputArgument::REQUIRED)
@@ -52,18 +57,17 @@ class BulkAdd extends Extension
});
}
public function onAdminBuilding(AdminBuildingEvent $event)
public function onAdminBuilding(AdminBuildingEvent $event): void
{
$this->theme->display_admin_block();
}
public function onBulkAdd(BulkAddEvent $event)
public function onBulkAdd(BulkAddEvent $event): void
{
if (is_dir($event->dir) && is_readable($event->dir)) {
$event->results = add_dir($event->dir);
} else {
$h_dir = html_escape($event->dir);
$event->results[] = "Error, $h_dir is not a readable directory";
$event->results = [new UploadError($event->dir, "is not a readable directory")];
}
}
}

View File

@@ -6,18 +6,14 @@ namespace Shimmie2;
class BulkAddTest extends ShimmiePHPUnitTestCase
{
public function testInvalidDir()
public function testInvalidDir(): void
{
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$bae = send_event(new BulkAddEvent('asdf'));
$this->assertContainsEquals(
"Error, asdf is not a readable directory",
$bae->results,
implode("\n", $bae->results)
);
$this->assertTrue(is_a($bae->results[0], UploadError::class));
}
public function testValidDir()
public function testValidDir(): void
{
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
send_event(new BulkAddEvent('tests'));

View File

@@ -4,21 +4,27 @@ declare(strict_types=1);
namespace Shimmie2;
use function MicroHTML\{UL, LI};
class BulkAddTheme extends Themelet
{
private array $messages = [];
/*
/**
* Show a standard page for results to be put into
*
* @param UploadResult[] $results
*/
public function display_upload_results(Page $page)
public function display_upload_results(Page $page, array $results): void
{
$page->set_title("Adding folder");
$page->set_heading("Adding folder");
$page->add_block(new NavBlock());
$html = "";
foreach ($this->messages as $block) {
$html .= "<br/>" . $block->body;
$html = UL();
foreach ($results as $r) {
if (is_a($r, UploadError::class)) {
$html->appendChild(LI("{$r->name} failed: {$r->error}"));
} else {
$html->appendChild(LI("{$r->name} ok"));
}
}
$page->add_block(new Block("Results", $html));
}
@@ -28,7 +34,7 @@ class BulkAddTheme extends Themelet
* links to bulk_add with POST[dir] set to the name of a server-side
* directory full of images
*/
public function display_admin_block()
public function display_admin_block(): void
{
global $page;
$html = "
@@ -46,9 +52,4 @@ class BulkAddTheme extends Themelet
";
$page->add_block(new Block("Bulk Add", $html));
}
public function add_status($title, $body)
{
$this->messages[] = new Block($title, $body);
}
}

View File

@@ -10,7 +10,7 @@ class BulkDownloadInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Bulk Download";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public array $authors = ["Matthew Barbour" => "matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public ExtensionCategory $category = ExtensionCategory::FILE_HANDLING;
public string $description = "Allows bulk downloading images.";

View File

@@ -13,13 +13,13 @@ class BulkDownload extends Extension
{
private const DOWNLOAD_ACTION_NAME = "bulk_download";
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_int(BulkDownloadConfig::SIZE_LIMIT, parse_shorthand_int('100MB'));
}
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event): void
{
global $user;
@@ -28,7 +28,7 @@ class BulkDownload extends Extension
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Bulk Download");
@@ -37,14 +37,14 @@ class BulkDownload extends Extension
$sb->end_table();
}
public function onBulkAction(BulkActionEvent $event)
public function onBulkAction(BulkActionEvent $event): void
{
global $user, $page, $config;
if ($user->can(Permissions::BULK_DOWNLOAD)&&
if ($user->can(Permissions::BULK_DOWNLOAD) &&
($event->action == BulkDownload::DOWNLOAD_ACTION_NAME)) {
$download_filename = $user->name . '-' . date('YmdHis') . '.zip';
$zip_filename = tempnam(sys_get_temp_dir(), "shimmie_bulk_download");
$zip_filename = shm_tempnam("bulk_download");
$zip = new \ZipArchive();
$size_total = 0;
$max_size = $config->get_int(BulkDownloadConfig::SIZE_LIMIT);
@@ -57,7 +57,6 @@ class BulkDownload extends Extension
throw new UserError("Bulk download limited to ".human_filesize($max_size));
}
$filename = urldecode($image->get_nice_image_name());
$filename = str_replace(":", ";", $filename);
$zip->addFile($img_loc, $filename);

View File

@@ -10,7 +10,7 @@ class BulkParentChildInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Bulk Parent Child";
public array $authors = ["Flatty"=>""];
public array $authors = ["Flatty" => ""];
public string $license = self::LICENSE_WTFPL;
public string $description = "Allows bulk setting of parent-child relationships, in order of manual selection";
public array $dependencies = [BulkActionsInfo::KEY];

View File

@@ -12,7 +12,7 @@ class BulkParentChild extends Extension
{
private const PARENT_CHILD_ACTION_NAME = "bulk_parent_child";
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event): void
{
global $user;
@@ -21,10 +21,10 @@ class BulkParentChild extends Extension
}
}
public function onBulkAction(BulkActionEvent $event)
public function onBulkAction(BulkActionEvent $event): void
{
global $user, $page, $config;
if ($user->can(Permissions::BULK_PARENT_CHILD)&&
if ($user->can(Permissions::BULK_PARENT_CHILD) &&
($event->action == BulkParentChild::PARENT_CHILD_ACTION_NAME)) {
$prev_id = null;
foreach ($event->items as $image) {

View File

@@ -63,7 +63,10 @@ class Comment
#[Field]
public string $posted;
public function __construct($row)
/**
* @param array<string,mixed> $row
*/
public function __construct(array $row)
{
$this->owner = null;
$this->owner_id = (int)$row['user_id'];
@@ -84,7 +87,7 @@ class Comment
SELECT COUNT(*) AS count
FROM comments
WHERE owner_id=:owner_id
", ["owner_id"=>$user->id]);
", ["owner_id" => $user->id]);
}
#[Field(name: "owner")]
@@ -96,6 +99,9 @@ class Comment
return $this->owner;
}
/**
* @return Comment[]
*/
#[Field(extends: "Post", name: "comments", type: "[Comment!]!")]
public static function get_comments(Image $post): array
{
@@ -116,7 +122,7 @@ class CommentList extends Extension
/** @var CommentListTheme $theme */
public Themelet $theme;
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_int('comment_window', 5);
@@ -126,7 +132,7 @@ class CommentList extends Extension
$config->set_default_bool('comment_captcha', false);
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $database;
if ($this->get_version("ext_comments_version") < 3) {
@@ -179,21 +185,21 @@ class CommentList extends Extension
}
public function onPageNavBuilding(PageNavBuildingEvent $event)
public function onPageNavBuilding(PageNavBuildingEvent $event): void
{
$event->add_nav_link("comment", new Link('comment/list'), "Comments");
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
if ($event->parent=="comment") {
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)
public function onPageRequest(PageRequestEvent $event): void
{
global $cache, $config, $database, $user, $page;
if ($event->page_matches("comment/add", method: "POST", permission: Permissions::CREATE_COMMENT)) {
@@ -295,28 +301,24 @@ class CommentList extends Extension
$event->add_disallow("comment");
}
public function onAdminBuilding(AdminBuildingEvent $event)
public function onAdminBuilding(AdminBuildingEvent $event): void
{
$this->theme->display_admin_block();
}
public function onPostListBuilding(PostListBuildingEvent $event)
public function onPostListBuilding(PostListBuildingEvent $event): void
{
global $cache, $config;
$cc = $config->get_int("comment_count");
if ($cc > 0) {
$recent = $cache->get("recent_comments");
if (is_null($recent)) {
$recent = $this->get_recent_comments($cc);
$cache->set("recent_comments", $recent, 60);
}
$recent = cache_get_or_set("recent_comments", fn () => $this->get_recent_comments($cc), 60);
if (count($recent) > 0) {
$this->theme->display_recent_comments($recent);
}
}
}
public function onUserPageBuilding(UserPageBuildingEvent $event)
public function onUserPageBuilding(UserPageBuildingEvent $event): void
{
$i_days_old = ((time() - \Safe\strtotime($event->display_user->join_date)) / 86400) + 1;
$i_comment_count = Comment::count_comments_by_user($event->display_user);
@@ -327,7 +329,7 @@ class CommentList extends Extension
$this->theme->display_recent_user_comments($recent, $event->display_user);
}
public function onDisplayingImage(DisplayingImageEvent $event)
public function onDisplayingImage(DisplayingImageEvent $event): void
{
global $user;
$this->theme->display_image_comments(
@@ -338,22 +340,22 @@ class CommentList extends Extension
}
// TODO: split akismet into a separate class, which can veto the event
public function onCommentPosting(CommentPostingEvent $event)
public function onCommentPosting(CommentPostingEvent $event): void
{
$this->add_comment_wrapper($event->image_id, $event->user, $event->comment);
}
public function onCommentDeletion(CommentDeletionEvent $event)
public function onCommentDeletion(CommentDeletionEvent $event): void
{
global $database;
$database->execute("
DELETE FROM comments
WHERE id=:comment_id
", ["comment_id"=>$event->comment_id]);
", ["comment_id" => $event->comment_id]);
log_info("comment", "Deleting Comment #{$event->comment_id}");
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Comment Options");
$sb->add_bool_option("comment_captcha", "Require CAPTCHA for anonymous comments: ");
@@ -372,7 +374,7 @@ class CommentList extends Extension
$sb->add_bool_option("comment_samefags_public");
}
public function onSearchTermParse(SearchTermParseEvent $event)
public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
@@ -392,9 +394,9 @@ class CommentList extends Extension
}
}
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
public function onHelpPageBuilding(HelpPageBuildingEvent $event): void
{
if ($event->key===HelpPages::SEARCH) {
if ($event->key === HelpPages::SEARCH) {
$block = new Block();
$block->header = "Comments";
$block->body = $this->theme->get_help_html();
@@ -403,7 +405,8 @@ class CommentList extends Extension
}
/**
* #return Comment[]
* @param array<string,mixed> $args
* @return Comment[]
*/
private static function get_generic_comments(string $query, array $args): array
{
@@ -417,7 +420,7 @@ class CommentList extends Extension
}
/**
* #return Comment[]
* @return Comment[]
*/
private static function get_recent_comments(int $count): array
{
@@ -431,13 +434,13 @@ class CommentList extends Extension
LEFT JOIN users ON comments.owner_id=users.id
ORDER BY comments.id DESC
LIMIT :limit
", ["limit"=>$count]);
", ["limit" => $count]);
}
/**
* #return Comment[]
* @return Comment[]
*/
private static function get_user_comments(int $user_id, int $count, int $offset=0): array
private static function get_user_comments(int $user_id, int $count, int $offset = 0): array
{
return CommentList::get_generic_comments("
SELECT
@@ -450,12 +453,12 @@ class CommentList extends Extension
WHERE users.id = :user_id
ORDER BY comments.id DESC
LIMIT :limit OFFSET :offset
", ["user_id"=>$user_id, "offset"=>$offset, "limit"=>$count]);
", ["user_id" => $user_id, "offset" => $offset, "limit" => $count]);
}
/**
* public just for Image::get_comments()
* #return Comment[]
* @return Comment[]
*/
public static function get_comments(int $image_id): array
{
@@ -469,7 +472,7 @@ class CommentList extends Extension
LEFT JOIN users ON comments.owner_id=users.id
WHERE comments.image_id=:image_id
ORDER BY comments.id ASC
", ["image_id"=>$image_id]);
", ["image_id" => $image_id]);
}
private function is_comment_limit_hit(): bool
@@ -495,7 +498,7 @@ class CommentList extends Extension
SELECT *
FROM comments
WHERE owner_ip = :remote_ip AND posted > now() - $window_sql
", ["remote_ip"=>get_real_ip()]);
", ["remote_ip" => get_real_ip()]);
return (count($result) >= $max);
}
@@ -515,7 +518,8 @@ class CommentList extends Extension
private function is_spam_akismet(string $text): bool
{
global $config, $user;
if (strlen($config->get_string('comment_wordpress_key')) > 0) {
$key = $config->get_string('comment_wordpress_key');
if (!is_null($key) && strlen($key) > 0) {
$comment = [
'author' => $user->name,
'email' => $user->email,
@@ -527,11 +531,7 @@ class CommentList extends Extension
];
// @phpstan-ignore-next-line
$akismet = new \Akismet(
$_SERVER['SERVER_NAME'],
$config->get_string('comment_wordpress_key'),
$comment
);
$akismet = new \Akismet($_SERVER['SERVER_NAME'], $key, $comment);
// @phpstan-ignore-next-line
if ($akismet->errorsExist()) {
@@ -552,11 +552,11 @@ class CommentList extends Extension
SELECT *
FROM comments
WHERE image_id=:image_id AND comment=:comment
", ["image_id"=>$image_id, "comment"=>$comment]);
", ["image_id" => $image_id, "comment" => $comment]);
}
// do some checks
private function add_comment_wrapper(int $image_id, User $user, string $comment)
private function add_comment_wrapper(int $image_id, User $user, string $comment): void
{
global $database, $page;
@@ -567,12 +567,12 @@ class CommentList extends Extension
// all checks passed
if ($user->is_anonymous()) {
$page->add_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/");
$page->add_cookie("nocache", "Anonymous Commenter", time() + 60 * 60 * 24, "/");
}
$database->execute(
"INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ".
"VALUES(:image_id, :user_id, :remote_addr, now(), :comment)",
["image_id"=>$image_id, "user_id"=>$user->id, "remote_addr"=>get_real_ip(), "comment"=>$comment]
["image_id" => $image_id, "user_id" => $user->id, "remote_addr" => get_real_ip(), "comment" => $comment]
);
$cid = $database->get_last_insert_id('comments_id_seq');
$snippet = substr($comment, 0, 100);
@@ -581,7 +581,7 @@ class CommentList extends Extension
log_info("comment", "Comment #$cid added to >>$image_id: $snippet");
}
private function comment_checks(int $image_id, User $user, string $comment)
private function comment_checks(int $image_id, User $user, string $comment): void
{
global $config, $page;

View File

@@ -1,8 +1,8 @@
function replyTo(imageId, commentId, userId) {
var box = $("#comment_on_"+imageId);
var box = document.getElementById("comment_on_"+imageId);
var text = "[url=site://post/view/"+imageId+"#c"+commentId+"]@"+userId+"[/url]: ";
box.focus();
box.val(box.val() + text);
box.value += text;
$("#c"+commentId).highlight();
}

View File

@@ -21,7 +21,7 @@ class CommentListTest extends ShimmiePHPUnitTestCase
parent::tearDown();
}
public function testCommentsPage()
public function testCommentsPage(): void
{
global $user;
@@ -90,7 +90,7 @@ class CommentListTest extends ShimmiePHPUnitTestCase
$this->assert_no_text('ASDFASDF');
}
public function testSingleDel()
public function testSingleDel(): void
{
global $database, $user;

View File

@@ -8,12 +8,15 @@ class CommentListTheme extends Themelet
{
private bool $show_anon_id = false;
private int $anon_id = 1;
/** @var array<string,int> */
private array $anon_map = [];
/**
* Display a page with a list of images, and for each image, the image's comments.
*
* @param array<array{0: Image, 1: Comment[]}> $images
*/
public function display_comment_list(array $images, int $page_number, int $total_pages, bool $can_post)
public function display_comment_list(array $images, int $page_number, int $total_pages, bool $can_post): void
{
global $config, $page, $user;
@@ -29,7 +32,7 @@ class CommentListTheme extends Themelet
$h_prev = ($page_number <= 1) ? "Prev" :
'<a href="'.make_link('comment/list/'.$prev).'">Prev</a>';
$h_index = "<a href='".make_link("post/list")."'>Index</a>";
$h_index = "<a href='".make_link()."'>Index</a>";
$h_next = ($page_number >= $total_pages) ? "Next" :
'<a href="'.make_link('comment/list/'.$next).'">Next</a>';
@@ -56,7 +59,7 @@ class CommentListTheme extends Themelet
$comment_count = count($comments);
if ($comment_limit > 0 && $comment_count > $comment_limit) {
$comment_html .= "<p>showing $comment_limit of $comment_count comments</p>";
$comments = array_slice($comments, -$comment_limit);
$comments = array_slice($comments, negative_int($comment_limit));
$this->show_anon_id = false;
} else {
$this->show_anon_id = true;
@@ -91,7 +94,7 @@ class CommentListTheme extends Themelet
}
}
public function display_admin_block()
public function display_admin_block(): void
{
global $page;
@@ -111,9 +114,9 @@ class CommentListTheme extends Themelet
/**
* Add some comments to the page, probably in a sidebar.
*
* #param Comment[] $comments An array of Comment objects to be shown
* @param Comment[] $comments An array of Comment objects to be shown
*/
public function display_recent_comments(array $comments)
public function display_recent_comments(array $comments): void
{
global $page;
$this->show_anon_id = false;
@@ -128,9 +131,9 @@ class CommentListTheme extends Themelet
/**
* Show comments for an image.
*
* #param Comment[] $comments
* @param Comment[] $comments
*/
public function display_image_comments(Image $image, array $comments, bool $postbox)
public function display_image_comments(Image $image, array $comments, bool $postbox): void
{
global $page;
$this->show_anon_id = true;
@@ -147,9 +150,9 @@ class CommentListTheme extends Themelet
/**
* Show comments made by a user.
*
* #param Comment[] $comments
* @param Comment[] $comments
*/
public function display_recent_user_comments(array $comments, User $user)
public function display_recent_user_comments(array $comments, User $user): void
{
global $page;
$html = "";
@@ -164,7 +167,10 @@ class CommentListTheme extends Themelet
$page->add_block(new Block("Comments", $html, "left", 70, "comment-list-user"));
}
public function display_all_user_comments(array $comments, int $page_number, int $total_pages, User $user)
/**
* @param Comment[] $comments
*/
public function display_all_user_comments(array $comments, int $page_number, int $total_pages, User $user): void
{
global $page;
@@ -186,7 +192,7 @@ class CommentListTheme extends Themelet
//$query = empty($u_tags) ? "" : '/'.$u_tags;
$h_prev = ($page_number <= 1) ? "Prev" : "<a href='$prev'>Prev</a>";
$h_index = "<a href='".make_link("post/list")."'>Index</a>";
$h_index = "<a href='".make_link()."'>Index</a>";
$h_next = ($page_number >= $total_pages) ? "Next" : "<a href='$next'>Next</a>";
$page->set_title(html_escape($user->name)."'s comments");
@@ -194,7 +200,7 @@ class CommentListTheme extends Themelet
$this->display_paginator($page, "comment/beta-search/{$user->name}", null, $page_number, $total_pages);
}
protected function comment_to_html(Comment $comment, bool $trim=false): string
protected function comment_to_html(Comment $comment, bool $trim = false): string
{
global $config, $user;

View File

@@ -20,7 +20,7 @@ class CronUploaderInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Cron Uploader";
public string $url = self::SHIMMIE_URL;
public array $authors = ["YaoiFox"=>"admin@yaoifox.com", "Matthew Barbour"=>"matthew@darkholme.net"];
public array $authors = ["YaoiFox" => "admin@yaoifox.com", "Matthew Barbour" => "matthew@darkholme.net"];
public string $license = self::LICENSE_GPLV2;
public ExtensionCategory $category = ExtensionCategory::FILE_HANDLING;
public string $description = "Uploads images automatically using Cron Jobs";

View File

@@ -21,7 +21,7 @@ class CronUploader extends Extension
private static bool $IMPORT_RUNNING = false;
public function onInitUserConfig(InitUserConfigEvent $event)
public function onInitUserConfig(InitUserConfigEvent $event): void
{
$event->user_config->set_default_string(
CronUploaderConfig::DIR,
@@ -32,7 +32,7 @@ class CronUploader extends Extension
$event->user_config->set_default_int(CronUploaderConfig::LOG_LEVEL, SCORE_LOG_INFO);
}
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event): void
{
if ($event->user->can(Permissions::CRON_ADMIN)) {
$documentation_link = make_http(make_link("cron_upload"));
@@ -55,9 +55,9 @@ class CronUploader extends Extension
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
if ($event->parent=="system") {
if ($event->parent == "system") {
$event->add_nav_link("cron_docs", new Link('cron_upload'), "Cron Upload");
}
}
@@ -66,7 +66,7 @@ class CronUploader extends Extension
* Checks if the cron upload page has been accessed
* and initializes the upload.
*/
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
if ($event->page_matches("cron_upload/run")) {
$this->process_upload();
@@ -75,7 +75,7 @@ class CronUploader extends Extension
}
}
public function onAdminBuilding(AdminBuildingEvent $event)
public function onAdminBuilding(AdminBuildingEvent $event): void
{
$failed_dir = $this->get_failed_dir();
$results = get_dir_contents($failed_dir);
@@ -91,7 +91,7 @@ class CronUploader extends Extension
$this->theme->display_form($failed_dirs);
}
public function onAdminAction(AdminActionEvent $event)
public function onAdminAction(AdminActionEvent $event): void
{
$action = $event->action;
switch ($action) {
@@ -116,14 +116,14 @@ class CronUploader extends Extension
}
}
public function onLog(LogEvent $event)
public function onLog(LogEvent $event): void
{
global $user_config;
if (self::$IMPORT_RUNNING) {
$all = $user_config->get_bool(CronUploaderConfig::INCLUDE_ALL_LOGS);
if ($event->priority >= $user_config->get_int(CronUploaderConfig::LOG_LEVEL) &&
($event->section==self::NAME || $all)) {
($event->section == self::NAME || $all)) {
$output = "[" . date('Y-m-d H:i:s') . "] " . ($all ? '[' . $event->section . '] ' : '') . "[" . LOGGING_LEVEL_NAMES[$event->priority] . "] " . $event->message;
echo $output . "\r\n";
@@ -135,7 +135,7 @@ class CronUploader extends Extension
}
}
private function restage_folder(string $folder)
private function restage_folder(string $folder): void
{
global $page;
if (empty($folder)) {
@@ -153,7 +153,7 @@ class CronUploader extends Extension
$results = get_files_recursively($stage_dir);
if (count($results) == 0) {
if (remove_empty_dirs($stage_dir)===false) {
if (remove_empty_dirs($stage_dir) === false) {
$page->flash("Nothing to stage from $folder, cannot remove folder");
} else {
$page->flash("Nothing to stage from $folder, removing folder");
@@ -178,21 +178,21 @@ class CronUploader extends Extension
mkdir($dir, 0775, true);
}
if (rename($result, $new_path)===false) {
if (rename($result, $new_path) === false) {
$page->flash("Could not move file: " .$result);
$success = false;
}
}
if ($success===true) {
if ($success === true) {
$page->flash("Re-staged $folder to queue");
if (remove_empty_dirs($stage_dir)===false) {
if (remove_empty_dirs($stage_dir) === false) {
$page->flash("Could not remove $folder");
}
}
}
private function clear_folder($folder)
private function clear_folder(string $folder): void
{
global $page, $user_config;
$path = join_path($user_config->get_string(CronUploaderConfig::DIR), $folder);
@@ -321,7 +321,7 @@ class CronUploader extends Extension
{
global $database, $user, $user_config, $config, $_shm_load_start;
$max_time = intval(ini_get('max_execution_time'))*.8;
$max_time = intval(ini_get('max_execution_time')) * .8;
$this->set_headers();
@@ -361,34 +361,25 @@ class CronUploader extends Extension
// Upload the file(s)
foreach ($image_queue as $img) {
$execution_time = ftime() - $_shm_load_start;
if ($execution_time>$max_time) {
if ($execution_time > $max_time) {
break;
} else {
$remaining = $max_time - $execution_time;
$this->log_message(SCORE_LOG_DEBUG, "Max run time remaining: $remaining");
}
try {
$database->begin_transaction();
$this->log_message(SCORE_LOG_INFO, "Adding file: {$img[0]} - tags: {$img[2]}");
$result = $this->add_image($img[0], $img[1], $img[2]);
if ($database->is_transaction_open()) {
$database->commit();
}
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
$result = $database->with_savepoint(function () use ($img, $output_subdir) {
$this->log_message(SCORE_LOG_INFO, "Adding file: {$img[0]} - tags: {$img[2]}");
$result = $this->add_image($img[0], $img[1], $img[2]);
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
return $result;
});
if ($result->merged) {
$merged++;
} else {
$added++;
}
} catch (\Exception $e) {
try {
if ($database->is_transaction_open()) {
$database->rollback();
}
} catch (\Exception $e) {
// rollback failed, let's just log things and die
}
$failed++;
$this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage());
$this->log_message(SCORE_LOG_ERROR, $e->getTraceAsString());
@@ -401,7 +392,7 @@ class CronUploader extends Extension
}
// Throw exception if there's nothing in the queue
if ($merged+$failed+$added === 0) {
if ($merged + $failed + $added === 0) {
$this->log_message(SCORE_LOG_WARNING, "Your queue is empty so nothing could be uploaded.");
return false;
}
@@ -419,19 +410,19 @@ class CronUploader extends Extension
}
}
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false)
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false): void
{
global $user_config;
$rootDir = $user_config->get_string(CronUploaderConfig::DIR);
$rootLength = strlen($rootDir);
if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") {
if ($rootDir[$rootLength - 1] == "/" || $rootDir[$rootLength - 1] == "\\") {
$rootLength--;
}
$relativeDir = dirname(substr($path, $rootLength + 7));
if ($relativeDir==".") {
if ($relativeDir == ".") {
$relativeDir = "";
}
@@ -459,20 +450,22 @@ class CronUploader extends Extension
/**
* Generate the necessary DataUploadEvent for a given image and tags.
*
* @param string[] $tags
*/
private function add_image(string $tmpname, string $filename, string $tags): DataUploadEvent
private function add_image(string $tmpname, string $filename, array $tags): DataUploadEvent
{
$event = send_event(new DataUploadEvent($tmpname, basename($filename), 0, [
'tags' => Tag::implode($tags),
]));
// Generate info message
if ($event->image_id == -1) {
if (count($event->images) == 0) {
throw new UploadException("File type not recognised (".$event->mime."). Filename: {$filename}");
} elseif ($event->merged === true) {
$infomsg = "Post merged. ID: {$event->image_id} - Filename: {$filename}";
$infomsg = "Post merged. ID: {$event->images[0]->id} - Filename: {$filename}";
} else {
$infomsg = "Post uploaded. ID: {$event->image_id} - Filename: {$filename}";
$infomsg = "Post uploaded. ID: {$event->images[0]->id} - Filename: {$filename}";
}
$this->log_message(SCORE_LOG_INFO, $infomsg);
@@ -480,7 +473,7 @@ class CronUploader extends Extension
}
private const PARTIAL_DOWNLOAD_EXTENSIONS = ['crdownload','part'];
private const SKIPPABLE_FILES = ['.ds_store','thumbs.db'];
private const SKIPPABLE_FILES = ['.ds_store', 'thumbs.db', 'desktop.ini', '.listing'];
private function is_skippable_file(string $path): bool
{

View File

@@ -1,3 +1,3 @@
table.log th {
table.cron_uploader_log th {
width: 200px;
}

View File

@@ -17,6 +17,12 @@ use function MicroHTML\emptyHTML;
class CronUploaderTheme extends Themelet
{
/**
* @param array{path:string,total_files:int,total_mb:string} $queue_dirinfo
* @param array{path:string,total_files:int,total_mb:string} $uploaded_dirinfo
* @param array{path:string,total_files:int,total_mb:string} $failed_dirinfo
* @param array<array{date_sent:string,message:string}>|null $log_entries
*/
public function display_documentation(
bool $running,
array $queue_dirinfo,
@@ -25,7 +31,7 @@ class CronUploaderTheme extends Themelet
string $cron_cmd,
string $cron_url,
?array $log_entries
) {
): void {
global $page, $config, $user_config;
$info_html = "";
@@ -34,7 +40,7 @@ class CronUploaderTheme extends Themelet
$page->set_heading("Cron Uploader");
if (!$config->get_bool(UserConfig::ENABLE_API_KEYS)) {
$info_html .= "<b style='color:red'>THIS EXTENSION REQUIRES USER API KEYS TO BE ENABLED IN <a href=''>BOARD ADMIN</a></b>";
$info_html .= "<b style='color:red'>THIS EXTENSION REQUIRES USER API KEYS TO BE ENABLED IN <a href=''>BOARD ADMIN</a></b><br/>";
}
$info_html .= "<b>Information</b>
@@ -84,7 +90,7 @@ class CronUploaderTheme extends Themelet
</ol>";
$max_time = intval(ini_get('max_execution_time'))*.8;
$max_time = intval(ini_get('max_execution_time')) * .8;
$usage_html = "Upload your images you want to be uploaded to the queue directory using your FTP client or other means.
<br />(<b>{$queue_dirinfo['path']}</b>)
@@ -109,13 +115,13 @@ class CronUploaderTheme extends Themelet
$block = new Block("Cron Uploader", $info_html, "main", 10);
$block_install = new Block("Setup Guide", $install_html, "main", 30);
$block_usage= new Block("Usage Guide", $usage_html, "main", 20);
$block_usage = new Block("Usage Guide", $usage_html, "main", 20);
$page->add_block($block);
$page->add_block($block_install);
$page->add_block($block_usage);
if (!empty($log_entries)) {
$log_html = "<table class='log'>";
$log_html = "<table class='cron_uploader_log'>";
foreach ($log_entries as $entry) {
$log_html .= "<tr><th>{$entry["date_sent"]}</th><td>{$entry["message"]}</td></tr>";
}
@@ -130,28 +136,28 @@ class CronUploaderTheme extends Themelet
$form = SHM_SIMPLE_FORM(
"user_admin/cron_uploader",
TABLE(
["class"=>"form"],
["class" => "form"],
TBODY(
TR(
TH("Cron Uploader")
),
TR(
TH("Root dir"),
TD(INPUT(["type"=>'text', "name"=>'name', "required"=>true]))
TD(INPUT(["type" => 'text', "name" => 'name', "required" => true]))
),
TR(
TH(),
TD(
LABEL(INPUT(["type"=>'checkbox', "name"=>'stop_on_error']), "Stop On Error")
LABEL(INPUT(["type" => 'checkbox', "name" => 'stop_on_error']), "Stop On Error")
)
),
TR(
TH(rawHTML("Repeat&nbsp;Password")),
TD(INPUT(["type"=>'password', "name"=>'pass2', "required"=>true]))
TD(INPUT(["type" => 'password', "name" => 'pass2', "required" => true]))
)
),
TFOOT(
TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>"Save Settings"])))
TR(TD(["colspan" => "2"], INPUT(["type" => "submit", "value" => "Save Settings"])))
)
)
);
@@ -159,7 +165,10 @@ class CronUploaderTheme extends Themelet
return (string)$html;
}
public function display_form(array $failed_dirs)
/**
* @param string[] $failed_dirs
*/
public function display_form(array $failed_dirs): void
{
global $page;

View File

@@ -11,7 +11,7 @@ class CustomHtmlHeadersInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Custom HTML Headers";
public string $url = "http://www.drudexsoftware.com";
public array $authors = ["Drudex Software"=>"support@drudexsoftware.com"];
public array $authors = ["Drudex Software" => "support@drudexsoftware.com"];
public string $license = self::LICENSE_GPLV2;
public ExtensionCategory $category = ExtensionCategory::ADMIN;
public string $description = "Allows admins to modify & set custom <head> content";

View File

@@ -7,7 +7,7 @@ namespace Shimmie2;
class CustomHtmlHeaders extends Extension
{
# Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Custom HTML Headers");
@@ -25,30 +25,30 @@ class CustomHtmlHeaders extends Extension
], "<br>Add website name in title");
}
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_string("sitename_in_title", "none");
}
# Load Analytics tracking code on page request
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
$this->handle_custom_html_headers();
$this->handle_modified_page_title();
}
private function handle_custom_html_headers()
private function handle_custom_html_headers(): void
{
global $config, $page;
$header = $config->get_string('custom_html_headers', '');
if ($header!='') {
if ($header != '') {
$page->add_html_header($header);
}
}
private function handle_modified_page_title()
private function handle_modified_page_title(): void
{
global $config, $page;

View File

@@ -10,7 +10,7 @@ class DanbooruApiInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Danbooru Client API";
public array $authors = ["JJS"=>"jsutinen@gmail.com"];
public array $authors = ["JJS" => "jsutinen@gmail.com"];
public string $description = "Allow Danbooru apps like Danbooru Uploader for Firefox to communicate with Shimmie";
public ExtensionCategory $category = ExtensionCategory::INTEGRATION;
public ?string $documentation =

View File

@@ -6,18 +6,30 @@ namespace Shimmie2;
use MicroHTML\HTMLElement;
/**
* @param mixed[] ...$args
*/
function TAGS(...$args): HTMLElement
{
return new HTMLElement("tags", $args);
}
/**
* @param mixed[] ...$args
*/
function TAG(...$args): HTMLElement
{
return new HTMLElement("tag", $args);
}
/**
* @param mixed[] ...$args
*/
function POSTS(...$args): HTMLElement
{
return new HTMLElement("posts", $args);
}
/**
* @param mixed[] ...$args
*/
function POST(...$args): HTMLElement
{
return new HTMLElement("post", $args);
@@ -26,7 +38,7 @@ function POST(...$args): HTMLElement
class DanbooruApi extends Extension
{
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page;
@@ -75,6 +87,7 @@ class DanbooruApi extends Extension
$user = $duser;
} else {
$user = User::by_id($config->get_int("anon_id", 0));
assert(!is_null($user));
}
send_event(new UserLoginEvent($user));
}
@@ -101,7 +114,7 @@ class DanbooruApi extends Extension
foreach ($idlist as $id) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE id = :id",
['id'=>$id]
['id' => $id]
);
foreach ($sqlresult as $row) {
$results[] = [$row['count'], $row['tag'], $row['id']];
@@ -112,7 +125,7 @@ class DanbooruApi extends Extension
foreach ($namelist as $name) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE LOWER(tag) = LOWER(:tag)",
['tag'=>$name]
['tag' => $name]
);
foreach ($sqlresult as $row) {
$results[] = [$row['count'], $row['tag'], $row['id']];
@@ -130,7 +143,7 @@ class DanbooruApi extends Extension
$start = isset($GET['after_id']) ? int_escape($GET['offset']) : 0;
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE count > 0 AND id >= :id ORDER BY id DESC",
['id'=>$start]
['id' => $start]
);
foreach ($sqlresult as $row) {
$results[] = [$row['count'], $row['tag'], $row['id']];
@@ -200,13 +213,14 @@ class DanbooruApi extends Extension
$tags = array_filter($tags, static function ($element) {
return $element !== "*";
});
$count = Image::count_images($tags);
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
$tags = array_values($tags); // reindex array because count_images() expects a 0-based array
$count = Search::count_images($tags);
$results = Search::find_images(max($start, 0), min($limit, 100), $tags);
}
// Now we have the array $results filled with Image objects
// Let's display them
$xml = POSTS(["count"=>$count, "offset"=>$start]);
$xml = POSTS(["count" => $count, "offset" => $start]);
foreach ($results as $img) {
// Sanity check to see if $img is really an image object
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
@@ -267,8 +281,7 @@ class DanbooruApi extends Extension
*/
private function api_add_post(): void
{
global $user, $page;
$danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
global $database, $user, $page;
// Check first if a login was supplied, if it wasn't check if the user is logged in via cookie
// If all that fails, it's an anonymous upload
@@ -307,7 +320,7 @@ class DanbooruApi extends Extension
fetch_url($source, $file);
} catch (FetchException $e) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error");
$page->add_http_header("X-Danbooru-Errors: $e");
return;
}
$filename = basename($source);
@@ -322,6 +335,7 @@ class DanbooruApi extends Extension
// Was an md5 supplied? Does it match the file hash?
$hash = md5_file($file);
assert($hash !== false);
if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: md5 mismatch");
@@ -336,9 +350,7 @@ class DanbooruApi extends Extension
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id);
if ($danboorup_kludge) {
$existinglink = make_http($existinglink);
}
$existinglink = make_http($existinglink);
$page->add_http_header("X-Danbooru-Location: $existinglink");
return;
}
@@ -360,9 +372,7 @@ class DanbooruApi extends Extension
});
$newid = make_link("post/view/" . $newimg->id);
if ($danboorup_kludge) {
$newid = make_http($newid);
}
$newid = make_http($newid);
// Did we POST or GET this call?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
@@ -371,7 +381,6 @@ class DanbooruApi extends Extension
$page->add_http_header("Location: $newid");
}
} catch (UploadException $ex) {
// Did something screw up?
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
}

View File

@@ -6,20 +6,20 @@ namespace Shimmie2;
class DanbooruApiTest extends ShimmiePHPUnitTestCase
{
public function testSearch()
public function testSearch(): void
{
$this->log_in_as_admin();
$image_id = $this->post_image("tests/bedroom_workshop.jpg", "data");
$this->get_page("api/danbooru/find_posts");
$this->get_page("api/danbooru/find_posts", ["id"=>$image_id]);
$this->get_page("api/danbooru/find_posts", ["md5"=>"17fc89f372ed3636e28bd25cc7f3bac1"]);
$this->get_page("api/danbooru/find_posts", ["tags"=>"*"]);
$this->get_page("api/danbooru/find_posts", ["id" => $image_id]);
$this->get_page("api/danbooru/find_posts", ["md5" => "17fc89f372ed3636e28bd25cc7f3bac1"]);
$this->get_page("api/danbooru/find_posts", ["tags" => "*"]);
$this->get_page("api/danbooru/find_tags");
$this->get_page("api/danbooru/find_tags", ["id"=>1]);
$this->get_page("api/danbooru/find_tags", ["name"=>"data"]);
$this->get_page("api/danbooru/find_tags", ["id" => 1]);
$this->get_page("api/danbooru/find_tags", ["name" => "data"]);
$page = $this->get_page("api/danbooru/post/show/$image_id");
$this->assertEquals(302, $page->code);

View File

@@ -10,7 +10,7 @@ class DownloadInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Download";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public array $authors = ["Matthew Barbour" => "matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "System-wide download functions";
public bool $core = true;

View File

@@ -14,17 +14,13 @@ class Download extends Extension
return 99;
}
public function onImageDownloading(ImageDownloadingEvent $event)
public function onImageDownloading(ImageDownloadingEvent $event): void
{
global $page;
$page->set_mime($event->mime);
$page->set_mode(PageMode::FILE);
$page->set_file($event->path, $event->file_modified);
$event->stop_processing = true;
}
}

16
ext/download/test.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class DownloadTest extends ShimmiePHPUnitTestCase
{
public function testView(): void
{
global $page;
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$this->get_page("image/$image_id/moo.jpg");
$this->assertEquals(PageMode::FILE, $page->mode);
}
}

View File

@@ -14,14 +14,14 @@ class Downtime extends Extension
return 10;
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Downtime");
$sb->add_bool_option("downtime", "Disable non-admin access: ");
$sb->add_longtext_option("downtime_message", "<br>");
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $config, $page, $user;

View File

@@ -10,9 +10,10 @@ class DowntimeTest extends ShimmiePHPUnitTestCase
{
global $config;
$config->set_bool("downtime", false);
parent::tearDown();
}
public function testDowntime()
public function testDowntime(): void
{
global $config;

View File

@@ -9,11 +9,11 @@ class DowntimeTheme extends Themelet
/**
* Show the admin that downtime mode is enabled
*/
public function display_notification(Page $page)
public function display_notification(Page $page): void
{
$page->add_block(new Block(
"Downtime",
"<span style='font-size: 1.5em; text-align: center;'><b>DOWNTIME MODE IS ON!</b></span>",
"<span style='font-size: 1.5rem; text-align: center;'><b>DOWNTIME MODE IS ON!</b></span>",
"left",
0
));
@@ -22,7 +22,7 @@ class DowntimeTheme extends Themelet
/**
* Display $message and exit
*/
public function display_message(string $message)
public function display_message(string $message): void
{
global $config, $user, $page;
$theme_name = $config->get_string(SetupConfig::THEME);

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class EmoticonsTest extends ShimmiePHPUnitTestCase
{
public function testEmoticons()
public function testEmoticons(): void
{
global $user;

View File

@@ -12,7 +12,7 @@ class EmoticonList extends Extension
/** @var EmoticonListTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
if ($event->page_matches("emote/list")) {
$this->theme->display_emotes(\Safe\glob("ext/emoticons/default/*"));

View File

@@ -6,7 +6,10 @@ namespace Shimmie2;
class EmoticonListTheme extends Themelet
{
public function display_emotes(array $list)
/**
* @param string[] $list
*/
public function display_emotes(array $list): void
{
global $page;
$data_href = get_base_href();

View File

@@ -11,7 +11,7 @@ class Eokm extends Extension
return 40;
} // early, to veto ImageUploadEvent
public function onImageAddition(ImageAdditionEvent $event)
public function onImageAddition(ImageAdditionEvent $event): void
{
global $config;
$username = $config->get_string("eokm_username");
@@ -41,7 +41,7 @@ class Eokm extends Extension
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("EOKM Filter");

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class EokmTest extends ShimmiePHPUnitTestCase
{
public function testPass()
public function testPass(): void
{
// no EOKM login details set, so be a no-op
$this->log_in_as_user();
@@ -15,17 +15,4 @@ class EokmTest extends ShimmiePHPUnitTestCase
$this->assert_no_text("Image too small");
$this->assert_no_text("ratio");
}
/*
public function testFail()
{
$this->log_in_as_user();
try {
$this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$this->assertTrue(false, "Invalid-size image was allowed");
} catch (UploadException $e) {
$this->assertEquals("Image too small", $e->getMessage());
}
}
*/
}

View File

@@ -4,12 +4,16 @@ declare(strict_types=1);
namespace Shimmie2;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\{InputInterface,InputArgument};
use Symfony\Component\Console\Output\OutputInterface;
class ET extends Extension
{
/** @var ETTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $user;
if ($event->page_matches("system_info", permission: Permissions::VIEW_SYSINFO)) {
@@ -17,7 +21,7 @@ class ET extends Extension
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
global $user;
if ($event->parent === "system") {
@@ -27,7 +31,7 @@ class ET extends Extension
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::VIEW_SYSINFO)) {
@@ -35,19 +39,20 @@ class ET extends Extension
}
}
public function onCommand(CommandEvent $event)
public function onCliGen(CliGenEvent $event): void
{
if ($event->cmd == "help") {
print "\tinfo\n";
print "\t\tList a bunch of info\n\n";
}
if ($event->cmd == "info") {
print($this->to_yaml($this->get_info()));
}
$event->app->register('info')
->setDescription('List a bunch of info')
->setCode(function (InputInterface $input, OutputInterface $output): int {
print($this->to_yaml($this->get_info()));
return Command::SUCCESS;
});
}
/**
* Collect the information and return it in a keyed array.
*
* @return array<string, mixed>
*/
private function get_info(): array
{
@@ -81,7 +86,7 @@ class ET extends Extension
'url' => make_http(make_link("/")),
],
"versions" => [
'shimmie' => VERSION,
'shimmie' => $ver,
'schema' => $config->get_int("db_version"),
'php' => phpversion(),
'db' => $database->get_driver_id()->value . " " . $database->get_version(),
@@ -134,6 +139,9 @@ class ET extends Extension
return $info;
}
/**
* @param array<string, mixed> $info
*/
private function to_yaml(array $info): string
{
$data = "";

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class ETTest extends ShimmiePHPUnitTestCase
{
public function testET()
public function testET(): void
{
$this->log_in_as_admin();
$this->get_page("system_info");

View File

@@ -13,10 +13,8 @@ class ETTheme extends Themelet
{
/*
* Create a page showing info
*
* $info = an array of ($name => $value)
*/
public function display_info_page($yaml)
public function display_info_page(string $yaml): void
{
global $page;
@@ -26,21 +24,21 @@ class ETTheme extends Themelet
$page->add_block(new Block("Information:", $this->build_data_form($yaml)));
}
protected function build_data_form($yaml): \MicroHTML\HTMLElement
protected function build_data_form(string $yaml): \MicroHTML\HTMLElement
{
return FORM(
["action"=>"https://shimmie.shishnet.org/register.php", "method"=>"POST"],
INPUT(["type"=>"hidden", "name"=>"registration_api", "value"=>"2"]),
["action" => "https://shimmie.shishnet.org/register.php", "method" => "POST"],
INPUT(["type" => "hidden", "name" => "registration_api", "value" => "2"]),
P(
"Your stats are useful so that I know which combinations of ".
"web servers / databases / etc I need to support :)"
),
P(TEXTAREA(
["name"=>'data', "style"=>"width: 100%; height: 20em;"],
["name" => 'data', "style" => "width: 100%; height: 20em;"],
$yaml
)),
P(INPUT(
["type"=>'submit', "value"=>'Click to send to Shish', "style"=>"width: 100%; padding: 1em;"]
["type" => 'submit', "value" => 'Click to send to Shish', "style" => "width: 100%; padding: 1em;"]
)),
);
}

View File

@@ -8,7 +8,7 @@ use function MicroHTML\{PRE};
class ETServer extends Extension
{
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $database, $page, $user;
if ($event->page_matches("register.php")) {
@@ -28,7 +28,7 @@ class ETServer extends Extension
foreach ($database->get_all("SELECT responded, data FROM registration ORDER BY responded DESC") as $row) {
$page->add_block(new Block(
$row["responded"],
PRE(["style"=>"text-align: left; overflow: scroll;"], $row["data"]),
PRE(["style" => "text-align: left; overflow: scroll;"], $row["data"]),
"main",
$n++
));
@@ -37,7 +37,7 @@ class ETServer extends Extension
}
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $database;

21
ext/et_server/test.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class ETServerTest extends ShimmiePHPUnitTestCase
{
public function testView(): void
{
$this->post_page("register.php", ["data" => "test entry"]);
$this->log_in_as_user();
$this->get_page("register.php");
$this->assert_no_text("test entry");
$this->log_in_as_admin();
$this->get_page("register.php");
$this->assert_text("test entry");
}
}

View File

@@ -4,23 +4,9 @@ declare(strict_types=1);
namespace Shimmie2;
function __extman_extcmp(ExtensionInfo $a, ExtensionInfo $b): int
{
if ($a->beta===true&&$b->beta===false) {
return 1;
}
if ($a->beta===false&&$b->beta===true) {
return -1;
}
return strcmp($a->name, $b->name);
}
function __extman_extactive(ExtensionInfo $a): bool
{
return Extension::is_enabled($a->key);
}
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\{InputInterface,InputArgument};
use Symfony\Component\Console\Output\OutputInterface;
class ExtensionAuthor
{
@@ -39,7 +25,7 @@ class ExtManager extends Extension
/** @var ExtManagerTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user;
if ($event->page_matches("ext_manager/set", method: "POST", permission: Permissions::MANAGE_EXTENSION_LIST)) {
@@ -71,21 +57,20 @@ class ExtManager extends Extension
}
}
public function onCommand(CommandEvent $event)
public function onCliGen(CliGenEvent $event): void
{
if ($event->cmd == "help") {
print "\tdisable-all-ext\n";
print "\t\tdisable all extensions\n\n";
}
if ($event->cmd == "disable-all-ext") {
$this->write_config([]);
}
$event->app->register('disable-all-ext')
->setDescription('Disable all extensions')
->setCode(function (InputInterface $input, OutputInterface $output): int {
$this->write_config([]);
return Command::SUCCESS;
});
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
global $user;
if ($event->parent==="system") {
if ($event->parent === "system") {
if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
$event->add_nav_link("ext_manager", new Link('ext_manager'), "Extension Manager");
} else {
@@ -94,7 +79,7 @@ class ExtManager extends Extension
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
@@ -103,13 +88,13 @@ class ExtManager extends Extension
}
/**
* #return ExtensionInfo[]
* @return ExtensionInfo[]
*/
private function get_extensions(bool $all): array
{
$extensions = ExtensionInfo::get_all();
if (!$all) {
$extensions = array_filter($extensions, "Shimmie2\__extman_extactive");
$extensions = array_filter($extensions, fn ($x) => Extension::is_enabled($x->key));
}
usort($extensions, function ($a, $b) {
if ($a->category->name !== $b->category->name) {
@@ -123,13 +108,19 @@ class ExtManager extends Extension
return $extensions;
}
private function set_things($settings)
/**
* @param array<string, mixed> $settings
*/
private function set_things(array $settings): void
{
$core = ExtensionInfo::get_core_extensions();
$extras = [];
foreach (ExtensionInfo::get_all_keys() as $key) {
if (!in_array($key, $core) && isset($settings["ext_$key"])) {
if (in_array($key, $core)) {
continue; // core extensions are always enabled
}
if (isset($settings["ext_$key"]) && $settings["ext_$key"] === "on") {
$extras[] = $key;
}
}
@@ -138,19 +129,14 @@ class ExtManager extends Extension
}
/**
* #param string[] $extras
* @param string[] $extras
*/
private function write_config(array $extras)
private function write_config(array $extras): void
{
file_put_contents(
"data/config/extensions.conf.php",
'<' . '?php' . "\n" .
'define("EXTRA_EXTS", "' . implode(",", $extras) . '");' . "\n"
);
// when the list of active extensions changes, we can be
// pretty sure that the list of who reacts to what will
// change too
_clear_cached_event_listeners();
}
}

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class ExtManagerTest extends ShimmiePHPUnitTestCase
{
public function testAuth()
public function testAuth(): void
{
$this->get_page('ext_manager');
$this->assert_title("Extensions");

View File

@@ -25,16 +25,16 @@ use function MicroHTML\rawHTML;
class ExtManagerTheme extends Themelet
{
/**
* #param ExtensionInfo[] $extensions
* @param ExtensionInfo[] $extensions
*/
public function display_table(Page $page, array $extensions, bool $editable)
public function display_table(Page $page, array $extensions, bool $editable): void
{
$tbody = TBODY();
$form = SHM_SIMPLE_FORM(
"ext_manager/set",
TABLE(
["id"=>'extensions', "class"=>'zebra'],
["id" => 'extensions', "class" => 'zebra'],
THEAD(TR(
$editable ? TH("Enabled") : null,
TH("Name"),
@@ -42,7 +42,7 @@ class ExtManagerTheme extends Themelet
TH("Description")
)),
$tbody,
$editable ? TFOOT(TR(TD(["colspan"=>'5'], INPUT(["type"=>'submit', "value"=>'Set Extensions'])))) : null
$editable ? TFOOT(TR(TD(["colspan" => '5'], INPUT(["type" => 'submit', "value" => 'Set Extensions'])))) : null
)
);
@@ -68,18 +68,18 @@ class ExtManagerTheme extends Themelet
}
$tbody->appendChild(TR(
["data-ext"=>$extension->name],
["data-ext" => $extension->name],
$editable ? TD(INPUT([
"type"=>'checkbox',
"name"=>"ext_{$extension->key}",
"id"=>"ext_{$extension->key}",
"checked"=>($extension->is_enabled() === true),
"disabled"=>($extension->is_supported()===false || $extension->core===true)
"type" => 'checkbox',
"name" => "ext_{$extension->key}",
"id" => "ext_{$extension->key}",
"checked" => ($extension->is_enabled() === true),
"disabled" => ($extension->is_supported() === false || $extension->core === true)
])) : null,
TD(LABEL(
["for"=>"ext_{$extension->key}"],
["for" => "ext_{$extension->key}"],
(
($extension->beta===true ? "[BETA] " : "").
($extension->beta === true ? "[BETA] " : "").
(empty($extension->name) ? $extension->key : $extension->name)
)
)),
@@ -87,16 +87,16 @@ class ExtManagerTheme extends Themelet
// TODO: A proper "docs" symbol would be preferred here.
$extension->documentation ?
A(
["href"=>make_link("ext_doc/" . url_escape($extension->key))],
IMG(["src"=>'ext/ext_manager/baseline_open_in_new_black_18dp.png'])
["href" => make_link("ext_doc/" . url_escape($extension->key))],
IMG(["src" => 'ext/ext_manager/baseline_open_in_new_black_18dp.png'])
) :
null
),
TD(
["style"=>'text-align: left;'],
["style" => 'text-align: left;'],
$extension->description,
" ",
B(["style"=>'color:red'], $extension->get_support_info())
B(["style" => 'color:red'], $extension->get_support_info())
),
));
}
@@ -127,15 +127,15 @@ class ExtManagerTheme extends Themelet
$page->add_block(new Block("Extension Manager", $form));
}
public function display_doc(Page $page, ExtensionInfo $info)
public function display_doc(Page $page, ExtensionInfo $info): void
{
$author = emptyHTML();
if (count($info->authors) > 0) {
$author->appendChild(BR());
$author->appendChild(B(count($info->authors) > 1 ? "Authors: " : "Author: "));
foreach ($info->authors as $auth=>$email) {
foreach ($info->authors as $auth => $email) {
if (!empty($email)) {
$author->appendChild(A(["href"=>"mailto:$email"], $auth));
$author->appendChild(A(["href" => "mailto:$email"], $auth));
} else {
$author->appendChild($auth);
}
@@ -144,12 +144,12 @@ class ExtManagerTheme extends Themelet
}
$html = DIV(
["style"=>'margin: auto; text-align: left; width: 512px;'],
["style" => 'margin: auto; text-align: left; width: 512px;'],
$author,
($info->link ? emptyHTML(BR(), B("Home Page"), A(["href" => $info->link], "Link")) : null),
P(rawHTML($info->documentation ?? "(This extension has no documentation)")),
// <hr>,
P(A(["href"=>make_link("ext_manager")], "Back to the list"))
P(A(["href" => make_link("ext_manager")], "Back to the list"))
);
$page->set_title("Documentation for " . html_escape($info->name));

View File

@@ -10,7 +10,7 @@ class FavoritesInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Favorites";
public array $authors = ["Daniel Marschall"=>"info@daniel-marschall.de"];
public array $authors = ["Daniel Marschall" => "info@daniel-marschall.de"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Allow users to favorite images";
public ?string $documentation =

View File

@@ -27,7 +27,12 @@ class Favorites extends Extension
/** @var FavoritesTheme */
protected Themelet $theme;
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
public function onInitExt(InitExtEvent $event): void
{
Image::$prop_types["favorites"] = ImagePropType::INT;
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event): void
{
global $database, $user;
if (!$user->is_anonymous()) {
@@ -36,7 +41,7 @@ class Favorites extends Extension
$is_favorited = $database->get_one(
"SELECT COUNT(*) AS ct FROM user_favorites WHERE user_id = :user_id AND image_id = :image_id",
["user_id"=>$user_id, "image_id"=>$image_id]
["user_id" => $user_id, "image_id" => $image_id]
) > 0;
if ($is_favorited) {
@@ -47,7 +52,7 @@ class Favorites extends Extension
}
}
public function onDisplayingImage(DisplayingImageEvent $event)
public function onDisplayingImage(DisplayingImageEvent $event): void
{
$people = $this->list_persons_who_have_favorited($event->image);
if (count($people) > 0) {
@@ -55,7 +60,7 @@ class Favorites extends Extension
}
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user;
if ($user->is_anonymous()) {
@@ -76,7 +81,7 @@ class Favorites extends Extension
}
}
public function onUserPageBuilding(UserPageBuildingEvent $event)
public function onUserPageBuilding(UserPageBuildingEvent $event): void
{
$i_favorites_count = Search::count_images(["favorited_by={$event->display_user->name}"]);
$i_days_old = ((time() - \Safe\strtotime($event->display_user->join_date)) / 86400) + 1;
@@ -85,7 +90,7 @@ class Favorites extends Extension
$event->add_part("<a href='$favorites_link'>Posts favorited</a>: $i_favorites_count, $h_favorites_rate per day");
}
public function onImageInfoSet(ImageInfoSetEvent $event)
public function onImageInfoSet(ImageInfoSetEvent $event): void
{
global $user;
$action = $event->get_param("favorite_action");
@@ -98,7 +103,7 @@ class Favorites extends Extension
}
}
public function onFavoriteSet(FavoriteSetEvent $event)
public function onFavoriteSet(FavoriteSetEvent $event): void
{
global $user;
$this->add_vote($event->image_id, $user->id, $event->do_set);
@@ -106,26 +111,26 @@ class Favorites extends Extension
// FIXME: this should be handled by the foreign key. Check that it
// is, and then remove this
public function onImageDeletion(ImageDeletionEvent $event)
public function onImageDeletion(ImageDeletionEvent $event): void
{
global $database;
$database->execute("DELETE FROM user_favorites WHERE image_id=:image_id", ["image_id"=>$event->image->id]);
$database->execute("DELETE FROM user_favorites WHERE image_id=:image_id", ["image_id" => $event->image->id]);
}
public function onParseLinkTemplate(ParseLinkTemplateEvent $event)
public function onParseLinkTemplate(ParseLinkTemplateEvent $event): void
{
$event->replace('$favorites', (string)$event->image->favorites);
$event->replace('$favorites', (string)$event->image['favorites']);
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
{
global $user;
$username = url_escape($user->name);
$event->add_link("My Favorites", make_link("post/list/favorited_by=$username/1"), 20);
$event->add_link("My Favorites", search_link(["favorited_by=$username"]), 20);
}
public function onSearchTermParse(SearchTermParseEvent $event)
public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
@@ -149,21 +154,21 @@ class Favorites extends Extension
}
}
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
public function onHelpPageBuilding(HelpPageBuildingEvent $event): void
{
if ($event->key===HelpPages::SEARCH) {
if ($event->key === HelpPages::SEARCH) {
$event->add_block(new Block("Favorites", $this->theme->get_help_html()));
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
{
global $user;
if ($event->parent=="posts") {
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 ($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");
@@ -171,7 +176,7 @@ class Favorites extends Extension
}
}
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event): void
{
global $user;
@@ -181,7 +186,7 @@ class Favorites extends Extension
}
}
public function onBulkAction(BulkActionEvent $event)
public function onBulkAction(BulkActionEvent $event): void
{
global $page, $user;
@@ -209,7 +214,7 @@ class Favorites extends Extension
}
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $database;
@@ -240,30 +245,30 @@ class Favorites extends Extension
}
}
private function add_vote(int $image_id, int $user_id, bool $do_set)
private function add_vote(int $image_id, int $user_id, bool $do_set): void
{
global $database;
if ($do_set) {
if (!$database->get_row("select 1 from user_favorites where image_id=:image_id and user_id=:user_id", ["image_id"=>$image_id, "user_id"=>$user_id])) {
if (!$database->get_row("select 1 from user_favorites where image_id=:image_id and user_id=:user_id", ["image_id" => $image_id, "user_id" => $user_id])) {
$database->execute(
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(:image_id, :user_id, NOW())",
["image_id"=>$image_id, "user_id"=>$user_id]
["image_id" => $image_id, "user_id" => $user_id]
);
}
} else {
$database->execute(
"DELETE FROM user_favorites WHERE image_id = :image_id AND user_id = :user_id",
["image_id"=>$image_id, "user_id"=>$user_id]
["image_id" => $image_id, "user_id" => $user_id]
);
}
$database->execute(
"UPDATE images SET favorites=(SELECT COUNT(*) FROM user_favorites WHERE image_id=:image_id) WHERE id=:user_id",
["image_id"=>$image_id, "user_id"=>$user_id]
["image_id" => $image_id, "user_id" => $user_id]
);
}
/**
* #return string[]
* @return string[]
*/
private function list_persons_who_have_favorited(Image $image): array
{
@@ -271,7 +276,7 @@ class Favorites extends Extension
return $database->get_col(
"SELECT name FROM users WHERE id IN (SELECT user_id FROM user_favorites WHERE image_id = :image_id) ORDER BY name",
["image_id"=>$image->id]
["image_id" => $image->id]
);
}
}

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class FavoritesTest extends ShimmiePHPUnitTestCase
{
public function testFavorites()
public function testFavorites(): void
{
global $user;
$this->log_in_as_user();

View File

@@ -9,13 +9,13 @@ class Featured extends Extension
/** @var FeaturedTheme */
protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_int('featured_id', 0);
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $config, $page, $user;
if ($event->page_matches("featured_image/set/{image_id}", method: "POST", permission: Permissions::EDIT_FEATURE)) {
@@ -41,22 +41,25 @@ class Featured extends Extension
}
}
public function onPostListBuilding(PostListBuildingEvent $event)
public function onPostListBuilding(PostListBuildingEvent $event): void
{
global $cache, $config, $page, $user;
$fid = $config->get_int("featured_id");
if ($fid > 0) {
$image = $cache->get("featured_image_object:$fid");
if (is_null($image)) {
$image = Image::by_id($fid);
if ($image) { // make sure the object is fully populated before saving
$image->get_tag_array();
}
$cache->set("featured_image_object:$fid", $image, 600);
}
$image = cache_get_or_set(
"featured_image_object:$fid",
function () use ($fid) {
$image = Image::by_id($fid);
if ($image) { // make sure the object is fully populated before saving
$image->get_tag_array();
}
return $image;
},
600
);
if (!is_null($image)) {
if (Extension::is_enabled(RatingsInfo::KEY)) {
if (!in_array($image->rating, Ratings::get_user_class_privs($user))) {
if (!in_array($image['rating'], Ratings::get_user_class_privs($user))) {
return;
}
}
@@ -65,7 +68,7 @@ class Featured extends Extension
}
}
public function onImageDeletion(ImageDeletionEvent $event)
public function onImageDeletion(ImageDeletionEvent $event): void
{
global $config;
if ($event->image->id == $config->get_int("featured_id")) {
@@ -74,7 +77,7 @@ class Featured extends Extension
}
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event): void
{
global $user;
if ($user->can(Permissions::EDIT_FEATURE) && $event->context == "view") {

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class FeaturedTest extends ShimmiePHPUnitTestCase
{
public function testFeatured()
public function testFeatured(): void
{
global $config;

View File

@@ -21,16 +21,16 @@ class FeaturedTheme extends Themelet
$tsize = get_thumbnail_size($image->width, $image->height);
return DIV(
["style"=>"text-align: center;"],
["style" => "text-align: center;"],
A(
["href"=>make_link("post/view/{$image->id}", $query)],
["href" => make_link("post/view/{$image->id}", $query)],
IMG([
"id"=>"thumb_rand_{$image->id}",
"title"=>$image->get_tooltip(),
"alt"=>$image->get_tooltip(),
"class"=>'highlighted',
"style"=>"max-height: {$tsize[1]}px; max-width: 100%;",
"src"=>$image->get_thumb_link()
"id" => "thumb_rand_{$image->id}",
"title" => $image->get_tooltip(),
"alt" => $image->get_tooltip(),
"class" => 'highlighted',
"style" => "max-height: {$tsize[1]}px; max-width: 100%;",
"src" => $image->get_thumb_link()
])
)
);

View File

@@ -10,7 +10,7 @@ class ForumInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Forum";
public array $authors = ["Sein Kraft"=>"mail@seinkraft.info","Alpha"=>"alpha@furries.com.ar"];
public array $authors = ["Sein Kraft" => "mail@seinkraft.info","Alpha" => "alpha@furries.com.ar"];
public string $license = self::LICENSE_GPLV2;
public ExtensionCategory $category = ExtensionCategory::FEATURE;
public string $description = "Rough forum extension";

View File

@@ -17,7 +17,7 @@ class Forum extends Extension
/** @var ForumTheme */
protected Themelet $theme;
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
{
global $config, $database;
@@ -65,7 +65,7 @@ class Forum extends Extension
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Forum");
$sb->add_int_option("forumTitleSubString", "Title max long: ");
@@ -75,12 +75,12 @@ class Forum extends Extension
$sb->add_int_option("forumMaxCharsPerPost", "<br>Max chars per post: ");
}
public function onUserPageBuilding(UserPageBuildingEvent $event)
public function onUserPageBuilding(UserPageBuildingEvent $event): void
{
global $database;
$threads_count = $database->get_one("SELECT COUNT(*) FROM forum_threads WHERE user_id=:user_id", ['user_id'=>$event->display_user->id]);
$posts_count = $database->get_one("SELECT COUNT(*) FROM forum_posts WHERE user_id=:user_id", ['user_id'=>$event->display_user->id]);
$threads_count = $database->get_one("SELECT COUNT(*) FROM forum_threads WHERE user_id=:user_id", ['user_id' => $event->display_user->id]);
$posts_count = $database->get_one("SELECT COUNT(*) FROM forum_posts WHERE user_id=:user_id", ['user_id' => $event->display_user->id]);
$days_old = ((time() - \Safe\strtotime($event->display_user->join_date)) / 86400) + 1;
@@ -91,12 +91,12 @@ class Forum extends Extension
$event->add_part("Forum posts: $posts_count, $posts_rate per day");
}
public function onPageNavBuilding(PageNavBuildingEvent $event)
public function onPageNavBuilding(PageNavBuildingEvent $event): void
{
$event->add_nav_link("forum", new Link('forum/index'), "Forum");
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page, $user;
if ($event->page_matches("forum/index", paged: true)) {
@@ -173,63 +173,75 @@ class Forum extends Extension
private function get_total_pages_for_thread(int $threadID): int
{
global $database, $config;
$result = $database->get_row("SELECT COUNT(1) AS count FROM forum_posts WHERE thread_id = :thread_id", ['thread_id'=>$threadID]);
$result = $database->get_row("
SELECT COUNT(1) AS count
FROM forum_posts
WHERE thread_id = :thread_id
", ['thread_id' => $threadID]);
return (int) ceil($result["count"] / $config->get_int("forumPostsPerPage"));
}
/**
* @return string[]
*/
private function sanity_check_new_thread(): array
{
$errors = null;
$errors = [];
if (!array_key_exists("title", $_POST)) {
$errors .= "<div id='error'>No title supplied.</div>";
$errors[] = "No title supplied.";
} elseif (strlen($_POST["title"]) == 0) {
$errors .= "<div id='error'>You cannot have an empty title.</div>";
} elseif (strlen(html_escape($_POST["title"])) > 255) {
$errors .= "<div id='error'>Your title is too long.</div>";
$errors[] = "You cannot have an empty title.";
} elseif (strlen($_POST["title"]) > 255) {
$errors[] = "Your title is too long.";
}
if (!array_key_exists("message", $_POST)) {
$errors .= "<div id='error'>No message supplied.</div>";
$errors[] = "No message supplied.";
} elseif (strlen($_POST["message"]) == 0) {
$errors .= "<div id='error'>You cannot have an empty message.</div>";
$errors[] = "You cannot have an empty message.";
}
return [$errors];
return $errors;
}
/**
* @return string[]
*/
private function sanity_check_new_post(): array
{
$errors = null;
$errors = [];
if (!array_key_exists("threadID", $_POST)) {
$errors = "<div id='error'>No thread ID supplied.</div>";
$errors[] = "No thread ID supplied.";
} elseif (strlen($_POST["threadID"]) == 0) {
$errors = "<div id='error'>No thread ID supplied.</div>";
$errors[] = "No thread ID supplied.";
} elseif (is_numeric($_POST["threadID"])) {
if (!array_key_exists("message", $_POST)) {
$errors .= "<div id='error'>No message supplied.</div>";
$errors[] = "No message supplied.";
} elseif (strlen($_POST["message"]) == 0) {
$errors .= "<div id='error'>You cannot have an empty message.</div>";
$errors[] = "You cannot have an empty message.";
}
}
return [$errors];
return $errors;
}
/**
* @return string[]
*/
private function sanity_check_viewed_thread(int $threadID): array
{
$errors = null;
$errors = [];
if (!$this->threadExists($threadID)) {
$errors = "<div id='error'>Inexistent thread.</div>";
$errors[] = "Inexistent thread.";
}
return [$errors];
return $errors;
}
private function get_thread_title(int $threadID): string
{
global $database;
$result = $database->get_row("SELECT t.title FROM forum_threads AS t WHERE t.id = :id ", ['id'=>$threadID]);
return $result["title"];
return $database->get_one("SELECT t.title FROM forum_threads AS t WHERE t.id = :id ", ['id' => $threadID]);
}
private function show_last_threads(Page $page, int $pageNumber, bool $showAdminOptions = false): void
@@ -275,7 +287,7 @@ class Forum extends Extension
private function save_new_thread(User $user): int
{
$title = html_escape($_POST["title"]);
$title = $_POST["title"];
$sticky = !empty($_POST["sticky"]);
global $database;
@@ -285,7 +297,7 @@ class Forum extends Extension
(title, sticky, user_id, date, uptodate)
VALUES
(:title, :sticky, :user_id, now(), now())",
['title'=>$title, 'sticky'=>$sticky, 'user_id'=>$user->id]
['title' => $title, 'sticky' => $sticky, 'user_id' => $user->id]
);
$threadID = $database->get_last_insert_id("forum_threads_id_seq");
@@ -299,7 +311,7 @@ class Forum extends Extension
{
global $config;
$userID = $user->id;
$message = html_escape($_POST["message"]);
$message = $_POST["message"];
$max_characters = $config->get_int('forumMaxCharsPerPost');
$message = substr($message, 0, $max_characters);
@@ -308,32 +320,32 @@ class Forum extends Extension
$database->execute("
INSERT INTO forum_posts (thread_id, user_id, date, message)
VALUES (:thread_id, :user_id, now(), :message)
", ['thread_id'=>$threadID, 'user_id'=>$userID, 'message'=>$message]);
", ['thread_id' => $threadID, 'user_id' => $userID, 'message' => $message]);
$postID = $database->get_last_insert_id("forum_posts_id_seq");
log_info("forum", "Post {$postID} created by {$user->name}");
$database->execute("UPDATE forum_threads SET uptodate=now() WHERE id=:id", ['id'=>$threadID]);
$database->execute("UPDATE forum_threads SET uptodate=now() WHERE id=:id", ['id' => $threadID]);
}
private function delete_thread(int $threadID): void
{
global $database;
$database->execute("DELETE FROM forum_threads WHERE id = :id", ['id'=>$threadID]);
$database->execute("DELETE FROM forum_posts WHERE thread_id = :thread_id", ['thread_id'=>$threadID]);
$database->execute("DELETE FROM forum_threads WHERE id = :id", ['id' => $threadID]);
$database->execute("DELETE FROM forum_posts WHERE thread_id = :thread_id", ['thread_id' => $threadID]);
}
private function delete_post(int $postID): void
{
global $database;
$database->execute("DELETE FROM forum_posts WHERE id = :id", ['id'=>$postID]);
$database->execute("DELETE FROM forum_posts WHERE id = :id", ['id' => $postID]);
}
private function threadExists(int $threadID): bool
{
global $database;
$result=$database->get_one("SELECT EXISTS (SELECT * FROM forum_threads WHERE id=:id)", ['id'=>$threadID]);
$result = $database->get_one("SELECT EXISTS (SELECT * FROM forum_threads WHERE id=:id)", ['id' => $threadID]);
return $result == 1;
}
}

View File

@@ -4,9 +4,20 @@ declare(strict_types=1);
namespace Shimmie2;
use MicroHTML\HTMLElement;
use function MicroHTML\{INPUT, LABEL, SMALL, TEXTAREA, TR, TD, TABLE, TH, TBODY, THEAD, DIV, A, BR, emptyHTML, SUP, rawHTML};
/**
* @phpstan-type Thread array{id:int,title:string,sticky:bool,user_name:string,uptodate:string,response_count:int}
* @phpstan-type Post array{id:int,user_name:string,user_class:string,date:string,message:string}
*/
class ForumTheme extends Themelet
{
public function display_thread_list(Page $page, $threads, $showAdminOptions, $pageNumber, $totalPages)
/**
* @param Thread[] $threads
*/
public function display_thread_list(Page $page, array $threads, bool $showAdminOptions, int $pageNumber, int $totalPages): void
{
if (count($threads) == 0) {
$html = "There are no threads to show.";
@@ -23,33 +34,45 @@ class ForumTheme extends Themelet
public function display_new_thread_composer(Page $page, $threadText = null, $threadTitle = null)
public function display_new_thread_composer(Page $page, string $threadText = null, string $threadTitle = null): void
{
global $config, $user;
$max_characters = $config->get_int('forumMaxCharsPerPost');
$html = make_form(make_link("forum/create"));
if (!is_null($threadTitle)) {
$threadTitle = html_escape($threadTitle);
}
if (!is_null($threadText)) {
$threadText = html_escape($threadText);
}
$html .= "
<table style='width: 500px;'>
<tr><td>Title:</td><td><input type='text' name='title' value='$threadTitle'></td></tr>
<tr><td>Message:</td><td><textarea id='message' name='message' >$threadText</textarea></td></tr>
<tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr>";
if ($user->can(Permissions::FORUM_ADMIN)) {
$html .= "<tr><td colspan='2'><label for='sticky'>Sticky:</label><input name='sticky' id='sticky' type='checkbox' value='Y' /></td></tr>";
}
$html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table>
</form>
";
$html = SHM_SIMPLE_FORM(
"forum/create",
TABLE(
["style" => "width: 500px;"],
TR(
TD("Title:"),
TD(INPUT(["type" => "text", "name" => "title", "value" => $threadTitle]))
),
TR(
TD("Message:"),
TD(TEXTAREA(
["id" => "message", "name" => "message"],
$threadText
))
),
TR(
TD(),
TD(SMALL("Max characters allowed: $max_characters."))
),
$user->can(Permissions::FORUM_ADMIN) ? TR(
TD(),
TD(
LABEL(["for" => "sticky"], "Sticky:"),
INPUT(["name" => "sticky", "id" => "sticky", "type" => "checkbox", "value" => "Y"])
)
) : null,
TR(
TD(
["colspan" => 2],
INPUT(["type" => "submit", "value" => "Submit"])
)
)
)
);
$blockTitle = "Write a new thread";
$page->set_title(html_escape($blockTitle));
@@ -57,36 +80,43 @@ class ForumTheme extends Themelet
$page->add_block(new Block($blockTitle, $html, "main", 120));
}
public function display_new_post_composer(Page $page, $threadID)
public function display_new_post_composer(Page $page, int $threadID): void
{
global $config;
$max_characters = $config->get_int('forumMaxCharsPerPost');
$html = make_form(make_link("forum/answer"));
$html .= '<input type="hidden" name="threadID" value="'.$threadID.'" />';
$html .= "
<table style='width: 500px;'>
<tr><td>Message:</td><td><textarea id='message' name='message' ></textarea>
<tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr>
</td></tr>";
$html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table>
</form>
";
$html = SHM_SIMPLE_FORM(
"forum/answer",
INPUT(["type" => "hidden", "name" => "threadID", "value" => $threadID]),
TABLE(
["style" => "width: 500px;"],
TR(
TD("Message:"),
TD(TEXTAREA(["id" => "message", "name" => "message"]))
),
TR(
TD(),
TD(SMALL("Max characters allowed: $max_characters."))
),
TR(
TD(
["colspan" => 2],
INPUT(["type" => "submit", "value" => "Submit"])
)
)
)
);
$blockTitle = "Answer to this thread";
$page->add_block(new Block($blockTitle, $html, "main", 130));
}
public function display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber, $totalPages)
/**
* @param array<Post> $posts
*/
public function display_thread(array $posts, bool $showAdminOptions, string $threadTitle, int $threadID, int $pageNumber, int $totalPages): void
{
global $config, $page/*, $user*/;
@@ -94,68 +124,70 @@ class ForumTheme extends Themelet
$current_post = 0;
$html =
"<div id=returnLink>[<a href=".make_link("forum/index/").">Return</a>]</div><br><br>".
"<table id='threadPosts' class='zebra'>".
"<thead><tr>".
"<th id=threadHeadUser>User</th>".
"<th>Message</th>".
"</tr></thead>";
$tbody = TBODY();
foreach ($posts as $post) {
$current_post++;
$message = mb_convert_encoding($post["message"], "UTF-8", "HTML-ENTITIES");
$message = send_event(new TextFormattingEvent($message))->formatted;
$message = str_replace('\n\r', '<br>', $message);
$message = str_replace('\r\n', '<br>', $message);
$message = str_replace('\n', '<br>', $message);
$message = str_replace('\r', '<br>', $message);
$message = stripslashes($message);
$userLink = "<a href='".make_link("user/".$post["user_name"]."")."'>".$post["user_name"]."</a>";
$poster = User::by_name($post["user_name"]);
$gravatar = $poster->get_avatar_html();
$rank = "<sup class='user_rank'>{$post["user_class"]}</sup>";
$postID = $post['id'];
//if($user->can(Permissions::FORUM_ADMIN)){
//$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
//} else {
//$delete_link = "";
//}
if ($showAdminOptions) {
$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
} else {
$delete_link = "";
}
$post_number = (($pageNumber-1)*$posts_per_page)+$current_post;
$html .= "<tr >
<tr class='postHead'>
<td class='forumSupuser'></td>
<td class='forumSupmessage'><div class=deleteLink>".$delete_link."</div></td>
</tr>
<tr class='posBody'>
<td class='forumUser'>".$userLink."<br>".$rank."<br>".$gravatar."<br></td>
<td class='forumMessage'>
<div class=postDate><small>".autodate($post['date'])."</small></div>
<div class=postNumber> #".$post_number."</div>
<br>
<div class=postMessage>".$message."</td>
</tr>
<tr class='postFoot'>
<td class='forumSubuser'></td>
<td class='forumSubmessage'></td>
</tr>";
$post_number = (($pageNumber - 1) * $posts_per_page) + $current_post;
$tbody->appendChild(
emptyHTML(
TR(
["class" => "postHead"],
TD(["class" => "forumSupuser"]),
TD(
["class" => "forumSupmessage"],
DIV(
["class" => "deleteLink"],
$showAdminOptions ? A(["href" => make_link("forum/delete/".$threadID."/".$post['id'])], "Delete") : null
)
)
),
TR(
["class" => "posBody"],
TD(
["class" => "forumUser"],
A(["href" => make_link("user/".$post["user_name"])], $post["user_name"]),
BR(),
SUP(["class" => "user_rank"], $post["user_class"]),
BR(),
rawHTML(User::by_name($post["user_name"])->get_avatar_html()),
BR()
),
TD(
["class" => "forumMessage"],
DIV(["class" => "postDate"], SMALL(rawHTML(autodate($post['date'])))),
DIV(["class" => "postNumber"], " #".$post_number),
BR(),
DIV(["class" => "postMessage"], rawHTML(send_event(new TextFormattingEvent($post["message"]))->formatted))
)
),
TR(
["class" => "postFoot"],
TD(["class" => "forumSubuser"]),
TD(["class" => "forumSubmessage"])
)
)
);
}
$html .= "</tbody></table>";
$html = emptyHTML(
DIV(
["id" => "returnLink"],
A(["href" => make_link("forum/index/")], "Return")
),
BR(),
BR(),
TABLE(
["id" => "threadPosts", "class" => "zebra"],
THEAD(
TR(
TH(["id" => "threadHeadUser"], "User"),
TH("Message")
)
),
$tbody
)
);
$this->display_paginator($page, "forum/view/".$threadID, null, $pageNumber, $totalPages);
@@ -164,61 +196,55 @@ class ForumTheme extends Themelet
$page->add_block(new Block($threadTitle, $html, "main", 20));
}
public function add_actions_block(Page $page, $threadID)
public function add_actions_block(Page $page, int $threadID): void
{
$html = '<a href="'.make_link("forum/nuke/".$threadID).'">Delete this thread and its posts.</a>';
$html = A(["href" => make_link("forum/nuke/".$threadID)], "Delete this thread and its posts.");
$page->add_block(new Block("Admin Actions", $html, "main", 140));
}
private function make_thread_list($threads, $showAdminOptions): string
/**
* @param Thread[] $threads
*/
private function make_thread_list(array $threads, bool $showAdminOptions): HTMLElement
{
$html = "<table id='threadList' class='zebra'>".
"<thead><tr>".
"<th>Title</th>".
"<th>Author</th>".
"<th>Updated</th>".
"<th>Responses</th>";
global $config;
if ($showAdminOptions) {
$html .= "<th>Actions</th>";
}
$tbody = TBODY();
$html = TABLE(
["id" => "threadList", "class" => "zebra"],
THEAD(
TR(
TH("Title"),
TH("Author"),
TH("Updated"),
TH("Responses"),
$showAdminOptions ? TH("Actions") : null
)
),
$tbody
);
$html .= "</tr></thead><tbody>";
$current_post = 0;
foreach ($threads as $thread) {
$oe = ($current_post++ % 2 == 0) ? "even" : "odd";
global $config;
$titleSubString = $config->get_int('forumTitleSubString');
$title = truncate($thread["title"], $titleSubString);
if (bool_escape($thread["sticky"])) {
$sticky = "Sticky: ";
} else {
$sticky = "";
}
$html .= "<tr class='$oe'>".
'<td class="left">'.$sticky.'<a href="'.make_link("forum/view/".$thread["id"]).'">'.$title."</a></td>".
'<td><a href="'.make_link("user/".$thread["user_name"]).'">'.$thread["user_name"]."</a></td>".
"<td>".autodate($thread["uptodate"])."</td>".
"<td>".$thread["response_count"]."</td>";
if ($showAdminOptions) {
$html .= '<td><a href="'.make_link("forum/nuke/".$thread["id"]).'" title="Delete '.$title.'">Delete</a></td>';
}
$html .= "</tr>";
$tbody->appendChild(
TR(
TD(
["class" => "left"],
bool_escape($thread["sticky"]) ? "Sticky: " : "",
A(["href" => make_link("forum/view/".$thread["id"])], $title)
),
TD(
A(["href" => make_link("user/".$thread["user_name"])], $thread["user_name"])
),
TD(rawHTML(autodate($thread["uptodate"]))),
TD($thread["response_count"]),
$showAdminOptions ? TD(A(["href" => make_link("forum/nuke/".$thread["id"])], "Delete")) : null
)
);
}
$html .= "</tbody></table>";
return $html;
}
}

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class FourOhFour extends Extension
{
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $page;
// hax.
@@ -20,7 +20,10 @@ class FourOhFour extends Extension
}
}
private function count_main($blocks): int
/**
* @param Block[] $blocks
*/
private function count_main(array $blocks): int
{
$n = 0;
foreach ($blocks as $block) {

View File

@@ -6,7 +6,7 @@ namespace Shimmie2;
class FourOhFourTest extends ShimmiePHPUnitTestCase
{
public function test404Handler()
public function test404Handler(): void
{
$this->get_page('not/a/page');
// most descriptive error first

View File

@@ -11,7 +11,7 @@ class GoogleAnalyticsInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Google Analytics";
public string $url = "http://drudexsoftware.com";
public array $authors = ["Drudex Software"=>"support@drudexsoftware.com"];
public array $authors = ["Drudex Software" => "support@drudexsoftware.com"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Integrates Google Analytics tracking";
public ?string $documentation =

View File

@@ -7,7 +7,7 @@ namespace Shimmie2;
class GoogleAnalytics extends Extension
{
# Add analytics to config
public function onSetupBuilding(SetupBuildingEvent $event)
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Google Analytics");
$sb->add_text_option("google_analytics_id", "Analytics ID: ");
@@ -15,7 +15,7 @@ class GoogleAnalytics extends Extension
}
# Load Analytics tracking code on page request
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $config, $page;

View File

@@ -106,14 +106,14 @@ class GraphQL extends Extension
}
}
public function onInitExt(InitExtEvent $event)
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_string('graphql_cors_pattern', "");
$config->set_default_bool('graphql_debug', false);
}
public function onPageRequest(PageRequestEvent $event)
public function onPageRequest(PageRequestEvent $event): void
{
global $config, $page;
if ($event->page_matches("graphql")) {
@@ -125,6 +125,8 @@ class GraphQL extends Extension
]);
$t2 = ftime();
$resp = $server->executeRequest();
assert(!is_array($resp));
assert(is_a($resp, \GraphQL\Executor\ExecutionResult::class));
if ($config->get_bool("graphql_debug")) {
$debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::RETHROW_INTERNAL_EXCEPTIONS;
$body = $resp->toArray($debug);
@@ -148,6 +150,9 @@ class GraphQL extends Extension
}
}
/**
* @return array{error?:string,results?:array<array{error?:string,image_ids?:int[]}>}
*/
private static function handle_uploads(): array
{
global $user;
@@ -159,7 +164,7 @@ class GraphQL extends Extension
$metadata = only_strings($_POST);
$results = [];
for ($n=0; $n<100; $n++) {
for ($n = 0; $n < 100; $n++) {
if (empty($_POST["url$n"]) && empty($_FILES["data$n"])) {
break;
}
@@ -181,10 +186,9 @@ class GraphQL extends Extension
*/
private static function handle_upload(int $n, array $metadata): array
{
global $database;
if (!empty($_POST["url$n"])) {
return ["error" => "URLs not handled yet"];
$tmpname = "...";
$filename = "...";
throw new UploadException("URLs not handled yet");
} else {
$ec = $_FILES["data$n"]["error"];
switch ($ec) {
@@ -193,7 +197,7 @@ class GraphQL extends Extension
$filename = $_FILES["data$n"]["name"];
break;
case UPLOAD_ERR_INI_SIZE:
return ["error" => "File larger than PHP can handle"];
throw new UploadException("File larger than PHP can handle");
default:
throw new UploadException("Mystery error: ".var_export($ec, true));
}
@@ -203,10 +207,10 @@ class GraphQL extends Extension
return send_event(new DataUploadEvent($tmpname, $filename, $n, $metadata));
});
return ["image_id" => $event->image_id];
return array_map(fn ($im) => $im->id, $event->images);
}
public function onCommand(CommandEvent $event)
public function onCliGen(CliGenEvent $event): void
{
$event->app->register('graphql:query')
->addArgument('query', InputArgument::REQUIRED)

Some files were not shown because too many files have changed in this diff Show More