mirror of
https://github.com/gnosygnu/xowa.git
synced 2024-09-28 14:30:51 +00:00
XOMW: Add array_filter [#632]
This commit is contained in:
parent
a6b128422e
commit
3d74406a3e
@ -13,7 +13,9 @@ The terms of each license can be found in the source code repository:
|
||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.mediawiki; import gplx.*;
|
||||
package gplx.xowa.mediawiki;
|
||||
|
||||
import gplx.*;
|
||||
import gplx.core.strings.*;
|
||||
public class XophpArray_ {
|
||||
// REF.PHP:https://www.php.net/manual/en/function.array-merge.php
|
||||
@ -172,12 +174,12 @@ public class XophpArray_ {
|
||||
}
|
||||
|
||||
// REF.PHP:https://www.php.net/manual/en/function.array-map.php
|
||||
public static XophpArray array_map(XophpCallableOwner callback_owner, String method, XophpArray array) {
|
||||
public static XophpArray array_map(XophpCallbackOwner callback_owner, String method, XophpArray array) {
|
||||
XophpArray rv = XophpArray.New();
|
||||
int len = array.count();
|
||||
for (int i = 0; i < len; i++) {
|
||||
String itm = array.Get_at_str(i);
|
||||
rv.Add((String)callback_owner.Callback(method, itm));
|
||||
rv.Add((String)callback_owner.Call(method, itm));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@ -272,4 +274,39 @@ public class XophpArray_ {
|
||||
}
|
||||
return itms[0].Val();
|
||||
}
|
||||
|
||||
// REF.PHP: https://www.php.net/manual/en/function.array-filter.php
|
||||
public static final int ARRAY_FILTER_USE_BOTH = 1, ARRAY_FILTER_USE_KEY = 2, ARRAY_FILTER_USE_VAL = 0; // XO:USE_VAL is not PHP
|
||||
public static XophpArray array_filter(XophpArray array) {return array_filter(array, XophpArrayFilterNullCallback.Instance, 0);}
|
||||
public static XophpArray array_filter(XophpArray array, XophpCallback callback) {return array_filter(array, callback, 0);}
|
||||
public static XophpArray array_filter(XophpArray array, XophpCallback callback, int flag) {
|
||||
XophpArray rv = new XophpArray();
|
||||
int len = array.count();
|
||||
for (int i = 0; i < len; i++) {
|
||||
XophpArrayItm itm = array.Get_at_itm(i);
|
||||
boolean filter = false;
|
||||
switch (flag) {
|
||||
case ARRAY_FILTER_USE_VAL:
|
||||
filter = Bool_.Cast(callback.Call(itm.Val()));
|
||||
break;
|
||||
case ARRAY_FILTER_USE_KEY:
|
||||
filter = Bool_.Cast(callback.Call(itm.Key()));
|
||||
break;
|
||||
case ARRAY_FILTER_USE_BOTH:
|
||||
filter = Bool_.Cast(callback.Call(itm.Key(), itm.Val()));
|
||||
break;
|
||||
}
|
||||
if (filter)
|
||||
rv.Add(itm.Key(), itm.Val());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
class XophpArrayFilterNullCallback implements XophpCallbackOwner {
|
||||
public Object Call(String method, Object... args) {
|
||||
if (args.length != 1) throw new XophpRuntimeException("ArrayFilterNullCallback requires 1 arg");
|
||||
Object arg = args[0];
|
||||
return !XophpObject_.empty_obj(arg);
|
||||
}
|
||||
public static XophpCallback Instance = new XophpCallback(new XophpArrayFilterNullCallback(), "");
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
package gplx.xowa.mediawiki;
|
||||
|
||||
public class XophpCallable {
|
||||
public XophpCallable(XophpCallableOwner owner, String methodName) {
|
||||
this.owner = owner;
|
||||
this.methodName = methodName;
|
||||
}
|
||||
public XophpCallableOwner Owner() {return owner;} private final XophpCallableOwner owner;
|
||||
public String MethodName() {return methodName;} private final String methodName;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package gplx.xowa.mediawiki;
|
||||
|
||||
public class XophpCallable_ {
|
||||
// REF.PHP: https://www.php.net/manual/en/function.call-user-func-array.phphttps://www.php.net/manual/en/function.call-user-func-array.php
|
||||
public static Object call_user_func_array(XophpCallable callback, XophpArray param_arr) {
|
||||
return callback.Owner().Callback(callback.MethodName(), param_arr);
|
||||
}
|
||||
}
|
36
400_xowa/src/gplx/xowa/mediawiki/XophpCallback.java
Normal file
36
400_xowa/src/gplx/xowa/mediawiki/XophpCallback.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
XOWA: the XOWA Offline Wiki Application
|
||||
Copyright (C) 2012-2020 gnosygnu@gmail.com
|
||||
|
||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
||||
or alternatively under the terms of the Apache License Version 2.0.
|
||||
|
||||
You may use XOWA according to either of these licenses as is most appropriate
|
||||
for your project on a case-by-case basis.
|
||||
|
||||
The terms of each license can be found in the source code repository:
|
||||
|
||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.mediawiki;
|
||||
|
||||
public class XophpCallback {
|
||||
public XophpCallback(XophpCallbackOwner owner, String methodName) {
|
||||
this.owner = owner;
|
||||
this.methodName = methodName;
|
||||
}
|
||||
public XophpCallbackOwner Owner() {return owner;} private final XophpCallbackOwner owner;
|
||||
public String MethodName() {return methodName;} private final String methodName;
|
||||
public Object Call(Object arg) {
|
||||
return owner.Call(methodName, arg);
|
||||
}
|
||||
public Object Call(Object arg0, Object arg1) {
|
||||
return owner.Call(methodName, arg0, arg1);
|
||||
}
|
||||
|
||||
// REF.PHP: https://www.php.net/manual/en/function.call-user-func-array.php
|
||||
public static Object call_user_func_array(XophpCallback callback, XophpArray param_arr) {
|
||||
return callback.Owner().Call(callback.MethodName(), param_arr);
|
||||
}
|
||||
}
|
@ -14,6 +14,6 @@ GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.mediawiki;
|
||||
public interface XophpCallableOwner {
|
||||
Object Callback(String method, Object... args);
|
||||
public interface XophpCallbackOwner {
|
||||
Object Call(String method, Object... args);
|
||||
}
|
23
400_xowa/src/gplx/xowa/mediawiki/XophpClosure
Normal file
23
400_xowa/src/gplx/xowa/mediawiki/XophpClosure
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
XOWA: the XOWA Offline Wiki Application
|
||||
Copyright (C) 2012-2020 gnosygnu@gmail.com
|
||||
|
||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
||||
or alternatively under the terms of the Apache License Version 2.0.
|
||||
|
||||
You may use XOWA according to either of these licenses as is most appropriate
|
||||
for your project on a case-by-case basis.
|
||||
|
||||
The terms of each license can be found in the source code repository:
|
||||
|
||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.mediawiki;
|
||||
// REF.PHP: https://www.php.net/manual/en/class.closure.php
|
||||
public class XophpClosure {
|
||||
// public static bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] ) : Closure
|
||||
// public bindTo ( object $newthis [, mixed $newscope = "static" ] ) : Closure
|
||||
// public call ( object $newthis [, mixed $... ] ) : mixed
|
||||
// public static fromCallable ( callable $callable ) : Closure
|
||||
}
|
@ -18,8 +18,8 @@ import gplx.xowa.mediawiki.includes.XomwTitleOld;
|
||||
|
||||
public class XophpObject_ {
|
||||
public static final Object False = null; // handles code like "if ($var === false)" where var is an Object;
|
||||
public static boolean is_true(Object val) {return val != null;}
|
||||
public static boolean is_false(Object val) {return val == null;}
|
||||
public static boolean is_true(Object val) {return !empty_obj(val);}
|
||||
public static boolean is_false(Object val) {return empty_obj(val);}
|
||||
public static boolean is_null(Object val) {return val == null;}
|
||||
|
||||
// REF.PHP:http://php.net/manual/en/function.empty.php
|
||||
|
@ -19,7 +19,7 @@ import gplx.core.intls.*;
|
||||
import gplx.objects.strings.unicodes.*;
|
||||
import gplx.core.primitives.*;
|
||||
import gplx.objects.strings.bfrs.*;
|
||||
public class XophpString_ implements XophpCallableOwner {
|
||||
public class XophpString_ implements XophpCallbackOwner {
|
||||
public static final String False = null;
|
||||
public static boolean is_true (String s) {return s != null;} // handles code like "if ($var)" where var is an Object;
|
||||
public static boolean is_false(String s) {return s == null;}
|
||||
@ -564,7 +564,7 @@ public class XophpString_ implements XophpCallableOwner {
|
||||
return String_.Eq(Char_as_str(s, idx), comp);
|
||||
}
|
||||
|
||||
public Object Callback(String method, Object... args) {
|
||||
public Object Call(String method, Object... args) {
|
||||
if (String_.Eq(method, "strtoupper")) {
|
||||
String val = (String)args[0];
|
||||
return strtoupper(val);
|
||||
@ -573,5 +573,5 @@ public class XophpString_ implements XophpCallableOwner {
|
||||
throw Err_.new_unhandled_default(method);
|
||||
}
|
||||
}
|
||||
public static final XophpCallableOwner Callback_owner = new XophpString_();
|
||||
public static final XophpCallbackOwner Callback_owner = new XophpString_();
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
/*
|
||||
XOWA: the XOWA Offline Wiki Application
|
||||
Copyright (C) 2012-2017 gnosygnu@gmail.com
|
||||
|
||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
||||
or alternatively under the terms of the Apache License Version 2.0.
|
||||
|
||||
You may use XOWA according to either of these licenses as is most appropriate
|
||||
for your project on a case-by-case basis.
|
||||
|
||||
The terms of each license can be found in the source code repository:
|
||||
|
||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
/*
|
||||
XOWA: the XOWA Offline Wiki Application
|
||||
Copyright (C) 2012-2017 gnosygnu@gmail.com
|
||||
|
||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
||||
or alternatively under the terms of the Apache License Version 2.0.
|
||||
|
||||
You may use XOWA according to either of these licenses as is most appropriate
|
||||
for your project on a case-by-case basis.
|
||||
|
||||
The terms of each license can be found in the source code repository:
|
||||
|
||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
|
||||
public class XophpType_ {
|
||||
public static int To_type_id(Object o) {
|
||||
@ -51,4 +51,6 @@ public class XophpType_ {
|
||||
public static boolean instance_of(Object o, Class<?> t) {
|
||||
return Type_.Eq_by_obj(o, t) || Type_.Is_assignable_from_by_obj(o, t);
|
||||
}
|
||||
|
||||
public static Class<?> get_class(Object o) {return o.getClass();}
|
||||
}
|
||||
|
@ -14,15 +14,12 @@ GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.mediawiki.includes;
|
||||
// MW.SRC:1.33.1
|
||||
|
||||
import gplx.core.primitives.String_obj_ref;
|
||||
import gplx.xowa.mediawiki.*;
|
||||
/*
|
||||
TODO:
|
||||
* class XophpClosure: https://www.php.net/manual/en/class.closure.php
|
||||
* array_filter: https://www.php.net/manual/en/function.array-filter.php
|
||||
*/
|
||||
import gplx.xowa.mediawiki.includes.exception.XomwMWException;
|
||||
|
||||
// MW.SRC:1.33.1
|
||||
/**
|
||||
* Hooks class.
|
||||
*
|
||||
@ -45,7 +42,7 @@ public class XomwHooks {
|
||||
*
|
||||
* @since 1.18
|
||||
*/
|
||||
public static void register(String name, XophpCallable callback) {
|
||||
public static void register(String name, XophpCallback callback) {
|
||||
handlers.Get_by_ary(name).Add(callback);
|
||||
}
|
||||
|
||||
@ -112,7 +109,7 @@ public class XomwHooks {
|
||||
private static String callHook(String event, Object hookObj, XophpArray args) {return callHook(event, hookObj, args, null, null);}
|
||||
private static String callHook(String event, Object hookObj, XophpArray args, String deprecatedVersion) {return callHook(event, hookObj, args, deprecatedVersion, null);}
|
||||
private static String callHook(String event, Object hookObj, XophpArray args, String deprecatedVersion,
|
||||
String fname
|
||||
String_obj_ref fname
|
||||
) {
|
||||
XophpArray hook;
|
||||
// Turn non-array values into an array. (Can't use casting because of objects.)
|
||||
@ -123,59 +120,59 @@ public class XomwHooks {
|
||||
hook = (XophpArray)hookObj;
|
||||
}
|
||||
|
||||
// if (!array_filter(hook)) {
|
||||
// Either array is empty or it's an array filled with null/false/empty.
|
||||
// return null;
|
||||
// }
|
||||
if (!XophpObject_.is_true(hook)) {
|
||||
// Either array is empty or it's an array filled with null/false/empty.
|
||||
return null;
|
||||
}
|
||||
|
||||
// if (is_array(hook[0])) {
|
||||
if (XophpArray_.is_array(hook.Get_at(0))) {
|
||||
// First element is an array, meaning the developer intended
|
||||
// the first element to be a callback. Merge it in so that
|
||||
// processing can be uniform.
|
||||
// hook = array_merge(hook[0], array_slice(hook, 1));
|
||||
// }
|
||||
hook = XophpArray_.array_merge(hook.Get_at_ary(0), XophpArray_.array_slice(hook, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* hook can be: a function, an object, an array of $function and
|
||||
* $data, an array of just a function, an array of object and
|
||||
* method, or an array of object, method, and data.
|
||||
*/
|
||||
if (XophpType_.instance_of(hook.Get_at(0), XophpCallable.class)) { // XophpClosure
|
||||
// $fname = "hook-" + event + "-closure";
|
||||
// $callback = array_shift(hook);
|
||||
XophpCallback callback = null;
|
||||
if (XophpType_.instance_of(hook.Get_at(0), XophpCallback.class)) { // XophpClosure
|
||||
if (fname != null) fname.Val_("hook-" + event + "-closure");
|
||||
callback = (XophpCallback) XophpArray_.array_shift(hook);
|
||||
} else if (XophpObject_.is_object(hook.Get_at_str(0))) {
|
||||
// Object object = XophpArray_.array_shift(hook);
|
||||
// Object method = XophpArray_.array_shift(hook);
|
||||
XophpCallbackOwner object = (XophpCallbackOwner)XophpArray_.array_shift(hook);
|
||||
String method = (String)XophpArray_.array_shift(hook);
|
||||
|
||||
// If no method was specified, default to on$event.
|
||||
// if ($method === null) {
|
||||
// $method = "on" + event;
|
||||
// }
|
||||
if (XophpObject_.is_null(method)) {
|
||||
method = "on" + event;
|
||||
}
|
||||
|
||||
// $fname = get_class($object) . '::' . $method;
|
||||
// $callback = [ $object, $method ];
|
||||
// } else if (is_string(hook[0])) {
|
||||
// $fname = $callback = array_shift(hook);
|
||||
// } else {
|
||||
// throw new MWException('Unknown datatype in hooks for ' . $event . "\n");
|
||||
if (fname != null) fname.Val_(XophpType_.get_class(object).getName() + "::" + method);
|
||||
callback = new XophpCallback(object, method);
|
||||
} else if (XophpString_.is_string(hook.Get_at(0))) {
|
||||
throw new XomwMWException("XOMW does not support string callbacks! Should not have been passed here!; event=" + event + "; fname=" + XophpArray_.array_shift(hook) + "\n");
|
||||
} else {
|
||||
throw new XomwMWException("Unknown datatype in hooks for " + event + "\n");
|
||||
}
|
||||
|
||||
// XOMW:skip as callback already strongly-typed above
|
||||
// Run autoloader (workaround for call_user_func_array bug)
|
||||
// and throw error if not callable.
|
||||
// if (!is_callable($callback)) {
|
||||
// throw new MWException('Invalid callback ' . $fname . ' in hooks for ' . $event . "\n");
|
||||
// }
|
||||
// if (!is_callable($callback)) {
|
||||
// throw new MWException('Invalid callback ' . $fname . ' in hooks for ' . $event . "\n");
|
||||
// }
|
||||
|
||||
// mark hook as deprecated, if deprecation version is specified
|
||||
if (deprecatedVersion != null) {
|
||||
// wfDeprecated("$event hook (used in $fname)", deprecatedVersion);
|
||||
}
|
||||
|
||||
XophpCallable callback = null;
|
||||
|
||||
// Call the hook.
|
||||
XophpArray hook_args = XophpArray_.array_merge(hook, args);
|
||||
return (String)XophpCallable_.call_user_func_array(callback, hook_args);
|
||||
return (String) XophpCallback.call_user_func_array(callback, hook_args);
|
||||
}
|
||||
/**
|
||||
* Call hook functions defined in Hooks::register and $wgHooks.
|
||||
@ -205,7 +202,7 @@ public class XomwHooks {
|
||||
public static boolean run(String event, XophpArray args, String deprecatedVersion) {
|
||||
XophpArray handlers = getHandlers(event);
|
||||
for (int i = 0; i < handlers.count(); i++) {
|
||||
XophpCallable hook = (XophpCallable)handlers.Get_at(i);
|
||||
XophpCallback hook = (XophpCallback)handlers.Get_at(i);
|
||||
Object retval = callHook(event, hook, args, deprecatedVersion);
|
||||
if (retval == null) {
|
||||
continue;
|
||||
|
@ -16,6 +16,9 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
package gplx.xowa.mediawiki;
|
||||
|
||||
import gplx.Bool_;
|
||||
import gplx.Err_;
|
||||
import gplx.Int_;
|
||||
import gplx.String_;
|
||||
import gplx.core.tests.Gftest;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -263,7 +266,7 @@ public class XophpArray__tst {
|
||||
);
|
||||
}
|
||||
@Test public void in_array() {
|
||||
// PHP samples
|
||||
// PHP examples
|
||||
XophpArray array;
|
||||
// Example #1
|
||||
array = XophpArray.New("Mac", "NT", "Irix", "Linux");
|
||||
@ -322,7 +325,7 @@ public class XophpArray__tst {
|
||||
Gftest.Eq__bool_y(shifted == null);
|
||||
Gftest.Eq__bool_y(array == null);
|
||||
|
||||
// PHP samples
|
||||
// PHP examples
|
||||
// Example #1
|
||||
array = XophpArray.New("orange", "banana", "apple", "strawberry");
|
||||
shifted = (String)XophpArray_.array_shift(array);
|
||||
@ -333,6 +336,41 @@ public class XophpArray__tst {
|
||||
, array
|
||||
);
|
||||
}
|
||||
@Test public void array_filter() {
|
||||
// PHP examples
|
||||
// Example #1 array_filter() example
|
||||
XophpArrayTestCallbackOwner callbackOwner = new XophpArrayTestCallbackOwner();
|
||||
XophpArray array;
|
||||
array = XophpArray.New().Add("a", 1).Add("b", 2).Add("c", 3).Add("d", 4).Add("e", 5);
|
||||
fxt.Test__eq
|
||||
( XophpArray.New().Add("a", 1).Add("c", 3).Add("e", 5)
|
||||
, XophpArray_.array_filter(array, callbackOwner.NewCallback("array_filter_odd"))
|
||||
);
|
||||
|
||||
array = XophpArray.New(6, 7, 8, 9, 10, 11, 12);
|
||||
fxt.Test__eq
|
||||
( XophpArray.New().Add(0, 6).Add(2, 8).Add(4, 10).Add(6, 12)
|
||||
, XophpArray_.array_filter(array, callbackOwner.NewCallback( "array_filter_even"))
|
||||
);
|
||||
|
||||
// Example #2 array_filter() without callback
|
||||
array = XophpArray.New().Add(0, "foo").Add(1, false).Add(2, -1).Add(3, null).Add(4, "").Add(5, "0").Add(6, 0);
|
||||
fxt.Test__eq
|
||||
( XophpArray.New().Add(0, "foo").Add(2, -1)
|
||||
, XophpArray_.array_filter(array)
|
||||
);
|
||||
|
||||
// Example #3 array_filter() with flag
|
||||
array = XophpArray.New().Add("a", 1).Add("b", 2).Add("c", 3).Add("d", 4);
|
||||
fxt.Test__eq
|
||||
( XophpArray.New().Add("b", 2)
|
||||
, XophpArray_.array_filter(array, callbackOwner.NewCallback("array_filter_key"), XophpArray_.ARRAY_FILTER_USE_KEY)
|
||||
);
|
||||
fxt.Test__eq
|
||||
( XophpArray.New().Add("b", 2).Add("d", 4)
|
||||
, XophpArray_.array_filter(array, callbackOwner.NewCallback("array_filter_both"), XophpArray_.ARRAY_FILTER_USE_BOTH)
|
||||
);
|
||||
}
|
||||
}
|
||||
class XophpArray__fxt {
|
||||
public XophpArray Make() {return new XophpArray();}
|
||||
@ -340,3 +378,20 @@ class XophpArray__fxt {
|
||||
Gftest.Eq__str(expd.To_str(), actl.To_str());
|
||||
}
|
||||
}
|
||||
class XophpArrayTestCallbackOwner implements XophpCallbackOwner {
|
||||
public XophpCallback NewCallback(String method) {return new XophpCallback(this, method);}
|
||||
public Object Call(String method, Object... args) {
|
||||
switch (method) {
|
||||
case "array_filter_even":
|
||||
return Int_.Cast(args[0]) % 2 == 0;
|
||||
case "array_filter_odd":
|
||||
return Int_.Cast(args[0]) % 2 == 1;
|
||||
case "array_filter_key":
|
||||
return String_.cast(args[0]).equals("b");
|
||||
case "array_filter_both":
|
||||
return String_.cast(args[0]).equals("b") || Int_.Cast(args[1]) == 4;
|
||||
default:
|
||||
throw Err_.new_unhandled_default(method);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user