resolve merge conflict

This commit is contained in:
butterbutt 2020-10-02 04:28:59 -05:00
commit 3bf111603f
Signed by: butterbutt
GPG Key ID: 4D2AD715BC930E49
13 changed files with 445 additions and 131 deletions

View File

@ -0,0 +1,7 @@
<?php
abstract class AApiController
{
abstract public function process(array $args);
}

View File

@ -0,0 +1,77 @@
<?php
require_once "a_api_controller.php";
require_once "tags_controller.php";
class ApiImagesController extends AApiController
{
public function process(array $args)
{
$fourth_arg = @$args[3];
if (!is_numeric($fourth_arg)) {
// 4th arg should always be a number
return;
}
$id = intval($fourth_arg);
$image = Image::by_id($id);
if ($image===null) {
throw new SCoreException("Image $id not found");
}
if (@$args[4]==="tags") {
switch ($_SERVER['REQUEST_METHOD']) {
case "GET":
ApiTagsController::get_for_image_id($id);
break;
case "POST":
if (empty($_GET["tags"])) {
return;
}
$this->set_tags($image, json_decode($_GET["tags"]), false);
ApiTagsController::get_for_image_id($id);
break;
}
} else {
$this->search_tags();
}
}
private function set_tags(Image $image, array $tags, bool $replace)
{
global $user;
if ($user->can(Permissions::EDIT_IMAGE_TAG)) {
$tags = Tag::explode(implode(" ", $tags));
$tags = Tag::sanitize_array($tags);
$pos_tag_array = [];
$neg_tag_array = [];
foreach ($tags as $new_tag) {
if (strpos($new_tag, '-') === 0) {
$neg_tag_array[] = substr($new_tag, 1);
} else {
$pos_tag_array[] = $new_tag;
}
}
if ($replace) {
send_event(new TagSetEvent($image, $tags));
} else {
$img_tags = array_map("strtolower", $image->get_tag_array());
if (!empty($neg_tag_array)) {
$neg_tag_array = array_map("strtolower", $neg_tag_array);
$img_tags = array_merge($pos_tag_array, $img_tags);
$img_tags = array_diff($img_tags, $neg_tag_array);
} else {
$img_tags = array_merge($tags, $img_tags);
}
send_event(new TagSetEvent($image, $img_tags));
}
}
}
}

View File

@ -0,0 +1,120 @@
<?php
require_once "a_api_controller.php";
class ApiTagsController extends AApiController
{
public function process(array $args)
{
if (@$args[3]==="by_image") {
$this->get_for_image();
} else {
$this->search_tags();
}
}
public static function prepare_output(PDOStatement $input): string
{
$output = [];
$output["tags"] = [];
foreach ($input as $item) {
$output["tags"][] = ["id"=>$item["id"], "count"=>$item["count"], "tag"=>$item["tag"]];
}
return json_encode($output);
}
private function get_for_image()
{
if (!isset($_GET["id"])) {
return;
}
$id = intval($_GET["id"]);
self::get_for_image_id($id);
}
public static function get_for_image_id($id)
{
global $database, $page, $cache;
$page->set_mode(PageMode::DATA);
$page->set_mime(MimeType::JSON);
$cache_key = "api_tags_by_image-$id";
$SQLarr = ["id"=>$id];
$res = $cache->get($cache_key);
if (!$res) {
$res = $database->get_all_iterable(
$database->scoreql_to_sql("
SELECT *
FROM tags t
INNER JOIN image_tags it ON t.id = it.tag_id AND it.image_id = :id
ORDER BY count DESC"),
$SQLarr
);
$cache->set($cache_key, $res, 600);
}
$page->set_data(self::prepare_output($res));
}
private function search_tags()
{
global $database, $page, $cache;
if (!isset($_GET["query"])) {
return;
}
$page->set_mode(PageMode::DATA);
$page->set_mime(MimeType::JSON);
$s = strtolower($_GET["query"]);
if (
$s == '' ||
$s[0] == '_' ||
$s[0] == '%' ||
strlen($s) > 32
) {
$page->set_data("{}");
return;
}
//$limit = 0;
$cache_key = "api_tags_search-$s";
$limitSQL = "";
$searchSQL = "LOWER(tag) LIKE LOWER(:search)";
$s = str_replace('_', '\_', $s);
$s = str_replace('%', '\%', $s);
$SQLarr = ["search"=>"$s%"];
if (isset($_GET["search_categories"]) && $_GET["search_categories"] == "true") {
$searchSQL .= " OR LOWER(tag) LIKE LOWER(:cat_search)";
$SQLarr['cat_search'] = "%:$s%";
$cache_key .= "+cat";
}
if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
$limitSQL = "LIMIT :limit";
$SQLarr['limit'] = $_GET["limit"];
$cache_key .= "-" . $_GET["limit"];
}
$res = $cache->get($cache_key);
if (!$res) {
$res = $database->get_all_iterable(
"
SELECT *
FROM tags
WHERE $searchSQL
AND count > 0
ORDER BY count DESC
$limitSQL",
$SQLarr
);
$cache->set($cache_key, $res, 600);
}
$page->set_data(self::prepare_output($res));
}
}

19
ext/api_internal/info.php Normal file
View File

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
/*
* Name: Autocomplete
* Author: Daku <admin@codeanimu.net>
* Description: Adds autocomplete to search & tagging.
*/
class ApiInternalInfo extends ExtensionInfo
{
public const KEY = "api_internal";
public $key = self::KEY;
public $name = "Internal API";
public $authors = ["Daku"=>"admin@codeanimu.net", "Matthew Barbour"=>"matthew@darkholme.net"];
public $description = "Dependency extension used to provide a standardized source for performing operations via an API";
public $visibility = self::VISIBLE_HIDDEN;
}

53
ext/api_internal/main.php Normal file
View File

@ -0,0 +1,53 @@
<?php
require_once "controllers/tags_controller.php";
require_once "controllers/images_controller.php";
class ApiInternal extends Extension
{
private const INTERNAL_API = "api/internal/";
public const TAGS_API_PATH = self::INTERNAL_API."tags/";
public const TAGS_BY_IMAGE_PATH = self::TAGS_API_PATH."by_image";
public const IMAGE_API_PATH = self::INTERNAL_API."image/";
private $tags;
private $images;
public function __construct($class = null)
{
parent::__construct();
$this->tags = new ApiTagsController();
$this->images = new ApiImagesController();
}
public function get_priority(): int
{
return 30;
} // before Home
public function onPageRequest(PageRequestEvent $event)
{
if ($event->args[0]==="api"&&$event->args[1]==="internal") {
$controller_name = $event->args[2];
$controller = null;
switch ($controller_name) {
case "tags":
$controller = $this->tags;
break;
case "image":
$controller = $this->images;
break;
}
if ($controller!==null) {
$controller->process($event->args);
} else {
throw new SCoreException("Controller not found for $controller_name");
}
}
}
}

View File

@ -6,9 +6,9 @@ class AutoCompleteTest extends ShimmiePHPUnitTestCase
public function testAuth()
{
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/tags/search', ["query"=>"not-a-tag"]);
$this->assertEquals(200, $page->code);
$this->assertEquals(PageMode::DATA, $page->mode);
$this->assertEquals("[]", $page->data);
$this->assertEquals('{"tags":[]}', $page->data);
}
}

View File

@ -6,6 +6,7 @@ class AutoCompleteInfo extends ExtensionInfo
public $key = self::KEY;
public $name = "Autocomplete";
public $authors = ["Daku"=>"admin@codeanimu.net"];
public $authors = ["Daku"=>"admin@codeanimu.net", "Matthew Barbour"=>"matthew@darkholme.net"];
public $description = "Adds autocomplete to search & tagging.";
public $dependencies = [ApiInternalInfo::KEY];
}

View File

@ -1,5 +1,15 @@
<?php declare(strict_types=1);
class AutoCompleteConfig
{
public const SEARCH_LIMIT = "autocomplete_search_limit";
public const SEARCH_CATEGORIES = "autocomplete_search_categories";
public const NAVIGATION = "autocomplete_navigation";
public const TAGGING = "autocomplete_tagging";
}
class AutoComplete extends Extension
{
/** @var AutoCompleteTheme */
@ -10,60 +20,24 @@ class AutoComplete extends Extension
return 30;
} // before Home
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_bool(AutoCompleteConfig::NAVIGATION, true);
$config->set_default_bool(AutoCompleteConfig::TAGGING, false);
$config->set_default_int(AutoCompleteConfig::SEARCH_LIMIT, 20);
$config->set_default_bool(AutoCompleteConfig::SEARCH_CATEGORIES, false);
}
public function onPageRequest(PageRequestEvent $event)
{
global $cache, $page, $database;
if ($event->page_matches("api/internal/autocomplete")) {
if (!isset($_GET["s"])) {
return;
}
$page->set_mode(PageMode::DATA);
$page->set_mime(MimeType::JSON);
$s = strtolower($_GET["s"]);
if (
$s == '' ||
$s[0] == '_' ||
$s[0] == '%' ||
strlen($s) > 32
) {
$page->set_data("{}");
return;
}
//$limit = 0;
$cache_key = "autocomplete-$s";
$limitSQL = "";
$s = str_replace('_', '\_', $s);
$s = str_replace('%', '\%', $s);
$SQLarr = ["search"=>"$s%", "cat_search"=>"%:$s%"];
if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
$limitSQL = "LIMIT :limit";
$SQLarr['limit'] = $_GET["limit"];
$cache_key .= "-" . $_GET["limit"];
}
$res = $cache->get($cache_key);
if (!$res) {
$res = $database->get_pairs(
"
SELECT tag, count
FROM tags
WHERE LOWER(tag) LIKE LOWER(:search)
OR LOWER(tag) LIKE LOWER(:cat_search)
AND count > 0
ORDER BY count DESC
$limitSQL",
$SQLarr
);
$cache->set($cache_key, $res, 600);
}
$page->set_data(json_encode($res));
}
global $page;
$this->theme->build_autocomplete($page);
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
$this->theme->display_admin_block($event);
}
}

View File

@ -1,7 +1,7 @@
document.addEventListener('DOMContentLoaded', () => {
var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename'];
var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename'];
$('[name="search"]').tagit({
function enableTagAutoComplete(element, limit, search_categories) {
$(element).tagit({
singleFieldDelimiter: ' ',
beforeTagAdded: function(event, ui) {
if(metatags.indexOf(ui.tagLabel) !== -1) {
@ -32,23 +32,30 @@ document.addEventListener('DOMContentLoaded', () => {
);
var isNegative = (request.term[0] === '-');
var requestData = {
'query': (isNegative ? request.term.substring(1) : request.term),
'limit': limit,
'search_categories': search_categories
};
$.ajax({
url: base_href + '/api/internal/autocomplete',
data: {'s': (isNegative ? request.term.substring(1) : request.term)},
url: base_href + '/api/internal/tags/search',
data: requestData,
dataType : 'json',
type : 'GET',
success : function (data) {
response(
$.merge(ac_metatags,
$.map(data, function (count, item) {
item = (isNegative ? '-'+item : item);
var output = $.merge(ac_metatags,
$.map(data.tags, function (item, i) {
console.log(item);
var tag = (isNegative ? '-'+item.tag : item.tag);
return {
label : item + ' ('+count+')',
value : item
label : tag + ' ('+item.count+')',
value : tag
};
})
)
);
response(output);
},
error : function (request, status, error) {
console.log(error);
@ -58,8 +65,7 @@ document.addEventListener('DOMContentLoaded', () => {
minLength: 1
})
});
$('.ui-autocomplete-input').keydown(function(e) {
$(element).find('.ui-autocomplete-input').keydown(function(e) {
var keyCode = e.keyCode || e.which;
//Stop tags containing space.
@ -78,4 +84,4 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
});
});
}

View File

@ -7,3 +7,8 @@ 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; }
ul.tagit {
margin:0;
}

View File

@ -2,8 +2,22 @@
class AutoCompleteTheme extends Themelet
{
public static function generate_autocomplete_enable_script(string $selector)
{
global $config;
$limit = $config->get_int(AutoCompleteConfig::SEARCH_LIMIT);
$search_categories = $config->get_bool(AutoCompleteConfig::SEARCH_CATEGORIES) ? "'true'" : "'false'";
return "enableTagAutoComplete($('$selector'),$limit,$search_categories);";
}
public function build_autocomplete(Page $page)
{
global $config;
$base_href = get_base_href();
// TODO: AJAX test and fallback.
@ -11,5 +25,41 @@ class AutoCompleteTheme extends Themelet
$page->add_html_header("<script defer src='$base_href/ext/autocomplete/lib/tag-it.min.js' type='text/javascript'></script>");
$page->add_html_header('<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">');
$page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/autocomplete/lib/jquery.tagit.css' />");
$scripts = "";
if ($config->get_bool(AutoCompleteConfig::NAVIGATION)) {
$scripts .= self::generate_autocomplete_enable_script('.autocomplete_tags[name="search"]');
}
if ($config->get_bool(AutoCompleteConfig::TAGGING)) {
$scripts .= self::generate_autocomplete_enable_script('.autocomplete_tags[name^="tags"], .autocomplete_tags[name="bulk_tags"], .autocomplete_tags[name="tag_edit__tags"]');
}
$b = new Block(
null,
"<script type='text/javascript'>
document.addEventListener('DOMContentLoaded', () => {
// Autocomplete initializations
$scripts
});
</script>",
"main",
1000
);
$b->is_content = false;
$page->add_block($b);
}
public function display_admin_block(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Autocomplete");
$sb->start_table();
$sb->add_int_option(AutoCompleteConfig::SEARCH_LIMIT, "Search Limit", true);
$sb->add_bool_option(AutoCompleteConfig::SEARCH_CATEGORIES, "Search Categories", true);
$sb->add_bool_option(AutoCompleteConfig::NAVIGATION, "Enable For Navigation", true);
$sb->add_bool_option(AutoCompleteConfig::TAGGING, "Enable For Tagging", true);
$sb->end_table();
$event->panel->add_block($sb);
}
}

View File

@ -57,7 +57,7 @@ class BulkActionsTheme extends Themelet
public function render_tag_input()
{
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
"<input type='text' name='bulk_tags' required='required' placeholder='Enter tags here' />";
"<input type='text' name='bulk_tags' class='autocomplete_tags' required='required' placeholder='Enter tags here' />";
}
public function render_source_input()

View File

@ -53,7 +53,9 @@ class TagEditTheme extends Themelet
<td>
".($user->can(Permissions::EDIT_IMAGE_TAG) ? "
<span class='view'>$h_tag_links</span>
<input class='edit autocomplete_tags' type='text' name='tag_edit__tags' value='$h_tags' id='tag_editor' autocomplete='off'>
<div class='edit'>
<input class='autocomplete_tags' type='text' name='tag_edit__tags' value='$h_tags' autocomplete='off'>
</div>
" : "
$h_tag_links
")."