├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .htaccess
├── Classes
├── class-checkup.php
├── class-cmodes.php
├── class-hook.php
├── class-log.php
├── class-message.php
├── class-notes.php
├── class-paneluser.php
├── class-plugin-git.php
├── class-plugins.php
├── class-rpc.php
├── class-table.php
├── class-unrealconf.php
├── class-upgrade.php
└── index.php
├── LICENSE
├── README.md
├── api
├── channels.php
├── common_api.php
├── data.php
├── index.php
├── installation.php
├── log.php
├── notification.php
├── overview.php
├── plugin.php
├── search.php
├── server-bans.php
├── set_rpc_server.php
├── test_rpc_server.php
├── timeout.php
├── upgrade.php
└── users.php
├── channels
├── details.php
└── index.php
├── composer.json
├── composer.lock
├── config
├── .gitignore
├── .htaccess
├── compat.php
└── index.php
├── css
├── datatables.min.css
├── index.php
└── unrealircd-admin.css
├── data
├── .gitignore
├── .htaccess
└── index.php
├── doc
├── AUTHORS.md
└── CODE_OF_CONDUCT.md
├── img
├── background.jpg
├── favicon.ico
├── index.php
├── linen.png
├── no-image-available.jpg
├── patreon.png
├── unreal.jpeg
├── unreal.jpg
├── unreal.png
└── wallpaper.jpg
├── inc
├── common.php
├── connection.php
├── defines.php
├── footer.php
└── header.php
├── index.php
├── js
├── bs-modal.js
├── bs-toast.js
├── datatables-ellipsis.js
├── datatables-natural-sort.js
├── datatables.min.js
├── index.php
├── json-formatter.umd.js
├── moment-with-locales.min.js
├── right-click-menus.js
├── service-worker.js
└── unrealircd-admin.js
├── login
└── index.php
├── logs
└── index.php
├── misc
├── ban-exceptions-misc.php
├── channel-lookup-misc.php
├── ip-whois-misc.php
├── modes-parser.php
├── pwa-manifest.php
├── right-click.php
├── server-lookup-misc.php
├── strings.php
└── user-lookup-misc.php
├── news.php
├── plugins
├── config_blocks
│ ├── config_blocks.php
│ └── index.php
├── file_db
│ ├── file_db.php
│ └── index.php
├── index.php
└── sql_db
│ ├── SQL
│ ├── settings.php
│ └── sql.php
│ ├── index.php
│ ├── sql_db.php
│ └── user-info.php
├── server-bans
├── ban-exceptions.php
├── index.php
├── name-bans.php
└── spamfilter.php
├── servers
├── details.php
└── index.php
├── settings
├── add-plugin.php
├── general.php
├── index.php
├── install.php
├── plugins.php
├── rpc-servers.php
├── user-edit.php
└── user-role-edit.php
├── tools
├── checkup.php
├── index.php
└── ip-whois.php
└── users
├── details.php
└── index.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ValwareIRC
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request or enhancement
3 | about: Suggest an idea for this project
4 | title: "[Feature Request]: "
5 | labels: enhancement
6 | assignees: ValwareIRC
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | config.php
2 | vendor/
3 | vendor/**
4 | /log
5 | **~
6 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Order allow,deny
3 | Deny from all
4 |
5 |
--------------------------------------------------------------------------------
/Classes/class-cmodes.php:
--------------------------------------------------------------------------------
1 | "kliRzOL",
10 | "Message restrictions"=>"cSmMnGT",
11 | "Anti-flood and other restrictions"=>"FftCNKVQ",
12 | "Visibility"=>"sp",
13 | "Other"=>"rPHzZDd",
14 | ];
15 | public static $cmodes =
16 | [
17 | "a" => [
18 | "name" => "Admin",
19 | "description" => "Marks someone as channel admin (&)",
20 | "requires" => "Admin"
21 | ],
22 | "b" => [
23 | "name" => "Ban",
24 | "description" => "Marks a ban from a channel",
25 | "requires" => "HalfOp"
26 | ],
27 | "c" => [
28 | "name" => "No colors",
29 | "description" => "Block messages containing mIRC color codes",
30 | "requires" => "Operator"
31 | ],
32 | "d" => [
33 | "name" => "Delay Join",
34 | "description" => "Indicates there are invisible users left over due to unsetting 'D'",
35 | "requires" => "Server"
36 | ],
37 | "e" => [
38 | "name" => "Ban Exemption",
39 | "description" => "Marks an exemption from channel bans",
40 | "requires" => "HalfOp"
41 | ],
42 | "f" => [
43 | "name" => "Flood Protection",
44 | "description" => "Implements channel flood protection",
45 | "requires" => "Operator"
46 | ],
47 | "h" => [
48 | "name" => "Half Op",
49 | "description" => "Marks someone as channel halfop (%)",
50 | "requires" => "Operator"
51 | ],
52 | "i" => [
53 | "name" => "Invite Only",
54 | "description" => "Requires an invitation to join",
55 | "requires" => "HalfOp"
56 | ],
57 | "k" => [
58 | "name" => "Key",
59 | "description" => "Requires a key/password to join",
60 | "requires" => "HalfOp"
61 | ],
62 | "l" => [
63 | "name" => "Limit",
64 | "description" => "Limits a channel to a specific amount of users",
65 | "requires" => "HalfOp"
66 | ],
67 | "m" => [
68 | "name" => "Moderation",
69 | "description" => "Prevents non-voiced users from speaking in a channel",
70 | "requires" => "HalfOp"
71 | ],
72 | "n" => [
73 | "name" => "No External Messages",
74 | "description" => "Messages cannot be sent to the channel from outside it",
75 | "requires" => "HalfOp"
76 | ],
77 | "o" => [
78 | "name" => "Operator",
79 | "description" => "Marks someone as channel operator (@)",
80 | "requires" => "Operator"
81 | ],
82 | "p" => [
83 | "name" => "Private",
84 | "description" => "Prevents the channel from showing up in /WHOIS
outputs and is replaces with \"*\" in /LIST
outputs",
85 | "requires" => "Operator"
86 | ],
87 | "q" => [
88 | "name" => "Owner",
89 | "description" => "Marks someone as channel owner (~)",
90 | "requires" => "Owner"
91 | ],
92 | "r" => [
93 | "name" => "Registered",
94 | "description" => "Channel has been registered to an account",
95 | "requires" => "Server"
96 | ],
97 | "s" => [
98 | "name" => "Secret",
99 | "description" => "Prevents the channel from showing up in /WHOIS
and /LIST
outputs",
100 | "requires" => "Operator"
101 | ],
102 | "t" => [
103 | "name" => "Topic",
104 | "description" => "Only HalfOps and above may set the topic.",
105 | "requires" => "HalfOp"
106 | ],
107 | "v" => [
108 | "name" => "Voice",
109 | "description" => "Marks someone as voiced in the channel (+)",
110 | "requires" => "HalfOp"
111 | ],
112 | "z" => [
113 | "name" => "Secure Only",
114 | "description" => "Only users using a secure connection may join this channel.",
115 | "requires" => "Operator"
116 | ],
117 | "C" => [
118 | "name" => "No CTCPs",
119 | "description" => "CTCP messages are not allowed on the channel.",
120 | "requires" => "Operator"
121 | ],
122 | "D" => [
123 | "name" => "Delay Join",
124 | "description" => "Delay showing joins until someone actually speaks.",
125 | "requires" => "Operator"
126 | ],
127 | "F" => [
128 | "name" => "Flood Profile",
129 | "description" => "Uses a Flood Profile to easily apply flood protection mechanisms",
130 | "requires" => "Operator"
131 | ],
132 | "G" => [
133 | "name" => "Filter",
134 | "description" => "Filters out all Bad words in messages with \"<censored>\".",
135 | "requires" => "Operator"
136 | ],
137 | "H" => [
138 | "name" => "History",
139 | "description" => "Record channel history with specified maximums.",
140 | "requires" => "Operator"
141 | ],
142 | "I" => [
143 | "name" => "Invitation",
144 | "description" => "Marks an inviation to a channel.",
145 | "requires" => "HalfOp"
146 | ],
147 | "K" => [
148 | "name" => "No Knock",
149 | "description" => "Users may not knock on this channel.",
150 | "requires" => "HalfOp"
151 | ],
152 | "L" => [
153 | "name" => "Link",
154 | "description" => "Link to another channel when unable to join",
155 | "requires" => "Operator"
156 | ],
157 | "M" => [
158 | "name" => "Auth Moderated",
159 | "description" => "Only users who have voice or are authenticated may talk in this channel.",
160 | "requires" => "HalfOp"
161 | ],
162 | "N" => [
163 | "name" => "No Nick Changes",
164 | "description" => "Nickname changes are not permitted on the channel.",
165 | "requires" => "HalfOp"
166 | ],
167 | "O" => [
168 | "name" => "IRCOps Only",
169 | "description" => "Only IRC Operators may join this channel.",
170 | "requires" => "IRC Operator"
171 | ],
172 | "P" => [
173 | "name" => "Permanent",
174 | "description" => "This channel will exist even when nobody is inside.",
175 | "requires" => "IRC Operator"
176 | ],
177 | "Q" => [
178 | "name" => "No Kicks",
179 | "description" => "Kicks are not allowed in this channel.",
180 | "requires" => "Operator"
181 | ],
182 | "R" => [
183 | "name" => "Reg Only",
184 | "description" => "Only registered/authenticated users may join the channel.",
185 | "requires" => "Operator"
186 | ],
187 | "S" => [
188 | "name" => "Strip Color",
189 | "description" => "All color is stripped from channel messages.",
190 | "requires" => "IRC Operator"
191 | ],
192 | "T" => [
193 | "name" => "No Notices",
194 | "description" => "Notices are not permitted on the channel.",
195 | "requires" => "IRC Operator"
196 | ],
197 | "V" => [
198 | "name" => "No Invites",
199 | "description" => "Users are not allowed to /INVITE
others to the channel.",
200 | "requires" => "IRC Operator"
201 | ],
202 | "Z" => [
203 | "name" => "Is Secure",
204 | "description" => "Indication that all users on the channel are on a Secure connection.",
205 | "requires" => "Server"
206 | ]
207 | ];
208 |
209 | static function lookup($mode)
210 | {
211 | return (isset(self::$cmodes[$mode])) ? self::$cmodes[$mode] :
212 | [
213 | 'name' => "Unknown mode",
214 | 'description' => "Unknown mode +$mode",
215 | 'requires' => 'Unknown'
216 | ];
217 | }
218 | static function setmodes($modes)
219 | {
220 | $g = [];
221 | if (is_array($modes))
222 | {
223 | self::$uplink = $modes;
224 | return;
225 | }
226 | else if (!strstr($modes,","))
227 | $g = [$modes];
228 | else $g = split($g,",");
229 | self::$uplink = $g;
230 | }
231 | static $uplink = [];
232 |
233 | }
234 |
--------------------------------------------------------------------------------
/Classes/class-hook.php:
--------------------------------------------------------------------------------
1 | ""];
13 | *
14 | * So when you call this hook, you must refer to the
15 | * parameter by reference. For example:
16 | * Hook::func(HOOKTYPE_NAVBAR, 'add_navbar_item');
17 | *
18 | * function add_navbar_item(&$pages) // remember the & to use by reference
19 | * { insert_hacks_here(); }
20 | */
21 | define('HOOKTYPE_NAVBAR', 100);
22 |
23 | /** HOOKTYPE_PRE_HEADER
24 | *
25 | * This doesn't receive anything, however you must still specify an
26 | * parameter for your hook function, because it's referring to memory. Sorry =]
27 | *
28 | * Putting HTML in this hook is not a good idea.
29 | */
30 | define('HOOKTYPE_PRE_HEADER', 101);
31 |
32 | /** HOOKTYPE_HEADER
33 | *
34 | * This is run after/during the header is sent. You can call your global scripts, global css or whatnot from here.
35 | */
36 | define('HOOKTYPE_HEADER', 119);
37 |
38 | /** HOOKTYPE_PRE_OVERVIEW_CARD
39 | *
40 | * @param object $stats
41 | *
42 | * This is called before the initial cards have loaded in the overview.
43 | * This lets you add your own HTML or whatever you like on the overview,
44 | * new cards, whatever.
45 | *
46 | * The parameter is an object containing stats used in the overview.
47 | * See "index.php" to see how it's used.
48 | *
49 | */
50 | define('HOOKTYPE_PRE_OVERVIEW_CARD', 102);
51 |
52 | /** HOOKTYPE_OVERVIEW_CARD
53 | *
54 | * @param object $stats
55 | *
56 | * This is called after the initial cards have loaded in the overview.
57 | * This lets you add your own HTML or whatever you like on the overview,
58 | * new cards, whatever.
59 | *
60 | * The parameter is an object containing stats used in the overview.
61 | * See "index.php" to see how it's used.
62 | *
63 | */
64 | define('HOOKTYPE_OVERVIEW_CARD', 103);
65 |
66 | /** HOOKTYPE_NOTIFICATION
67 | *
68 | * @param array $notification
69 | * The array should contain:
70 | *
71 | * "name" - The name of the recipient
72 | * "message" - The notification message
73 | *
74 | * This does not do anything special by itself. It simply allows plugins
75 | * to be able to use it with regards to notification sending.
76 | * This is not run at any place, but should be run from your plugin.
77 | *
78 | * This hook is simple in design and only contains two elements in attempt
79 | * to make it work cross-plugin. That is, if you have implemented your own
80 | * notificiation system, you will be able to do whatever you like such as
81 | * display a navbar list of notifications or send important emails by running
82 | * this hook.
83 | *
84 | */
85 | define('HOOKTYPE_NOTIFICATION', 104);
86 |
87 |
88 | /** HOOKTYPE_PRE_FOOTER
89 | * @param array $empty - Doesn't do anything
90 | *
91 | * This runs inside the footer body before anything else.
92 | */
93 | define('HOOKTYPE_PRE_FOOTER', 105);
94 |
95 | /** HOOKTYPE_FOOTER
96 | * @param array $empty - Doesn't do anything
97 | *
98 | * This runs inside the footer body after everything else.
99 | */
100 | define('HOOKTYPE_FOOTER', 106);
101 |
102 | /** HOOKTYPE_USER_LOOKUP
103 | * @param array $user [name, id]
104 | */
105 | define('HOOKTYPE_USER_LOOKUP', 107);
106 |
107 | /** HOOKTYPE_USERMETA_ADD
108 | * @param array $meta [[id, key, value], (object)PanelUser]
109 | */
110 | define('HOOKTYPE_USERMETA_ADD', 108);
111 |
112 | /** HOOKTYPE_USERMETA_ADD
113 | * @param array $meta [id, key, value]
114 | */
115 | define('HOOKTYPE_USERMETA_DEL', 109);
116 |
117 | /** HOOKTYPE_USERMETA_ADD
118 | * @param array $meta [id, key, value]
119 | */
120 | define('HOOKTYPE_USERMETA_GET', 110);
121 |
122 | /** HOOKTYPE_USER_CREATE
123 | * @param array $userinfo []
124 | */
125 | define('HOOKTYPE_USER_CREATE', 111);
126 |
127 | /** HOOKTYPE_GET_USER_LIST
128 | * @param array $userlist []
129 | */
130 | define('HOOKTYPE_GET_USER_LIST', 112);
131 |
132 | define('HOOKTYPE_USER_DELETE', 113);
133 |
134 | define('HOOKTYPE_USER_LOGIN', 114);
135 |
136 | define('HOOKTYPE_USER_LOGIN_FAIL', 115);
137 |
138 | define('HOOKTYPE_USER_PERMISSION_LIST', 116);
139 |
140 | define('HOOKTYPE_EDIT_USER', 117);
141 |
142 | define('HOOKTYPE_RIGHTCLICK_MENU', 118);
143 |
144 | /* 119 = HOOKTYPE_HEADER (See under HOOKTYPE_PRE_HEADER) */
145 |
146 | define('HOOKTYPE_GENERAL_SETTINGS', 120);
147 |
148 | /* Array passed is $_POST[] */
149 | define('HOOKTYPE_GENERAL_SETTINGS_POST', 121);
150 |
151 |
152 |
153 | /** Send out a request to ask if there are any plugins which provide authentication */
154 | define('HOOKTYPE_AUTH_MOD', 200);
155 |
156 | /** An upgrade has been detected.
157 | * @param array $versioninfo[]
158 | * Array contains: "old_version" and "new_version"
159 | */
160 | define('HOOKTYPE_UPGRADE', 201);
161 |
162 | /**
163 | * Class for "Hook"
164 | * This is the main function which gets called whenever you want to use a Hook.
165 | *
166 | * Example:
167 | * Calling the Hook using a function:
168 | * Hook::func(HOOKTYPE_NAVBAR, 'bob');
169 | *
170 | * This Hook references the function 'bob', and will run this
171 | * function bob
172 | * {
173 | * echo "We rehashed!";
174 | * }
175 | *
176 | * Example 2:
177 | * Calling the Hook using an initialized object class method:
178 | * Hook::func(HOOKTYPE_NAVBAR, [$this, 'method']);
179 | *
180 | * Example 3:
181 | * Calling the Hook using a static class method:
182 | * Hook::func(HOOKTYPE_NAVBAR, 'classname::method');
183 | *
184 | */
185 | class Hook {
186 |
187 | /** A static list of Hooks and their associated functions */
188 | private static $actions = [];
189 |
190 | /** Runs a Hook.
191 | * The parameter for $Hook should be a "HOOKTYPE_" as defined in hook.php
192 | * @param string $Hook The define or string name of the Hook. For example, HOOKTYPE_REHASH.
193 | * @param array &$args The array of information you are sending along in the Hook, so that other functions may see and modify things.
194 | * @return void Does not return anything.
195 | *
196 | */
197 | public static function run($Hook, &$args = array())
198 | {
199 | if (!empty(self::$actions[$Hook]))
200 | foreach (self::$actions[$Hook] as &$f)
201 | $f($args);
202 |
203 | }
204 |
205 | /** Calls a Hook
206 | * @param string $Hook The define or string name of the Hook. For example, HOOKTYPE_REHASH.
207 | * @param string|Closure $function This is a string reference to a Closure function or a class method.
208 | * @return void Does not return anything.
209 | */
210 | public static function func($Hook, $function)
211 | {
212 | self::$actions[$Hook][] = $function;
213 | }
214 |
215 | /** Deletes a Hook
216 | * @param string $Hook The Hook from which we are removing a function reference.
217 | * @param string $function The name of the function that we are removing.
218 | * @return void Does not reuturn anything.
219 | */
220 | public static function del($Hook, $function)
221 | {
222 | for ($i = 0; isset(self::$actions[$Hook][$i]); $i++)
223 | if (self::$actions[$Hook][$i] == $function)
224 | array_splice(self::$actions[$Hook],$i);
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/Classes/class-log.php:
--------------------------------------------------------------------------------
1 | it($strings);
37 | }
38 |
39 | function get_date($year, $month, $day, $hour, $minute)
40 | {
41 | return "$year-$month-$day" . "T$hour-$minute" . "Z";
42 | }
--------------------------------------------------------------------------------
/Classes/class-message.php:
--------------------------------------------------------------------------------
1 |
11 |
12 | ×
13 | ";
19 | }
20 | ?>
21 |
29 |
30 | ×
31 | ";
37 | }
38 | ?>
39 |
47 |
48 | ×
49 | ";
55 | }
56 | ?>
57 |
62 |
63 | ×
64 | ";
70 | }
71 | ?>
72 |
"127.0.0.1", "nick" => "bob", "account" => "bob", "id" => "lol]
9 | * @return array Returns an array of objects (notes)
10 | */
11 | public static function find(array $query) : array
12 | {
13 | global $config;
14 | read_config_db();
15 | if (!isset($config['notes']))
16 | return [];
17 |
18 | $notes = [];
19 | foreach ($query as $key => $value)
20 | {
21 | foreach ($config['notes'] as $nkey => $nvalue) // $nkey = "ip" "nick" "account", $nvalue = array
22 | {
23 | foreach ($nvalue as $k => $n) // $k = "127.0.0.1", "bob", "bobsaccount", $n = array of notes [id => note]
24 | {
25 | if ($value != $k)
26 | continue;
27 | $note = [];
28 | $note["type"] = $nkey;
29 | $note["data"] = $k;
30 | $note["notes"] = $n;
31 | $notes[$key] = $note;
32 | }
33 | }
34 | }
35 | return !empty($notes) ? $notes : [];
36 | }
37 |
38 | /**
39 | * Add a note to one or more peices of data
40 | * @param array ["ip" => "127.0.0.1"]
41 | * @param string $note "This is a note"
42 | * @return void
43 | */
44 | public static function add(array $params, string $note)
45 | {
46 | global $config;
47 | read_config_db();
48 | foreach ($params as $key => $value)
49 | {
50 | $id = md5(random_bytes(20)); // note ID (for linking)
51 | $config['notes'][$key][$value][$id] = $note;
52 | }
53 | write_config(); // write db
54 | }
55 |
56 | public static function delete_by_id(string $id)
57 | {
58 | global $config;
59 | read_config_db();
60 | if (!isset($config['notes']))
61 | return NULL;
62 |
63 | foreach ($config['notes'] as $nkey => $nvalue)
64 | foreach ($nvalue as $key => $value)
65 | if ($value == $id)
66 | {
67 | unset($config['notes'][$nkey][$key]);
68 | break;
69 | }
70 |
71 | write_config('notes');
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Classes/class-plugin-git.php:
--------------------------------------------------------------------------------
1 | 200) // Cache for 3.333 minutes lol
22 | {
23 | // come simba it is taem
24 | $curl = curl_init($url);
25 |
26 | // Set the options
27 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // Return the response instead of printing it
28 | curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); // Set the content type to JSON
29 | curl_setopt($curl, CURLOPT_USERAGENT, "UnrealIRCd Admin Panel"); // This is Secret Agent UnrealIRCd Admin Panel reporting for doody
30 | // Execute the request
31 | $response = curl_exec($curl);
32 |
33 | // Check for errors
34 | if ($response === false)
35 | $this->err = curl_error($curl);
36 | else
37 | {
38 | $this->data = json_decode($response, false);
39 | $config['third-party-plugins']['data'] = $this->data;
40 | $config['third-party-plugins']['timestamp'] = time();
41 | write_config('third-party-plugins');
42 | }
43 | }
44 | else
45 | $this->data = $config['third-party-plugins']['data'];
46 |
47 | }
48 |
49 |
50 |
51 | public function ifInstalledLabel($name, $installed = false)
52 | {
53 | if ($installed)
54 | { ?>
55 | ✔ Installed
56 |
60 | ✔ Installed
61 | minver <= $wpversion)
69 | { ?>
70 | Compatible
71 |
74 | Incompatible
75 | err)
84 | die("Could not fetch list.\n");
85 |
86 | ?>
87 |
88 | data->list as $p)
90 | {
91 | $tok = split(WEBPANEL_VERSION,"-");
92 | $upgradeRequired = false;
93 | $wpversion = $tok[0];
94 | if ($p->minver > $wpversion)
95 | $upgradeRequired = true;
96 | $installed = in_array($p->name, $config['plugins']) ? true : false;
97 | if (is_string($p))
98 | continue;
99 |
100 | // use a default image if there was none
101 | $p->icon = $p->icon ?? get_config("base_url")."img/no-image-available.jpg";
102 | ?>
103 |
104 |
plugin-card card text-dark bg-light ml-4 mb-3 w-25" style="min-width:300px">
105 |
106 |
107 |
117 |
118 |
119 |
120 |
title ?> vversion ?>
121 |
description ?>
122 |
123 |
124 |
125 |
138 |
139 |
144 |
145 | Want to see your plugin listed here? Make a pull request to our GitHub Repository!
146 |
151 |
--------------------------------------------------------------------------------
/Classes/class-plugins.php:
--------------------------------------------------------------------------------
1 | error)
36 | {
37 | Message::Fail("Warning: Plugin \"$modname\" failed to load: $plugin->error");
38 | }
39 | else
40 | {
41 | self::$list[] = $plugin;
42 | }
43 | }
44 | static function plugin_exists($name, $version = NULL)
45 | {
46 | foreach(self::$list as $p)
47 | if (!strcmp($p->name,$name) && (!$version || ($version >= $p->version)))
48 | return true;
49 |
50 | return false;
51 | }
52 |
53 | }
54 |
55 | class Plugin
56 | {
57 | public $name;
58 | public $author;
59 | public $version;
60 | public $description;
61 | public $handle;
62 | public $email;
63 |
64 | public $error = NULL;
65 | function __construct($handle)
66 | {
67 | if (!is_dir(UPATH."/plugins/$handle"))
68 | $this->error = "Plugin directory \"".UPATH."/plugins/$handle\" doesn't exist";
69 |
70 | else if (!is_file(UPATH."/plugins/$handle/$handle.php"))
71 | $this->error = "Plugin file \"".UPATH."/plugins/$handle/$handle.php\" doesn't exist";
72 |
73 | else
74 | {
75 | require_once UPATH."/plugins/$handle/$handle.php";
76 |
77 | if (!class_exists($handle))
78 | $this->error = "Class \"$handle\" doesn't exist";
79 |
80 | else
81 | {
82 | $plugin = new $handle();
83 |
84 | if (!isset($plugin->name))
85 | $this->error = "Plugin name not defined";
86 | elseif (!isset($plugin->author))
87 | $this->error = "Plugin author not defined";
88 | elseif (!isset($plugin->version))
89 | $this->error = "Plugin version not defined";
90 | elseif (!isset($plugin->description))
91 | $this->error = "Plugin description not defined";
92 | elseif (!isset($plugin->email))
93 | $this->error = "Plugin email not defined";
94 | else
95 | {
96 | $this->handle = $handle;
97 | $this->name = $plugin->name;
98 | $this->author = $plugin->author;
99 | $this->version = $plugin->version;
100 | $this->description = $plugin->description;
101 | $this->email = $plugin->email;
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
108 | if (get_config("plugins"))
109 | {
110 | foreach(get_config("plugins") as $plugin)
111 | Plugins::load($plugin);
112 | }
113 |
114 | /* Requires the plugin */
115 | function require_plugin($name, $version)
116 | {
117 | if (!Plugins::plugin_exists($name,$version))
118 | die("Missing plugin: $name v$version");
119 | }
120 |
121 | /* I'm not a fan of globals */
122 | class AuthModLoaded
123 | {
124 | public static $status = 0;
125 | }
126 |
127 | function is_auth_provided()
128 | {
129 | return AuthModLoaded::$status;
130 | }
131 |
--------------------------------------------------------------------------------
/Classes/class-rpc.php:
--------------------------------------------------------------------------------
1 | user()->getAll();
41 | // TODO: error checking
42 |
43 | foreach($ret as $r)
44 | {
45 | RPC_List::$user[] = $r;
46 | if (strpos($r->user->modes,"o") !== false && strpos($r->user->modes,"S") == false)
47 | RPC_List::$opercount++;
48 | elseif (strpos($r->user->modes,"S") !== false)
49 | RPC_List::$services_count++;
50 | }
51 |
52 | /* Get the channels list */
53 | $ret = $rpc->channel()->getAll();
54 | foreach($ret as $r)
55 | {
56 | RPC_List::$channel[] = $r;
57 | if ($r->num_users > RPC_List::$channel_pop_count)
58 | {
59 | RPC_List::$channel_pop_count = $r->num_users;
60 | RPC_List::$most_populated_channel = $r->name;
61 | }
62 | }
63 |
64 | /* Get the tkl list */
65 | $ret = $rpc->serverban()->getAll();
66 | foreach($ret as $r)
67 | RPC_List::$tkl[] = $r;
68 |
69 | /* Get the spamfilter list */
70 | $ret = $rpc->spamfilter()->getAll();
71 | foreach($ret as $r)
72 | RPC_List::$spamfilter[] = $r;
73 |
74 | foreach ($rpc->nameban()->getAll() as $r)
75 | RPC_List::$nameban[] = $r;
76 |
77 | foreach ($rpc->serverbanexception()->getAll() as $r)
78 | RPC_List::$exception[] = $r;
79 |
80 | foreach ($rpc->server()->getAll() as $r)
81 | RPC_List::$server[] = $r;
82 | }
83 |
84 | /** Convert the duration_string */
85 | function rpc_convert_duration_string($str)
86 | {
87 | $tok = explode("w", $str);
88 | $weeks = $tok[0];
89 | $tok = explode("d", $tok[1]);
90 | $days = $tok[0];
91 | $tok = explode("h", $tok[1]);
92 | $hours = $tok[0];
93 | return "$weeks weeks, $days days and $hours hours";
94 |
95 | }
--------------------------------------------------------------------------------
/Classes/class-table.php:
--------------------------------------------------------------------------------
1 | parse_config($file, $error);
27 | }
28 | }
29 |
30 | private function parse_config($string, &$error)
31 | {
32 | $tok = split($string);
33 | $blockstring = "";
34 | $full = "";
35 | foreach($tok as $str)
36 | {
37 | $str = trim($str);
38 | if ($str[0] == '#' || substr($str,0,2) == "//")
39 | {
40 | var_dump($str);
41 | continue;
42 | }
43 | if (!strcmp($str,"{") && mb_substr($blockstring,-2,2) !== "::")
44 | strcat($blockstring,"::");
45 |
46 | elseif (!strcmp($str,"}"))
47 | {
48 | $split = split($blockstring,"::");
49 | if (BadPtr($split[sizeof($split) - 1]))
50 | unset($split[sizeof($split) - 1]);
51 | unset($split[sizeof($split) - 1]);
52 | $blockstring = glue($split,"::");
53 | if (!BadPtr($blockstring))
54 | {
55 | strcat($blockstring,"::");
56 | }
57 | }
58 | // if we found a value and it's time to go to the next one
59 | elseif (!BadPtr($str) && $str[strlen($str) - 1] == ";")
60 | {
61 | if (substr_count($str,"\"") != 1)
62 | strcat($blockstring, "::".rtrim($str,";")); // finish off our item
63 | else strcat($blockstring, " ".rtrim($str,";"));
64 | strcat($full,str_replace(["::::", "\""],["::", ""],$blockstring)."\n"); // add the full line to our $full variable
65 |
66 | /* rejig the blockstring */
67 | $split = split($blockstring,"::");
68 | if (BadPtr($split[sizeof($split) - 1]))
69 | unset($split[sizeof($split) - 1]);
70 | unset($split[sizeof($split) - 1]);
71 | unset($split[sizeof($split) - 1]);
72 | $blockstring = glue($split,"::");
73 | if (!BadPtr($blockstring))
74 | {
75 | rtrim($blockstring,":");
76 | strcat($blockstring,"::");
77 | }
78 | }
79 |
80 | else
81 | { if (!BadPtr($blockstring) && mb_substr($blockstring,-2,2) !== "::")
82 | strcat($blockstring," ");
83 | strcat($blockstring,$str);
84 | }
85 | }
86 |
87 | $full = split($full,"\n");
88 | echo highlight_string(var_export($full, true));
89 | $long = [];
90 |
91 | foreach($full as $config_item)
92 | {
93 | $arr = &$long;
94 | self::$settings_short[] = $config_item;
95 | $tok = split($config_item,"::");
96 | for ($i = 0; $i <= count($tok); $i++)
97 | {
98 | if (isset($tok[$i + 2]))
99 | $arr = &$arr[$tok[$i]];
100 |
101 | elseif (isset($tok[$i + 1]) && isset($tok[$i - 1]))
102 | $arr[$tok[$i]] = $tok[$i + 1];
103 |
104 | elseif (isset($tok[$i + 1]))
105 | $arr[$tok[$i]][] = $tok[$i + 1];
106 | }
107 | }
108 | self::$settings_temp = $long;
109 | $cf = &self::$settings_temp;
110 |
111 | if (!empty($error))
112 | {
113 | self::$settings_temp = [];
114 | return false;
115 | }
116 | $arr = ['cfg' => $cf, 'err' => &$error];
117 |
118 |
119 | if (!empty($error))
120 | {
121 | self::$settings_temp = [];
122 | return false;
123 | }
124 | self::$settings = self::$settings_temp;
125 | self::$settings_temp = [];
126 |
127 | echo highlight_string(var_export(self::$settings, true));
128 | }
129 | function parse2()
130 | {
131 | $configFile = 'unrealircd.conf';
132 |
133 | $config = array();
134 |
135 | if (file_exists($configFile)) {
136 | $lines = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
137 | foreach ($lines as $line)
138 | {
139 | $config[] = trim($line);
140 | }
141 | }
142 | echo highlight_string(var_export($config, true));
143 | return $config;
144 | }
145 | }
146 |
147 | $errors = [];
148 | new Conf("unrealircd.conf", $errors);
149 |
--------------------------------------------------------------------------------
/Classes/class-upgrade.php:
--------------------------------------------------------------------------------
1 | web_dir = implode('/',$tok).'/';
25 |
26 | /** prepare the temp directory */
27 | $temp_dir = $this->web_dir."panel_upgrade";
28 | $temp_dir .= ($temp_dir[strlen($temp_dir) - 1] != '/') ? "/" : "";
29 | if (file_exists($temp_dir)) {
30 | deleteDirectoryContents($temp_dir);
31 | rmdir($temp_dir);
32 | }
33 | $mkdir = mkdir($temp_dir, 0755, true);
34 |
35 | $this->temp_dir = $mkdir ? $temp_dir : NULL;
36 | $this->error = $mkdir ? NULL : "Could not create directory: $temp_dir";
37 | Upgrade::$upgrade_available = false;
38 | if ($this->error)
39 | error_log($this->error);
40 | }
41 |
42 | /** Checks for a new upgrade */
43 | function checkForNew()
44 | {
45 | global $config;
46 | read_config_db();
47 | $last_check = &$config['upgrade']['last_check'] ?? 0;
48 | if (isset($last_check) && time() - $last_check < 6) // only check every 15 mins
49 | {
50 | error_log("Skipping upgrade check, checked ".time() - $last_check." seconds ago");
51 | return false;
52 | }
53 | error_log(time()." - ".$last_check." = ".time()-$last_check);
54 | $apiUrl = "https://api.github.com/repos/unrealircd/unrealircd-webpanel/releases";
55 | $response = file_get_contents($apiUrl, false, stream_context_create(
56 | ["http" => ["method" => "GET", "header" => "User-agent: UnrealIRCd Webpanel"]]
57 | ));
58 |
59 | if ($response === false)
60 | {
61 | $this->error = "Couldn't check github.";
62 | return false;
63 | }
64 | $data = json_decode($response, true);
65 | $latest = $data[0];
66 | $config['upgrade']['latest_version'] = $latest['tag_name'];
67 | $last_check = time();
68 | $config['upgrade']['download_link'] = $latest['zipball_url'];
69 | write_config('upgrade');
70 | error_log($latest['tag_name'] ." ". WEBPANEL_VERSION);
71 | Upgrade::$upgrade_available = version_compare($latest['tag_name'], WEBPANEL_VERSION, ">") ? true : false;
72 | }
73 |
74 | function downloadUpgradeZip()
75 | {
76 | $ch = curl_init(get_config('upgrade::download_link'));
77 | $fp = fopen($this->temp_dir."unrealircd-webpanel-upgrade.zip", 'w+');
78 |
79 | curl_setopt($ch, CURLOPT_FILE, $fp);
80 | curl_setopt($ch, CURLOPT_TIMEOUT, 60);
81 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
82 | curl_setopt($ch, CURLOPT_HTTPHEADER, [
83 | 'User-Agent: UnrealIRCd Webpanel',
84 | ]);
85 | $success = curl_exec($ch);
86 | $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
87 |
88 | if ($code == "403" || $code == "404")
89 | {
90 | $this->error ="Unable to download";
91 | }
92 | curl_close($ch);
93 | fclose($fp);
94 |
95 | return $success;
96 | }
97 | function extractZip() {
98 | $zip = new ZipArchive;
99 | if ($zip->open($this->temp_dir."unrealircd-webpanel-upgrade.zip") === true)
100 | {
101 | $zip->extractTo("$this->temp_dir");
102 | $zip->close();
103 | self::$temp_extracted_dir = findOnlyDirectory($this->temp_dir);
104 | error_log(self::$temp_extracted_dir);
105 | return true;
106 | } else {
107 | return false;
108 | }
109 | }
110 | function cleanupOldFiles()
111 | {
112 | foreach ($this->compareAndGetFilesToDelete() as $file)
113 | {
114 | unlink("$this->web_dir$file");
115 | error_log("Deleting: $file");
116 | }
117 | }
118 | function compareAndGetFilesToDelete() : array
119 | {
120 | $currentFiles = $this->listFiles($this->web_dir);
121 | $updateFiles = $this->listFiles(self::$temp_extracted_dir);
122 | $filesToDelete = array_diff($currentFiles, $updateFiles);
123 | $filesToActuallyDelete = [];
124 | error_log("Comparing... Files to delete:");
125 | foreach ($filesToDelete as $file)
126 | {
127 | // skip the relevant directories
128 | if (str_starts_with($file, "panel_upgrade/")
129 | || str_starts_with($file, "vendor/")
130 | || str_starts_with($file, "config/")
131 | || str_starts_with($file, "data/")
132 | || str_starts_with($file, "plugins/"))
133 | continue;
134 | $filesToActuallyDelete[] = $file;
135 | }
136 | return $filesToActuallyDelete;
137 | }
138 |
139 | function extractToWebdir()
140 | {
141 | recurse_copy(self::$temp_extracted_dir, $this->web_dir);
142 | }
143 |
144 | /**
145 | * Cleans up the extracted update files
146 | * @return void
147 | */
148 | function cleanupDownloadFiles()
149 | {
150 | $ex_dir = self::$temp_extracted_dir ?? findOnlyDirectory($this->temp_dir);
151 | deleteDirectoryContents($ex_dir);
152 | rmdir($ex_dir);
153 | }
154 |
155 | function listFiles($dir) {
156 | $files = [];
157 | $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
158 | foreach ($iterator as $file)
159 | {
160 | if ($file->isFile())
161 | {
162 | $f = substr($file->getPathname(), strlen($dir));
163 | if ($f[0] == "/") $f = substr($f,1);
164 |
165 | $files[] = $f;
166 | }
167 | }
168 | return $files;
169 | }
170 | }
171 |
172 |
173 | function findOnlyDirectory($topDir) {
174 | // Ensure the directory exists and is indeed a directory
175 | if (!is_dir($topDir)) {
176 | die("The specified path is not a directory.");
177 | }
178 |
179 | // Open the directory
180 | $dirHandle = opendir($topDir);
181 | if ($dirHandle === false) {
182 | die("Unable to open directory.");
183 | }
184 |
185 | $directories = [];
186 |
187 | // Read through the directory contents
188 | while (($entry = readdir($dirHandle)) !== false) {
189 | $fullPath = $topDir . DIRECTORY_SEPARATOR . $entry;
190 | // Check if the entry is a directory and not . or ..
191 | if (is_dir($fullPath) && $entry !== '.' && $entry !== '..') {
192 | $directories[] = $fullPath;
193 | }
194 | }
195 |
196 | // Close the directory handle
197 | closedir($dirHandle);
198 |
199 | // Check if there is exactly one directory
200 | if (count($directories) === 1) {
201 | return $directories[0];
202 | } elseif (count($directories) === 0) {
203 | return "No directories found after extracting. Possibly missing php-zip extention. Aborting upgrade.";
204 | } else {
205 | return "Multiple directories found. Previous cleanup was unsuccessful for some reason, maybe a permissions error? Aborting upgrade.";
206 | }
207 | }
208 |
209 |
210 | function deleteDirectoryContents($dir) {
211 | error_log("Deleting directory contents at $dir");
212 | if (!is_dir($dir)) {
213 | echo "The provided path is not a directory.";
214 | return false;
215 | }
216 |
217 | // Open the directory
218 | $handle = opendir($dir);
219 | if ($handle === false) {
220 | echo "Failed to open the directory.";
221 | return false;
222 | }
223 |
224 | // Loop through the directory contents
225 | while (($item = readdir($handle)) !== false) {
226 | // Skip the special entries "." and ".."
227 | if ($item == "." || $item == "..") {
228 | continue;
229 | }
230 |
231 | $itemPath = $dir."/".$item;
232 |
233 | // If the item is a directory, recursively delete its contents
234 | if (is_dir($itemPath)) {
235 | deleteDirectoryContents($itemPath);
236 | // Remove the empty directory
237 | rmdir($itemPath);
238 | } else {
239 | // If the item is a file, delete it
240 | unlink($itemPath);
241 | }
242 | }
243 |
244 | // Close the directory handle
245 | closedir($handle);
246 |
247 | return true;
248 | }
249 |
250 | function recurse_copy($src, $dst) {
251 | $dir = opendir($src);
252 | @mkdir($dst);
253 | while(false !== ( $file = readdir($dir)) )
254 | if (( $file != '.' ) && ( $file != '..' ))
255 | {
256 | if ( is_dir($src . '/' . $file) )
257 | recurse_copy($src . '/' . $file, $dst . '/' . $file);
258 |
259 | else
260 | copy($src . '/' . $file, $dst . '/' . $file);
261 | }
262 |
263 |
264 | closedir($dir);
265 | }
266 |
--------------------------------------------------------------------------------
/Classes/index.php:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 | ## Example Overview from Mobile
18 |
19 |

20 |

21 |
22 |
23 | ## Installation ##
24 | See https://www.unrealircd.org/docs/UnrealIRCd_webpanel for all documentation.
25 |
--------------------------------------------------------------------------------
/api/channels.php:
--------------------------------------------------------------------------------
1 | channel()->getAll();
11 |
12 | $columns = array_column($channels, 'num_users');
13 | array_multisort($columns, SORT_DESC, $channels);
14 |
15 | $out = [];
16 | foreach($channels as $channel)
17 | {
18 | $modes = (isset($channel->modes)) ? "+" . explode(" ",$channel->modes)[0] : "";
19 | $topic = '';
20 | if (isset($channel->topic))
21 | $topic = htmlentities(StripControlCharacters($channel->topic), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 | ENT_DISALLOWED);
22 | $date = explode("T", $channel->creation_time)[0];
23 | $out[] = [
24 | "Name" => htmlspecialchars($channel->name),
25 | "Users" => $channel->num_users,
26 | "Modes" => "modes)."\">$modes",
27 | "Topic" => $topic,
28 | "Created" => "creation_time."\">$date",
29 | ];
30 | }
31 |
32 | function custom_sort($a,$b)
33 | {
34 | return $b["Users"] <=> $a["Users"];
35 | }
36 |
37 | usort($out, "custom_sort");
38 |
39 | echo json_encode($out);
40 |
--------------------------------------------------------------------------------
/api/common_api.php:
--------------------------------------------------------------------------------
1 | log()->subscribe($sources);
79 | if ($rpc->error)
80 | {
81 | echo $rpc->error;
82 | die;
83 | }
84 |
85 | for(;;)
86 | {
87 | $res = $rpc->eventloop();
88 | if (!$res)
89 | {
90 | /* Output at least something every timeout (10) seconds,
91 | * otherwise PHP may not
92 | * notice when the webclient is gone.
93 | */
94 | if ($fpm_workaround_needed)
95 | echo str_repeat(" ", 4095)."\n";
96 | else
97 | echo "\n";
98 | continue;
99 | }
100 | send_sse($res);
101 | }
102 | }
103 |
104 | function api_timer_loop(int $every_msec, string $method, array|null $params = null)
105 | {
106 | GLOBAL $rpc;
107 |
108 | /* First, execute it immediately */
109 | $res = $rpc->query($method, $params);
110 | if (!$res)
111 | die;
112 | send_sse($res);
113 | $rpc->rpc()->add_timer("timer", $every_msec, $method, $params);
114 | if ($rpc->error)
115 | {
116 | /* Have to resort to old style: client-side timer */
117 | while(1)
118 | {
119 | $res = $rpc->query($method, $params);
120 | if (!$res)
121 | die;
122 | send_sse($res);
123 | usleep($every_msec * 1000);
124 | }
125 | }
126 |
127 | /* New style: use server-side timers */
128 | /* - First, execute it immediately */
129 | $res = $rpc->query($method, $params);
130 | if (!$res)
131 | die;
132 | send_sse($res);
133 | /* - Then add the timer */
134 | for(;;)
135 | {
136 | $res = $rpc->eventloop();
137 | if (!$res)
138 | {
139 | /* Output at least something every timeout (10) seconds,
140 | * otherwise PHP may not
141 | * notice when the webclient is gone.
142 | */
143 | if ($fpm_workaround_needed)
144 | echo str_repeat(" ", 4095)."\n";
145 | else
146 | echo "\n";
147 | continue;
148 | }
149 | send_sse($res);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/api/data.php:
--------------------------------------------------------------------------------
1 | $cpuUsage[0],
18 | "memory" => convert($memUsage),
19 | );
20 |
21 | header('Content-Type: application/json');
22 | echo json_encode($data);
23 | ?>
--------------------------------------------------------------------------------
/api/index.php:
--------------------------------------------------------------------------------
1 | "Configuration file exists."]));
10 |
11 | if (!isset($_POST) || empty($_POST))
12 | die(json_encode(["error" => "Incorrect parameters"]));
13 |
14 | if ($_POST['method'] == "sql")
15 | {
16 | try {
17 | $conn = mysqli_connect($_POST['host'], $_POST['user'], $_POST['password'], $_POST['database']);
18 | } catch(Exception $e)
19 | {
20 | }
21 | // check connection
22 | if (mysqli_connect_errno())
23 | die(json_encode(["error" => "Failed to connect to MySQL: " . mysqli_connect_error()]));
24 |
25 | $sql = "SHOW TABLES LIKE '".$conn->real_escape_string($_POST['table_prefix'])."%'"; // SQL query to check if table exists
26 | $result = $conn->query($sql);
27 | if ($result->num_rows > 0)
28 | die(json_encode(["warn" => "Database already has data"]));
29 |
30 | // close connection
31 | mysqli_close($conn);
32 | die(json_encode(["success" => "SQL Connection successful"]));
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/api/log.php:
--------------------------------------------------------------------------------
1 | log()->getAll($log_list);
26 | if ($response !== false)
27 | {
28 | /* Only supported in later UnrealIRCd versions */
29 | $cnt = 0;
30 | foreach($response as $r)
31 | {
32 | $r = (ARRAY)$r;
33 | $cnt++;
34 | if (($cnt % 100) != 0)
35 | $r["sync_option"] = "no_sync";
36 | send_sse($r);
37 | }
38 | }
39 |
40 | $r = ["sync_option"=>"sync_now"];
41 | send_sse($r);
42 |
43 | api_log_loop($log_list);
44 |
--------------------------------------------------------------------------------
/api/notification.php:
--------------------------------------------------------------------------------
1 | "Access denied"]));
10 | if (empty($_GET))
11 | die(json_encode($config['third-party-plugins']['data']));
12 |
13 | elseif(isset($_GET['install']))
14 | {
15 | install_plugin($_GET['install']);
16 | }
17 | elseif (isset($_GET['uninstall']))
18 | {
19 | uninstall_plugin($_GET['uninstall']);
20 | }
21 |
22 | function uninstall_plugin($name)
23 | {
24 | global $config;
25 | if (!Plugins::plugin_exists($name))
26 | die(json_encode(['error' => "Plugin not loaded"]));
27 |
28 | foreach($config['plugins'] as $k => $v)
29 | if ($v == $name)
30 | unset($config['plugins'][$k]);
31 | write_config();
32 |
33 | deleteDirectory(UPATH."/plugins/$name");
34 | die(json_encode(["success" => "Plugin was deleted successfully"]));
35 | }
36 |
37 | /**Attempt to install the plugin
38 | * @param string $name name of the plugin
39 | * @return void
40 | */
41 | function install_plugin($name)
42 | {
43 | global $config;
44 | if (in_array($name, $config['plugins']))
45 | die(json_encode(["error" => "Plugin already installed"]));
46 | $url = get_plugin_install_path_from_name($name);
47 | $pluginfile = file_get_contents($url);
48 | if (!is_dir(UPATH."/data/tmp"))
49 | mkdir(UPATH."/data/tmp");
50 |
51 | $path = UPATH."/data/tmp/";
52 | $file = $path.md5(time()).".tmp";
53 | if (!file_put_contents($file, $pluginfile))
54 | die(json_encode(["error" => "Cannot write to directory: Need write permission"]));
55 |
56 | unset($pluginfile);
57 |
58 | $zip = new ZipArchive;
59 | $res = $zip->open($file);
60 | if ($res !== true) {
61 | unlink($file);
62 | die(json_encode(["error" => "Could not open file we just wrote lol"]));
63 | }
64 |
65 | // ensure we have no conflicts
66 | $extractPath = UPATH."/plugins/$name";
67 | // lazy upgrade for now.
68 | if (is_dir($extractPath))
69 | {
70 | deleteDirectory($extractPath);
71 | }
72 | mkdir($extractPath);
73 | $zip->extractTo($extractPath);
74 | $zip->close();
75 |
76 | //clear up our temp shit
77 | unset($zip);
78 | unlink($file);
79 | unset($res);
80 |
81 | // load it in the config
82 | $config['plugins'][] = $name;
83 | write_config();
84 |
85 | // wahey
86 | die(json_encode(['success' => "Installation was complete"]));
87 | }
88 |
89 | /**
90 | * @param string $name Name of plugin
91 | * @return NULL|string Path or NULL
92 | */
93 | function get_plugin_install_path_from_name($name)
94 | {
95 | global $config;
96 | $list = $config['third-party-plugins']['data']->list;
97 | foreach($list as $p)
98 | {
99 | if (!strcmp($p->name,$name))
100 | return $p->download_link;
101 | }
102 | return NULL;
103 | }
104 |
105 | function deleteDirectory($dir) {
106 | if (!file_exists($dir)) {
107 | return true;
108 | }
109 |
110 | if (!is_dir($dir)) {
111 | return unlink($dir);
112 | }
113 |
114 | foreach (scandir($dir) as $item) {
115 | if ($item == '.' || $item == '..') {
116 | continue;
117 | }
118 |
119 | if (!deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
120 | return false;
121 | }
122 |
123 | }
124 |
125 | return rmdir($dir);
126 | }
--------------------------------------------------------------------------------
/api/search.php:
--------------------------------------------------------------------------------
1 | "No search query"]);
11 | die;
12 | }
13 | $search_term = $_GET['search'];
14 | $users = $rpc->user()->getAll();
15 | $chans = $rpc->channel()->getAll(2);
16 | $logs = $rpc->log()->getAll();
17 | $servers = $rpc->server()->getAll();
18 | $server_bans = $rpc->serverban()->getAll();
19 | $excepts = $rpc->serverbanexception()->getAll();
20 | $spamfilter = $rpc->spamfilter()->getAll();
21 | $name_bans = $rpc->nameban()->getAll();
22 |
23 | $search_results = [
24 | "users" => [],
25 | "channels" => [],
26 | "channel_bans" => [],
27 | "channel_invites" => [],
28 | "channel_excepts" => [],
29 | "logs" => [],
30 | "server_bans" => [],
31 | "excepts" => [],
32 | "spamfilter" => [],
33 | "name_bans" => []
34 | ];
35 |
36 | function strcasestr($haystack, $needle) : bool
37 | {
38 | $needle = strtolower($needle);
39 | $haystack = strtolower($haystack);
40 | $needle = preg_quote($needle, '/');
41 | $needle = str_replace('\*', '.*', $needle);
42 | $pattern = '/.*' . $needle . '.*' . '/';
43 |
44 | return preg_match($pattern, $haystack) === 1;
45 | }
46 | foreach ($users as $u)
47 | {
48 | if (strcasestr($u->name,$search_term))
49 | {
50 | $o = (object)[];
51 | $o->name = $u->name;
52 | $o->data = $u->name;
53 | $o->label = "nick";
54 | $search_results['users'][] = $o;
55 | }
56 | if (strcasestr($u->details,$search_term))
57 | {
58 | $o = (object)[];
59 | $o->name = $u->name;
60 | $o->data = $u->details;
61 | $o->label = "userhost";
62 | $search_results['users'][] = $o;
63 | }
64 | if (strcasestr($u->user->realname,$search_term))
65 | {
66 | $o = (object)[];
67 | $o->name = $u->name;
68 | $o->data = $u->user->realname;
69 | $o->label = "GECOS";
70 | $search_results['users'][] = $o;
71 | }
72 | if (@strcasestr($u->user->account,$search_term))
73 | {
74 | $o = (object)[];
75 | $o->name = $u->name;
76 | $o->data = $u->account;
77 | $o->label = "account";
78 | $search_results['users'][] = $o;
79 | }
80 | if (isset($u->geoip))
81 | {
82 | error_log("It's set");
83 | if (@strcasestr($u->geoip->asn,$search_term))
84 | {
85 | $o = (object)[];
86 | $o->name = $u->name;
87 | $o->data = $u->geoip->asn;
88 | $o->label = "ASN";
89 | $search_results['users'][] = $o;
90 | }
91 | if (@strcasestr($u->geoip->asname,$search_term))
92 | {
93 | $o = (object)[];
94 | $o->name = $u->name;
95 | $o->data = $u->geoip->asname;
96 | $o->label = "ASN Name";
97 | $search_results['users'][] = $o;
98 | }
99 | if (@strcasestr($u->geoip->country_code,$search_term))
100 | {
101 | $o = (object)[];
102 | $o->name = $u->name;
103 | $o->data = $u->geoip->country_code;
104 | $o->label = "Country Code";
105 | $search_results['users'][] = $o;
106 | }
107 | }
108 | else{
109 | error_log(json_encode($u));
110 | }
111 | }
112 | foreach ($chans as $c)
113 | {
114 | if (strcasestr($c->name,$search_term))
115 | {
116 | $c->label = "channel name";
117 | $search_results['channels'][] = $c;
118 | }
119 | if (isset($c->topic) && strcasestr($c->topic,$search_term))
120 | {
121 | $c->label = "channel topic";
122 | $search_results['channels'][] = $c;
123 | }
124 | if (isset($c->bans))
125 | {
126 | foreach ($c->bans as $i)
127 | {
128 | if (!strcasestr($i->name, $search_term))
129 | continue;
130 |
131 | $new = (object)[];
132 | $new->name = $c->name;
133 | $new->topic = $i->name;
134 | $new->label = "ban (+b)";
135 | $search_results['channels'][] = $new;
136 | error_log("$new->label for $i->name");
137 | }
138 | }
139 | if (isset($c->ban_exemptions))
140 | {
141 | foreach ($c->ban_exemptions as $i)
142 | {
143 | if (!strcasestr($i->name, $search_term))
144 | continue;
145 |
146 | $new = (object)[];
147 | $new->name = $c->name;
148 | $new->topic = $i->name;
149 | $new->label = "exempt (+e)";
150 | $search_results['channels'][] = $new;
151 | error_log("$new->label for $i->name");
152 | }
153 | }
154 | if (isset($c->invite_exceptions))
155 | {
156 | foreach ($c->invite_exceptions as $i)
157 | {
158 | if (!strcasestr($i->name, $search_term))
159 | continue;
160 |
161 | $new = (object)[];
162 | $new->name = $c->name;
163 | $new->topic = $i->name;
164 | $new->label = "invite (+I)";
165 | $search_results['channels'][] = $new;
166 | }
167 | }
168 | }
169 | foreach ($logs as $l)
170 | if (strcasestr($l->msg,$search_term))
171 | $search_results['logs'][] = $l;
172 |
173 | foreach ($servers as $s)
174 | if (strcasestr($s->name, $search_term))
175 | $search_results['servers'][] = $s;
176 |
177 | foreach ($server_bans as $ban)
178 | {
179 |
180 | if (strstr($ban->type,$search_term) || strstr($ban->type_string,$search_term))
181 | {
182 | $o = (object)[];
183 | $o->label = $ban->type_string;
184 | $o->name = $ban->name;
185 | $o->data = $ban->reason;
186 | $search_results['server_bans'][] = $o;
187 | }
188 | elseif (strstr($ban->name,$search_term))
189 | {
190 | $o = (object)[];
191 | $o->label = $ban->type_string;
192 | $o->name = "$ban->name
";
193 | $o->data = $ban->reason;
194 | $search_results['server_bans'][] = $o;
195 | }
196 |
197 | elseif (strcasestr($ban->reason,$search_term))
198 | {
199 | $o = (object)[];
200 | $o->label = $ban->type_string." reason";
201 | $o->name = "$ban->name
";
202 | $o->data = $ban->reason;
203 | $search_results['server_bans'][] = $o;
204 | }
205 | }
206 |
207 | foreach ($excepts as $ban)
208 | {
209 | if (strstr($ban->type,$search_term) || strstr($ban->type_string,$search_term))
210 | {
211 | $o = (object)[];
212 | $o->label = $ban->type_string;
213 | $o->name = $ban->name;
214 | $o->data = $ban->reason;
215 | $search_results['excepts'][] = $o;
216 | }
217 | elseif (strstr($ban->name,$search_term))
218 | {
219 | $o = (object)[];
220 | $o->label = $ban->type_string;
221 | $o->name = "$ban->name
";
222 | $o->data = $ban->reason;
223 | $search_results['excepts'][] = $o;
224 | }
225 | elseif (strcasestr($ban->reason,$search_term))
226 | {
227 | $o = (object)[];
228 | $o->label = $ban->type_string." reason";
229 | $o->name = $ban->name;
230 | $o->data = $ban->reason;
231 | $search_results['excepts'][] = $o;
232 | }
233 | }
234 |
235 |
236 | foreach ($name_bans as $ban)
237 | {
238 | if (strstr($ban->type,$search_term) || strstr($ban->type_string,$search_term))
239 | {
240 | $o = (object)[];
241 | $o->label = $ban->type_string;
242 | $o->name = $ban->name;
243 | $o->data = $ban->reason;
244 | $search_results['name_bans'][] = $o;
245 | }
246 | elseif (strstr($ban->name,$search_term))
247 | {
248 | $o = (object)[];
249 | $o->label = $ban->type_string;
250 | $o->name = "$ban->name
";
251 | $o->data = $ban->reason;
252 | $search_results['name_bans'][] = $o;
253 | }
254 | elseif (strcasestr($ban->reason,$search_term))
255 | {
256 | $o = (object)[];
257 | $o->label = $ban->type_string." reason";
258 | $o->name = $ban->name;
259 | $o->data = $ban->reason;
260 | $search_results['name_bans'][] = $o;
261 | }
262 | }
263 |
264 | foreach ($spamfilter as $ban)
265 | {
266 | if (strstr($ban->type,$search_term) || strstr($ban->type_string,$search_term))
267 | {
268 | $o = (object)[];
269 | $o->label = $ban->type_string;
270 | $o->name = $ban->name;
271 | $o->data = $ban->reason;
272 | $search_results['spamfilter'][] = $o;
273 | }
274 | elseif (strstr($ban->name,$search_term))
275 | {
276 | $o = (object)[];
277 | $o->label = $ban->type_string;
278 | $o->name = "$ban->name
";
279 | $o->data = $ban->reason;
280 | $search_results['spamfilter'][] = $o;
281 | }
282 | elseif (strcasestr($ban->reason,$search_term))
283 | {
284 | $o = (object)[];
285 | $o->label = $ban->type_string." reason";
286 | $o->name = $ban->name;
287 | $o->data = $ban->reason;
288 | $search_results['spamfilter'][] = $o;
289 | }
290 | }
291 |
292 |
293 |
294 | echo json_encode($search_results);
--------------------------------------------------------------------------------
/api/server-bans.php:
--------------------------------------------------------------------------------
1 | serverban()->getAll();
10 |
11 | $out = [];
12 | foreach($tkls as $tkl)
13 | {
14 | $set_in_config = ((isset($tkl->set_in_config) && $tkl->set_in_config) || ($tkl->set_by == "-config-")) ? true : false;
15 | $set_by = $set_in_config ? "Config" : show_nick_only(htmlspecialchars($tkl->set_by));
16 | $select = '';
17 | if (!$set_in_config)
18 | $select = "";
19 |
20 | $out[] = [
21 | "Select" => $select,
22 | "Mask" => htmlspecialchars($tkl->name),
23 | "Type" => $tkl->type_string,
24 | "Duration" => $tkl->duration_string,
25 | "Reason" => htmlspecialchars($tkl->reason),
26 | "Set By" => $set_by,
27 | "Set On" => $tkl->set_at_string,
28 | "Expires" => $tkl->expire_at_string,
29 | ];
30 | }
31 |
32 | function custom_sort($a,$b)
33 | {
34 | return strcmp(strtoupper($a["Mask"]), strtoupper($b["Mask"]));
35 | }
36 |
37 | usort($out, "custom_sort");
38 |
39 | echo json_encode($out);
40 |
--------------------------------------------------------------------------------
/api/set_rpc_server.php:
--------------------------------------------------------------------------------
1 | "Incorrect parameters"]));
7 |
8 | foreach(array("tls_verify","host","port","user","password","edit_existing") as $k)
9 | {
10 | if (!isset($_POST[$k]))
11 | die("MISSING: $k");
12 | ${$k} = $_POST[$k];
13 | }
14 |
15 | if ($tls_verify == "false")
16 | $tls_verify = false;
17 | elseif ($tls_verify == "true")
18 | $tls_verify = true;
19 |
20 | if (($edit_existing) && ($password == "****************"))
21 | {
22 | /* If editing existing and password unchanged,
23 | * try to look up existing password.
24 | */
25 | if (isset($config["unrealircd"][$edit_existing]))
26 | {
27 | $password = $config["unrealircd"][$edit_existing]["rpc_password"];
28 | if (str_starts_with($password, "secret:"))
29 | $password = secret_decrypt($password);
30 | }
31 | }
32 |
33 | try {
34 | $rpc = new UnrealIRCd\Connection
35 | (
36 | "wss://$host:$port",
37 | "$user:$password",
38 | ["tls_verify" => $tls_verify]
39 | );
40 | }
41 | catch (Exception $e)
42 | {
43 | die(json_encode(["error" => "Unable to connect to UnrealIRCd: ".$e->getMessage()]));
44 | }
45 |
46 | die(json_encode(["success" => "Successfully connected"]));
47 |
--------------------------------------------------------------------------------
/api/timeout.php:
--------------------------------------------------------------------------------
1 | 'active']));
8 | else
9 | {
10 | session_destroy();
11 | die(json_encode(['session' => 'none']));
12 | }
--------------------------------------------------------------------------------
/api/upgrade.php:
--------------------------------------------------------------------------------
1 | error)
10 | {
11 | error_log("Couldn't create dir.");
12 | return;
13 | }
14 | error_log("Checking for upgrade");
15 | $upgrade->checkForNew();
16 | if (Upgrade::$upgrade_available)
17 | {
18 | error_log("Upgrade available, downloading and installing");
19 | if (!$upgrade->downloadUpgradeZip())
20 | return error_log($upgrade->error);
21 |
22 | else if (!$upgrade->extractZip())
23 | return error_log($upgrade->error);
24 |
25 | $upgrade->cleanupOldFiles();
26 |
27 | if(!$upgrade->extractToWebdir())
28 | return error_log($upgrade->error);
29 |
30 | $upgrade->cleanupDownloadFiles();
31 | error_log("Upgrade was successful!");
32 | }
33 | else
34 | error_log("No upgrade available");
--------------------------------------------------------------------------------
/api/users.php:
--------------------------------------------------------------------------------
1 | user()->getAll();
11 |
12 | $out = [];
13 | foreach($users as $user)
14 | {
15 | $isBot = (strpos($user->user->modes, "B") !== false) ? ' Bot' : "";
16 | $nick = htmlspecialchars($user->name).$isBot;
17 |
18 | $country = isset($user->geoip->country_code) ? '
'.htmlspecialchars($user->geoip->country_code) : "";
19 |
20 | if ($user->hostname == $user->ip)
21 | $hostip = $user->ip;
22 | else if ($user->ip == null)
23 | $hostip = $user->hostname;
24 | else
25 | $hostip = $user->hostname . " (".$user->ip.")";
26 | $hostip = htmlspecialchars($hostip);
27 |
28 | $account = (isset($user->user->account)) ? "user->account."\">".htmlspecialchars($user->user->account)."" : 'None';
29 | $oper = (isset($user->user->operlogin)) ? $user->user->operlogin." ".$user->user->operclass."" : "";
30 | if (!strlen($oper))
31 | $oper = (strpos($user->user->modes, "S") !== false) ? 'Services Bot' : "";
32 | $servername = $user->user->servername;
33 | $reputation = $user->user->reputation;
34 |
35 | $nick = "id."\">$nick";
36 |
37 | $out[] = [
38 | "Select" => "", /* yeah ridiculous to have here in this file and the feed ;) */
39 | "Nick" => $nick,
40 | "Country" => $country,
41 | "Host/IP" => $hostip,
42 | "Account" => $account,
43 | "Oper" => $oper,
44 | "Connected to" => $servername,
45 | "Reputation" => $reputation,
46 | ];
47 | }
48 |
49 | function custom_sort($a,$b)
50 | {
51 | return strcmp(strtoupper($a["Nick"]), strtoupper($b["Nick"]));
52 | }
53 |
54 | usort($out, "custom_sort");
55 |
56 | echo json_encode($out);
57 |
--------------------------------------------------------------------------------
/channels/index.php:
--------------------------------------------------------------------------------
1 |
14 | Channels Overview
15 |
16 |
17 |
18 |
19 | Name |
20 | Users |
21 | Modes |
22 | Topic |
23 | Created |
24 |
25 |
26 |
27 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "unrealircd/unrealircd-rpc": "dev-main",
4 | "phpmailer/phpmailer": "^6.7"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/config/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except these files:
4 | !.gitignore
5 | !index.php
6 | !compat.php
7 |
--------------------------------------------------------------------------------
/config/.htaccess:
--------------------------------------------------------------------------------
1 | Order allow,deny
2 | Deny from all
--------------------------------------------------------------------------------
/config/compat.php:
--------------------------------------------------------------------------------
1 | new config */
4 |
5 | /* Base url */
6 | if (defined('BASE_URL'))
7 | $config["base_url"] = BASE_URL;
8 |
9 | /* UnrealIRCd settings */
10 | if (defined('UNREALIRCD_RPC_USER'))
11 | $config["unrealircd"]["rpc_user"] = UNREALIRCD_RPC_USER;
12 | if (defined('UNREALIRCD_RPC_PASSWORD'))
13 | $config["unrealircd"]["rpc_password"] = UNREALIRCD_RPC_PASSWORD;
14 | if (defined('UNREALIRCD_HOST'))
15 | $config["unrealircd"]["host"] = UNREALIRCD_HOST;
16 | if (defined('UNREALIRCD_PORT'))
17 | $config["unrealircd"]["port"] = UNREALIRCD_PORT;
18 | if (defined('UNREALIRCD_SSL_VERIFY'))
19 | $config["unrealircd"]["tls_verify_cert"] = UNREALIRCD_SSL_VERIFY;
20 |
21 | /* Debug */
22 | if (defined('UNREALIRCD_DEBUG'))
23 | $config["debug"] = UNREALIRCD_DEBUG;
24 |
25 | /* Plugins */
26 | if (defined('PLUGINS'))
27 | $config["plugins"] = PLUGINS;
28 |
29 | /* SQL settings */
30 | if (defined('SQL_IP'))
31 | $config["mysql"]["host"] = SQL_IP;
32 | if (defined('SQL_DATABASE'))
33 | $config["mysql"]["database"] = SQL_DATABASE;
34 | if (defined('SQL_USERNAME'))
35 | $config["mysql"]["username"] = SQL_USERNAME;
36 | if (defined('SQL_PASSWORD'))
37 | $config["mysql"]["password"] = SQL_PASSWORD;
38 | if (defined('SQL_PREFIX'))
39 | $config["mysql"]["table_prefix"] = SQL_PREFIX;
40 |
41 | /* DNS Blacklist */
42 | if (defined('DNSBL'))
43 | $config["dnsbl"] = DNSBL;
44 |
45 | /* Mailer */
46 | if (defined('EMAIL_SETTINGS'))
47 | $config["smtp"] = EMAIL_SETTINGS;
48 |
--------------------------------------------------------------------------------
/config/index.php:
--------------------------------------------------------------------------------
1 | tbody>tr:nth-child(even)>td,
160 | .table-striped>tbody>tr:nth-child(even)>th {
161 | background-color: #a5a5a549;
162 | }
163 | .table-striped>tbody>tr:nth-child(odd)>td,
164 | .table-striped>tbody>tr:nth-child(odd)>th {
165 | background-color: #ffffff;
166 | }
167 |
168 | /* Small screens (max-width: 576px) */
169 | @media (max-width: 576px) {
170 | /* Change the font size for all headings */
171 | h1, h2, h3, h4, h5, h6 {
172 | font-size: 18px;
173 | }
174 |
175 | /* Hide the sidebar */
176 | .sidebar {
177 | display: none;
178 | }
179 |
180 | /* Make the main content take up the full width of the screen */
181 | .main-content {
182 | width: 100%;
183 | }
184 | }
185 |
186 | /* Medium screens (min-width: 577px) and (max-width: 768px) */
187 | @media (min-width: 577px) and (max-width: 768px) {
188 | /* Change the font size for all headings */
189 |
190 | }
191 |
192 | /* Large screens (min-width: 769px) */
193 | @media (min-width: 769px) {
194 | /* CSS rules for large screens go here */
195 | }
196 | /* Portrait screens (max-width: 576px) */
197 | @media (max-width: 576px) and (max-height: 812px) {
198 | /* CSS rules for portrait screens go here */
199 | }
200 |
201 | /* Landscape screens (min-width: 577px) and (max-height: 812px) */
202 | @media (min-width: 577px) and (max-height: 812px) {
203 | /* CSS rules for landscape screens go here */
204 | }
205 |
206 | .container, .container-fluid, .container-lg, .container-md, .container-sm, .container-xl {
207 | width: inherit;
208 | }
209 |
210 | .card-header .badge {
211 | top:-10px;
212 | left:20px;
213 | }
214 |
215 | .card-header i.fa {
216 | line-height: 74px;
217 | }
218 |
219 | #top-country ul {
220 | list-style: none;
221 | }
222 |
223 | #top-country li {
224 | padding: 5px;
225 | display: inline-block;
226 | margin: 9px;
227 | border: solid 1px #ccc;
228 | }
229 |
230 | #top-country li .drag {
231 | text-align: center;
232 | }
233 |
234 | #top-country li .count {
235 | text-align: center;
236 | font-size: 2.2rem;
237 | }
238 |
239 | #top-country li .count span {
240 | display:block;
241 | text-align: center;
242 | font-size: 1rem;
243 | }
244 |
245 | /* Show version and 4-icons "footer" at bottom-left,
246 | * if screen height is sufficient.
247 | */
248 | @media only screen and (min-height: 650px) and (min-width: 768px) {
249 | footer {
250 | float: left;
251 | width: 160px;
252 | height: 60px;
253 | display: block !important;
254 | }
255 |
256 | #sidebarlol {
257 | height: calc(100% - 60px - 50px);
258 | }
259 | }
260 |
261 | /* Virtual link - hover effect (eg on Server Bans) */
262 | .virtuallink {
263 | text-decoration: none;
264 | }
265 | .virtuallink:hover {
266 | text-decoration: underline;
267 | cursor: pointer;
268 | }
269 |
270 | /* TD that may wrap - eg for Reason / Topic */
271 | .tdwrap {
272 | white-space: normal !important;
273 | }
274 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except these files:
4 | !.gitignore
5 | !index.php
6 |
--------------------------------------------------------------------------------
/data/.htaccess:
--------------------------------------------------------------------------------
1 | Order allow,deny
2 | Deny from all
--------------------------------------------------------------------------------
/data/index.php:
--------------------------------------------------------------------------------
1 | \n".
46 | "Either restore your previous config/config.php or start with a fresh database.
\n");
47 | die;
48 | }
49 |
50 | $user = unreal_get_current_user();
51 | if ($user)
52 | {
53 | /* Set issuer for all the RPC commands */
54 | $options['issuer'] = $user->username;
55 | }
56 |
57 | /* Connect now */
58 | try {
59 | $rpc = new UnrealIRCd\Connection
60 | (
61 | "wss://$host:$port",
62 | "$rpc_user:$rpc_password",
63 | $options
64 | );
65 | }
66 | catch (Exception $e)
67 | {
68 | if ($is_api_page)
69 | return;
70 | Message::Fail("Unable to connect to UnrealIRCd: ".$e->getMessage() . "
".
71 | "Verify that the connection details from Settings - RPC Servers match the ones in UnrealIRCd ".
72 | "and that UnrealIRCd is up and running");
73 | throw $e;
74 | }
75 | }
76 |
77 | connect_to_ircd();
78 |
--------------------------------------------------------------------------------
/inc/defines.php:
--------------------------------------------------------------------------------
1 |
2 |
53 |