├── .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 | 29 | 47 | 62 | "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 |
108 |
109 | 110 |
111 |
ifInstalledLabel($p->name); $this->ifCompatible($p) ?>
112 |

title ?>

113 | By contact" ?>" target="_blank">author ?> 114 |
115 |
116 |
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 | 20 | 21 | 22 | 23 | 24 | 25 |
NameUsersModesTopicCreated
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 | 54 | 55 | -------------------------------------------------------------------------------- /js/bs-toast.js: -------------------------------------------------------------------------------- 1 | 2 | /* Popup notifications */ 3 | 4 | function generate_notif(title, body) 5 | { 6 | /* generate a random number between 1000 and 90000 to use as an id */ 7 | const min = 1000; 8 | const max = 90000; 9 | const id = Math.floor(Math.random() * (max - min + 1)) + min; 10 | 11 | const toast = document.createElement('div'); 12 | toast.classList.add('toast', 'hide'); 13 | toast.id = 'toast' + id; 14 | toast.role = 'alert'; 15 | toast.ariaLive = 'assertive'; 16 | toast.ariaAtomic = 'true'; 17 | toast.setAttribute('data-delay', '10000'); 18 | 19 | const header = document.createElement('div'); 20 | header.classList.add('toast-header'); 21 | 22 | const theTitle = document.createElement('strong'); 23 | theTitle.classList.add('mr-auto'); 24 | theTitle.textContent = title; 25 | 26 | const notiftime = document.createElement('div'); 27 | notiftime.classList.add('badge', 'rounded-pill', 'badge-primary', 'ml-1'); 28 | notiftime.textContent = 'Just now'; // always just now I think right :D 29 | 30 | const closebutton = document.createElement('button'); 31 | closebutton.type = 'button'; 32 | closebutton.classList.add('ml-2', 'mb-1', 'close'); 33 | closebutton.setAttribute('data-dismiss', 'toast'); 34 | closebutton.ariaLabel = 'Close'; 35 | 36 | const closebuttonspan = document.createElement('span'); 37 | closebuttonspan.ariaHidden = 'true'; 38 | closebuttonspan.innerHTML = "×"; 39 | 40 | const toastbody = document.createElement('div'); 41 | toastbody.classList.add('toast-body'); 42 | toastbody.textContent = body; 43 | 44 | 45 | /* put it all together */ 46 | closebutton.appendChild(closebuttonspan); 47 | header.appendChild(theTitle); 48 | header.appendChild(notiftime); 49 | header.appendChild(closebutton); 50 | toast.appendChild(header); 51 | toast.appendChild(toastbody); 52 | document.getElementById('toaster').append(toast); 53 | 54 | $('#' + toast.id).toast('show'); 55 | } 56 | -------------------------------------------------------------------------------- /js/datatables-ellipsis.js: -------------------------------------------------------------------------------- 1 | /*! © SpryMedia Ltd - datatables.net/license */ 2 | 3 | (function( factory ){ 4 | if ( typeof define === 'function' && define.amd ) { 5 | // AMD 6 | define( ['jquery', 'datatables.net'], function ( $ ) { 7 | return factory( $, window, document ); 8 | } ); 9 | } 10 | else if ( typeof exports === 'object' ) { 11 | // CommonJS 12 | var jq = require('jquery'); 13 | var cjsRequires = function (root, $) { 14 | if ( ! $.fn.dataTable ) { 15 | require('datatables.net')(root, $); 16 | } 17 | }; 18 | 19 | if (typeof window !== 'undefined') { 20 | module.exports = function (root, $) { 21 | if ( ! root ) { 22 | // CommonJS environments without a window global must pass a 23 | // root. This will give an error otherwise 24 | root = window; 25 | } 26 | 27 | if ( ! $ ) { 28 | $ = jq( root ); 29 | } 30 | 31 | cjsRequires( root, $ ); 32 | return factory( $, root, root.document ); 33 | }; 34 | } 35 | else { 36 | cjsRequires( window, jq ); 37 | module.exports = factory( jq, window, window.document ); 38 | } 39 | } 40 | else { 41 | // Browser 42 | factory( jQuery, window, document ); 43 | } 44 | }(function( $, window, document, undefined ) { 45 | 'use strict'; 46 | var DataTable = $.fn.dataTable; 47 | 48 | 49 | /** 50 | * This data rendering helper method can be useful for cases where you have 51 | * potentially large data strings to be shown in a column that is restricted by 52 | * width. The data for the column is still fully searchable and sortable, but if 53 | * it is longer than a give number of characters, it will be truncated and 54 | * shown with ellipsis. A browser provided tooltip will show the full string 55 | * to the end user on mouse hover of the cell. 56 | * 57 | * This function should be used with the `dt-init columns.render` configuration 58 | * option of DataTables. 59 | * 60 | * It accepts three parameters: 61 | * 62 | * 1. `-type integer` - The number of characters to restrict the displayed data 63 | * to. 64 | * 2. `-type boolean` (optional - default `false`) - Indicate if the truncation 65 | * of the string should not occur in the middle of a word (`true`) or if it 66 | * can (`false`). This can allow the display of strings to look nicer, at the 67 | * expense of showing less characters. 68 | * 2. `-type boolean` (optional - default `false`) - Escape HTML entities 69 | * (`true`) or not (`false` - default). 70 | * 71 | * @name ellipsis 72 | * @summary Restrict output data to a particular length, showing anything 73 | * longer with ellipsis and a browser provided tooltip on hover. 74 | * @author [Allan Jardine](http://datatables.net) 75 | * @requires DataTables 1.10+ 76 | * 77 | * @returns {Number} Calculated average 78 | * 79 | * @example 80 | * // Restrict a column to 17 characters, don't split words 81 | * $('#example').DataTable( { 82 | * columnDefs: [ { 83 | * targets: 1, 84 | * render: DataTable.render.ellipsis( 17, true ) 85 | * } ] 86 | * } ); 87 | * 88 | * @example 89 | * // Restrict a column to 10 characters, do split words 90 | * $('#example').DataTable( { 91 | * columnDefs: [ { 92 | * targets: 2, 93 | * render: DataTable.render.ellipsis( 10 ) 94 | * } ] 95 | * } ); 96 | */ 97 | DataTable.render.ellipsis = function (cutoff, wordbreak, escapeHtml) { 98 | var esc = function (t) { 99 | return ('' + t) 100 | .replace(/&/g, '&') 101 | .replace(//g, '>') 103 | .replace(/"/g, '"'); 104 | }; 105 | return function (d, type, row) { 106 | // Order, search and type get the original data 107 | if (type !== 'display') { 108 | return d; 109 | } 110 | if (typeof d !== 'number' && typeof d !== 'string') { 111 | if (escapeHtml) { 112 | return esc(d); 113 | } 114 | return d; 115 | } 116 | d = d.toString(); // cast numbers 117 | if (d.length <= cutoff) { 118 | if (escapeHtml) { 119 | return esc(d); 120 | } 121 | return d; 122 | } 123 | var shortened = d.substr(0, cutoff - 1); 124 | // Find the last white space character in the string 125 | if (wordbreak) { 126 | shortened = shortened.replace(/\s([^\s]*)$/, ''); 127 | } 128 | // Protect against uncontrolled HTML input 129 | if (escapeHtml) { 130 | shortened = esc(shortened); 131 | } 132 | return ('' + 135 | shortened + 136 | '…'); 137 | }; 138 | }; 139 | 140 | 141 | return DataTable; 142 | })); 143 | -------------------------------------------------------------------------------- /js/datatables-natural-sort.js: -------------------------------------------------------------------------------- 1 | /*! © SpryMedia Ltd, Jim Palmer, Michael Buehler, Mike Grier, Clint Priest, Kyle Adams, guillermo - datatables.net/license */ 2 | 3 | (function( factory ){ 4 | if ( typeof define === 'function' && define.amd ) { 5 | // AMD 6 | define( ['jquery', 'datatables.net'], function ( $ ) { 7 | return factory( $, window, document ); 8 | } ); 9 | } 10 | else if ( typeof exports === 'object' ) { 11 | // CommonJS 12 | var jq = require('jquery'); 13 | var cjsRequires = function (root, $) { 14 | if ( ! $.fn.dataTable ) { 15 | require('datatables.net')(root, $); 16 | } 17 | }; 18 | 19 | if (typeof window !== 'undefined') { 20 | module.exports = function (root, $) { 21 | if ( ! root ) { 22 | // CommonJS environments without a window global must pass a 23 | // root. This will give an error otherwise 24 | root = window; 25 | } 26 | 27 | if ( ! $ ) { 28 | $ = jq( root ); 29 | } 30 | 31 | cjsRequires( root, $ ); 32 | return factory( $, root, root.document ); 33 | }; 34 | } 35 | else { 36 | cjsRequires( window, jq ); 37 | module.exports = factory( jq, window, window.document ); 38 | } 39 | } 40 | else { 41 | // Browser 42 | factory( jQuery, window, document ); 43 | } 44 | }(function( $, window, document, undefined ) { 45 | 'use strict'; 46 | var DataTable = $.fn.dataTable; 47 | 48 | 49 | /** 50 | * Data can often be a complicated mix of numbers and letters (file names 51 | * are a common example) and sorting them in a natural manner is quite a 52 | * difficult problem. 53 | * 54 | * Fortunately a deal of work has already been done in this area by other 55 | * authors - the following plug-in uses the [naturalSort() function by Jim 56 | * Palmer](http://www.overset.com/2008/09/01/javascript-natural-sort-algorithm-with-unicode-support) to provide natural sorting in DataTables. 57 | * 58 | * @name Natural sorting 59 | * @summary Sort data with a mix of numbers and letters _naturally_. 60 | * @author [Jim Palmer](http://www.overset.com/2008/09/01/javascript-natural-sort-algorithm-with-unicode-support) 61 | * @author [Michael Buehler] (https://github.com/AnimusMachina) 62 | * 63 | * @example 64 | * $('#example').dataTable( { 65 | * columnDefs: [ 66 | * { type: 'natural', targets: 0 } 67 | * ] 68 | * } ); 69 | * 70 | * Html can be stripped from sorting by using 'natural-nohtml' such as 71 | * 72 | * $('#example').dataTable( { 73 | * columnDefs: [ 74 | * { type: 'natural-nohtml', targets: 0 } 75 | * ] 76 | * } ); 77 | * 78 | */ 79 | /* 80 | * Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license 81 | * Author: Jim Palmer (based on chunking idea from Dave Koelle) 82 | * Contributors: Mike Grier (mgrier.com), Clint Priest, Kyle Adams, guillermo 83 | * See: http://js-naturalsort.googlecode.com/svn/trunk/naturalSort.js 84 | */ 85 | function naturalSort(a, b, html) { 86 | var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?%?$|^0x[0-9a-f]+$|[0-9]+)/gi, sre = /(^[ ]*|[ ]*$)/g, dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, hre = /^0x[0-9a-f]+$/i, ore = /^0/, htmre = /(<([^>]+)>)/gi, 87 | // convert all to strings and trim() 88 | x = a.toString().replace(sre, '') || '', y = b.toString().replace(sre, '') || ''; 89 | // remove html from strings if desired 90 | if (!html) { 91 | x = x.replace(htmre, ''); 92 | y = y.replace(htmre, ''); 93 | } 94 | // chunk/tokenize 95 | var xN = x 96 | .replace(re, '\0$1\0') 97 | .replace(/\0$/, '') 98 | .replace(/^\0/, '') 99 | .split('\0'), yN = y 100 | .replace(re, '\0$1\0') 101 | .replace(/\0$/, '') 102 | .replace(/^\0/, '') 103 | .split('\0'), 104 | // numeric, hex or date detection 105 | xD = parseInt(x.match(hre), 10) || 106 | (xN.length !== 1 && x.match(dre) && Date.parse(x)), yD = parseInt(y.match(hre), 10) || 107 | (xD && y.match(dre) && Date.parse(y)) || 108 | null; 109 | // first try and sort Hex codes or Dates 110 | if (yD) { 111 | if (xD < yD) { 112 | return -1; 113 | } 114 | else if (xD > yD) { 115 | return 1; 116 | } 117 | } 118 | // natural sorting through split numeric strings and default strings 119 | for (var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++) { 120 | // find floats not starting with '0', string or 0 if not defined (Clint Priest) 121 | var oFxNcL = (!(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc])) || xN[cLoc] || 0; 122 | var oFyNcL = (!(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc])) || yN[cLoc] || 0; 123 | // handle numeric vs string comparison - number < string - (Kyle Adams) 124 | if (isNaN(oFxNcL) !== isNaN(oFyNcL)) { 125 | return isNaN(oFxNcL) ? 1 : -1; 126 | } 127 | // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2' 128 | else if (typeof oFxNcL !== typeof oFyNcL) { 129 | oFxNcL += ''; 130 | oFyNcL += ''; 131 | } 132 | if (oFxNcL < oFyNcL) { 133 | return -1; 134 | } 135 | if (oFxNcL > oFyNcL) { 136 | return 1; 137 | } 138 | } 139 | return 0; 140 | } 141 | DataTable.ext.order['natural-asc'] = function (a, b) { 142 | return naturalSort(a, b, true); 143 | }; 144 | DataTable.ext.order['natural-desc'] = function (a, b) { 145 | return naturalSort(a, b, true) * -1; 146 | }; 147 | DataTable.ext.order['natural-nohtml-asc'] = function (a, b) { 148 | return naturalSort(a, b, false); 149 | }; 150 | DataTable.ext.order['natural-nohtml-asc'] = function (a, b) { 151 | return naturalSort(a, b, false) * -1; 152 | }; 153 | DataTable.ext.order['natural-ci-asc'] = function (a, b) { 154 | a = a.toString().toLowerCase(); 155 | b = b.toString().toLowerCase(); 156 | return naturalSort(a, b, true); 157 | }; 158 | DataTable.ext.order['natural-ci-asc'] = function (a, b) { 159 | a = a.toString().toLowerCase(); 160 | b = b.toString().toLowerCase(); 161 | return naturalSort(a, b, true) * -1; 162 | }; 163 | 164 | 165 | return DataTable; 166 | })); 167 | -------------------------------------------------------------------------------- /js/index.php: -------------------------------------------------------------------------------- 1 | 39 | { 40 | rclickmenu.classList.remove("visible"); 41 | }); 42 | 43 | 44 | document.addEventListener("contextmenu", (event) => 45 | { 46 | event.preventDefault(); 47 | click_target = event.target; 48 | 49 | rclickmenu.classList.remove("visible"); // hide it if it was already elsweyr 50 | var { clientX: mouseX, clientY: mouseY } = event; 51 | 52 | rclickmenu.style.top = `${mouseY}px`; 53 | rclickmenu.style.left = `${mouseX}px`; 54 | 55 | /* "Copy" option */ 56 | selection = window.getSelection().toString(); 57 | 58 | if (selection.length == 0 || !can_clipboard()) 59 | document.getElementById('rclick_opt1').style.display = 'none'; 60 | 61 | else if (can_clipboard()) 62 | document.getElementById('rclick_opt1').style.display = ''; 63 | 64 | /* Check if the browser supports pasting */ 65 | if (!can_clipboard() || (!click_target || click_target.tagName.toLowerCase() !== "input")) 66 | document.getElementById('rclick_opt2').style.display = 'none'; 67 | 68 | else if (click_target && click_target.tagName.toLowerCase() === "input") 69 | document.getElementById('rclick_opt2').style.display = ''; 70 | 71 | setTimeout(() => { rclickmenu.classList.add("visible"); }); 72 | }); 73 | document.addEventListener('keydown', (event) => { 74 | if (event.key === 'Escape') 75 | { 76 | rclickmenu.classList.remove("visible"); 77 | } 78 | }); -------------------------------------------------------------------------------- /js/service-worker.js: -------------------------------------------------------------------------------- 1 | self.addEventListener("install", (event) => { 2 | console.log("Service Worker installing..."); 3 | event.waitUntil( 4 | self.skipWaiting().then(() => { 5 | console.log("Service Worker installed and skipWaiting called."); 6 | }).catch((error) => { 7 | console.error("Error during Service Worker installation:", error); 8 | }) 9 | ); 10 | }); 11 | 12 | self.addEventListener("activate", (event) => { 13 | console.log("Service Worker activated!"); 14 | event.waitUntil( 15 | self.clients.claim().then(() => { 16 | console.log("Service Worker claimed clients."); 17 | }).catch((error) => { 18 | console.error("Error during Service Worker activation:", error); 19 | }) 20 | ); 21 | }); 22 | 23 | self.addEventListener("fetch", (event) => { 24 | console.log("Intercepting request for:", event.request.url); 25 | event.respondWith( 26 | fetch(event.request).catch((error) => { 27 | console.error("Fetch failed for:", event.request.url, "Error:", error); 28 | }) 29 | ); 30 | }); -------------------------------------------------------------------------------- /js/unrealircd-admin.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* TKL (un)select all checkbox */ 4 | function toggle_tkl(source) { 5 | checkboxes = document.getElementsByName("tklch[]"); 6 | for (var i = 0, n = checkboxes.length; i < n; i++) { 7 | checkboxes[i].checked = source.checked; 8 | } 9 | } 10 | 11 | 12 | /* TKL (un)select all checkbox */ 13 | function toggle_user(source) { 14 | checkboxes = document.getElementsByName("userch[]"); 15 | for (var i = 0, n = checkboxes.length; i < n; i++) { 16 | checkboxes[i].checked = source.checked; 17 | } 18 | } 19 | 20 | 21 | /* TKL (un)select all checkbox */ 22 | function toggle_server(source) { 23 | checkboxes = document.getElementsByName("serverch[]"); 24 | for (var i = 0, n = checkboxes.length; i < n; i++) { 25 | checkboxes[i].checked = source.checked; 26 | } 27 | } 28 | 29 | /* TKL (un)select all checkbox */ 30 | function toggle_sf(source) { 31 | checkboxes = document.getElementsByName("sf[]"); 32 | for (var i = 0, n = checkboxes.length; i < n; i++) { 33 | checkboxes[i].checked = source.checked; 34 | } 35 | } 36 | 37 | function toggle_chanbans(source) { 38 | checkboxes = document.getElementsByName("cb_checkboxes[]"); 39 | for (var i = 0, n = checkboxes.length; i < n; i++) { 40 | checkboxes[i].checked = source.checked; 41 | } 42 | } 43 | 44 | function toggle_chanexs(source) { 45 | checkboxes = document.getElementsByName("ce_checkboxes[]"); 46 | for (var i = 0, n = checkboxes.length; i < n; i++) { 47 | checkboxes[i].checked = source.checked; 48 | } 49 | } 50 | 51 | function toggle_chaninvs(source) { 52 | checkboxes = document.getElementsByName("ci_checkboxes[]"); 53 | for (var i = 0, n = checkboxes.length; i < n; i++) { 54 | checkboxes[i].checked = source.checked; 55 | } 56 | } 57 | 58 | function toggle_checkbox(source) { 59 | checkboxes = document.getElementsByName("checkboxes[]"); 60 | for (var i = 0, n = checkboxes.length; i < n; i++) { 61 | checkboxes[i].checked = source.checked; 62 | } 63 | } 64 | 65 | /* Popup notifications */ 66 | 67 | function generate_notif(title, body) 68 | { 69 | /* generate a random number between 1000 and 90000 to use as an id */ 70 | const min = 1000; 71 | const max = 90000; 72 | const id = Math.floor(Math.random() * (max - min + 1)) + min; 73 | 74 | const toast = document.createElement('div'); 75 | toast.classList.add('toast', 'hide'); 76 | toast.id = 'toast' + id; 77 | toast.role = 'alert'; 78 | toast.ariaLive = 'assertive'; 79 | toast.ariaAtomic = 'true'; 80 | toast.setAttribute('data-delay', '10000'); 81 | 82 | const header = document.createElement('div'); 83 | header.classList.add('toast-header'); 84 | 85 | const theTitle = document.createElement('strong'); 86 | theTitle.classList.add('mr-auto'); 87 | theTitle.textContent = title; 88 | 89 | const notiftime = document.createElement('div'); 90 | notiftime.classList.add('badge', 'rounded-pill', 'badge-primary', 'ml-1'); 91 | notiftime.textContent = 'Just now'; // always just now I think right :D 92 | 93 | const closebutton = document.createElement('button'); 94 | closebutton.type = 'button'; 95 | closebutton.classList.add('ml-2', 'mb-1', 'close'); 96 | closebutton.setAttribute('data-dismiss', 'toast'); 97 | closebutton.ariaLabel = 'Close'; 98 | 99 | const closebuttonspan = document.createElement('span'); 100 | closebuttonspan.ariaHidden = 'true'; 101 | closebuttonspan.innerHTML = "×"; 102 | 103 | const toastbody = document.createElement('div'); 104 | toastbody.classList.add('toast-body'); 105 | toastbody.textContent = body; 106 | 107 | 108 | /* put it all together */ 109 | closebutton.appendChild(closebuttonspan); 110 | header.appendChild(theTitle); 111 | header.appendChild(notiftime); 112 | header.appendChild(closebutton); 113 | toast.appendChild(header); 114 | toast.appendChild(toastbody); 115 | document.getElementById('toaster').append(toast); 116 | 117 | $('#' + toast.id).toast('show'); 118 | } 119 | 120 | function StreamNotifs(e) 121 | { 122 | var data; 123 | try { 124 | data = JSON.parse(e.data); 125 | } catch(e) { 126 | return; 127 | } 128 | title = data.subsystem + '.' + data.event_id; 129 | msg = data.msg; 130 | generate_notif(title, msg); 131 | } 132 | 133 | function StartStreamNotifs(url) 134 | { 135 | if (!!window.EventSource) { 136 | var source = new EventSource(url); 137 | source.addEventListener('message', StreamNotifs, false); 138 | } 139 | } 140 | 141 | /* Log streamer */ 142 | function NewLogEntry(e) 143 | { 144 | var data; 145 | try { 146 | data = JSON.parse(e.data); 147 | } catch(e) { 148 | return; 149 | } 150 | 151 | if (data.sync_option != "sync_now") 152 | { 153 | var sync = (data.sync_option == "no_sync") ? false : true; 154 | delete data.sync_option; 155 | 156 | data_list_table.row.add({ 157 | 'Time':data.timestamp, 158 | 'Level':data.level, 159 | 'Subsystem':data.subsystem, 160 | 'Event':data.event_id, 161 | 'Message':data.msg, 162 | 'Raw':JSON.stringify(data)}); 163 | 164 | if (!sync) 165 | return; 166 | } 167 | data_list_table.draw(true); 168 | data_list_table.rows().invalidate(); 169 | data_list_table.searchPanes.rebuildPane(); 170 | } 171 | 172 | function StartLogStream(url) 173 | { 174 | var source = new EventSource(url); 175 | source.addEventListener('message', NewLogEntry, false); 176 | } 177 | 178 | -------------------------------------------------------------------------------- /login/index.php: -------------------------------------------------------------------------------- 1 | id) && $user->password_verify($_POST['password'], $hash_needs_updating)) 40 | { 41 | /* SUCCESSFUL LOGIN */ 42 | if ($hash_needs_updating) 43 | { 44 | /* Set password again so it is freshly hashed */ 45 | $hash = PanelUser::password_hash($_POST['password']); 46 | $ar = ["update_pass_conf"=>$hash]; 47 | $user->update_core_info($ar); 48 | unset($ar); 49 | unset($hash); 50 | } 51 | panel_start_session($user); 52 | $_SESSION['id'] = $user->id; 53 | $user->add_meta("last_login", date("Y-m-d H:i:s")); 54 | Hook::run(HOOKTYPE_USER_LOGIN, $user); 55 | 56 | // ensure we have a manifest file for installing a PWA 57 | if (!file_exists("../manifest.json")) 58 | create_pwa_manifest(); 59 | 60 | /* Middle of install? Override redirect: */ 61 | if (!isset($config['unrealircd'])) 62 | $redirect = get_config("base_url")."settings/rpc-servers.php"; 63 | header('Location: ' . $redirect); 64 | die(); 65 | } 66 | else 67 | { 68 | /* LOGIN FAILED */ 69 | $fail = [ 70 | "login" => htmlspecialchars($_POST['username']), 71 | "IP" => $_SERVER['REMOTE_ADDR'] 72 | ]; 73 | Hook::run(HOOKTYPE_USER_LOGIN_FAIL, $fail); 74 | $failmsg = "Incorrect login"; 75 | } 76 | 77 | } 78 | else 79 | $failmsg = "Couldn't log you in: Missing credentials"; 80 | } 81 | 82 | ?> 83 | 84 | manifest.json"> 85 | css/unrealircd-admin.css" rel="stylesheet"> 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | img/unreal.png"> 106 | 107 | manifest.json"> 108 | 109 | 121 | 122 | UnrealIRCd Panel 123 | 124 | 164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | 172 | 173 | 178 |
179 |
180 |
181 | 182 |
183 |
184 | Username cannot be empty. 185 |
186 | 187 |
188 |
189 |
190 | 191 |
192 |
193 | Password cannot be empty. 194 |
195 | 196 |
197 | 198 |
199 | 200 |
201 |
202 |
203 |
204 |
205 |
206 | 213 | 240 | 241 | -------------------------------------------------------------------------------- /logs/index.php: -------------------------------------------------------------------------------- 1 | 5 |

Log viewer

6 |

This fetches up to 1000 historical log entries from UnrealIRCd (requires 6.1.1-git+) and then follows the logs 'live'.

7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
TimeLevelSubsystemEventMessage
18 |
19 | 20 | 21 | 61 | 62 | 63 | 64 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /misc/ban-exceptions-misc.php: -------------------------------------------------------------------------------- 1 | $name"; 39 | } 40 | return $badges; 41 | } 42 | -------------------------------------------------------------------------------- /misc/ip-whois-misc.php: -------------------------------------------------------------------------------- 1 | 70 |
71 | 72 | 73 | $val) 76 | { 77 | ?> 78 | 85 | 88 | 89 |
79 | $val"; 82 | echo "$val"; 83 | ?> 84 |
90 |
91 | BASE_URL.'img/favicon.ico', 15 | 'sizes' => '64x64', 16 | 'type' => 'image/x-icon' 17 | ], 18 | [ 19 | 'src' => BASE_URL.'img/unreal.jpg', 20 | 'sizes' => '128x128', 21 | 'type' => 'image/x-icon' 22 | ] 23 | ]; 24 | 25 | $json = json_encode($manifest, JSON_PRETTY_PRINT); 26 | return file_put_contents('../manifest.json', $json) ? true : false; 27 | } 28 | -------------------------------------------------------------------------------- /misc/right-click.php: -------------------------------------------------------------------------------- 1 | "Copy", 10 | "onclick" => "copy_to_clipboard(window.getSelection().toString())", 11 | "icon" => "fa-clipboard" 12 | ], 13 | [ 14 | "text" => "Paste", 15 | "onclick" => "paste_from_clipboard()", 16 | "icon" => "fa-paint-brush", 17 | ], 18 | ]; 19 | 20 | // register our menu 21 | Hook::run(HOOKTYPE_RIGHTCLICK_MENU, $menu); 22 | 23 | ?> 24 | 25 | 26 | -------------------------------------------------------------------------------- /misc/strings.php: -------------------------------------------------------------------------------- 1 | 'year', 86 | 2592000 => 'month', 87 | 604800 => 'week', 88 | 86400 => 'day', 89 | 3600 => 'hour', 90 | 60 => 'minute', 91 | 1 => 'second' 92 | ); 93 | 94 | foreach ($units as $unit => $text) { 95 | if ($diff < $unit) continue; 96 | $numberOfUnits = floor($diff / $unit); 97 | return $numberOfUnits.' '.$text.(($numberOfUnits>1)?'s':'').' ago'; 98 | } 99 | } 100 | 101 | /** 102 | * Uses system time. 103 | * Returns: 104 | * - evening 105 | * - morning 106 | * - afternoon 107 | */ 108 | function time_of_day() 109 | { 110 | $timeofday = "day"; // in case something went wrong? lol 111 | $hour = date("H"); 112 | if ($hour >= 18 || $hour < 4) 113 | $timeofday = "evening"; 114 | else if ($hour >= 4 && $hour < 12) 115 | $timeofday = "morning"; 116 | else if ($hour >= 12 && $hour < 18) 117 | $timeofday = "afternoon"; 118 | 119 | return $timeofday; 120 | } 121 | 122 | 123 | /** 124 | * Concatenate a string to a string 125 | */ 126 | function strcat(&$targ,$string) : void 127 | { $targ .= $string; } 128 | 129 | 130 | /** 131 | * Concatenate a string to a string and limits the string to a certain length 132 | */ 133 | function strlcat(&$targ,$string,$size) : void 134 | { 135 | strcat($targ,$string); 136 | $targ = mb_substr($targ,0,$size); 137 | } 138 | 139 | 140 | /** 141 | * Prefixes a string to a string 142 | */ 143 | function strprefix(&$targ,$string) : void 144 | { $targ = $string.$targ; } 145 | 146 | 147 | /** 148 | * Prefixes a string to a string and limits the string to a certain length 149 | */ 150 | function strlprefix(&$targ,$string,$size) : void 151 | { 152 | if (sizeof($targ) >= $size) 153 | return; 154 | 155 | strprefix($targ,$string); 156 | $targ = mb_substr($targ,0,$size); 157 | } 158 | 159 | /** Checks if the token provided is a bad pointer, by reference 160 | * Returns Bool value true if it IS bad 161 | * 162 | * Syntax: 163 | * BadPtr($variable) 164 | * 165 | * Returns: 166 | * @ 167 | */ 168 | function BadPtr(&$tok) 169 | { 170 | if (!isset($tok) || empty($tok) || !$tok || strlen($tok) == 0) 171 | return true; 172 | return false; 173 | } 174 | 175 | /** This function takes a string, tokenizes 176 | * it by a space (chr 32), removes the first 177 | * word/token, and returns the result. 178 | * Mostly used for string manipulation around 179 | * the source. 180 | * 181 | * Syntax: 182 | * rparv(String $sentence) 183 | * 184 | * Returns: 185 | * string|false 186 | */ 187 | function rparv($string) 188 | { 189 | $parv = split($string); 190 | $first = strlen($parv[0]) + 1; 191 | $string = substr($string, $first); 192 | if ($string) 193 | return $string; 194 | return false; 195 | } 196 | 197 | /* Taken from https://www.aviran.org/stripremove-irc-client-control-characters/ 198 | * We may want to re-base it off our UnrealIRCd's one though. 199 | */ 200 | function StripControlCharacters($text) 201 | { 202 | $controlCodes = array( 203 | '/(\x03(?:\d{1,2}(?:,\d{1,2})?)?)/', // Color code 204 | '/\x02/', // Bold 205 | '/\x0F/', // Escaped 206 | '/\x16/', // Italic 207 | '/\x1F/' // Underline 208 | ); 209 | return preg_replace($controlCodes,'',$text); 210 | } 211 | -------------------------------------------------------------------------------- /news.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | create_sql_table(); 23 | } 24 | 25 | /** HOOKTYPE_NAVBAR 26 | * If the current user has permission to manage config blocks, 27 | * add a link to the "Tools" menu about it 28 | */ 29 | public static function add_navbar(&$pages) 30 | { 31 | $page_name = "Config Blocks"; 32 | $page_link = "plugins/config_blocks/"; 33 | if (current_user_can(PERMISSION_CONFIG_BLOCKS)) 34 | $pages["Tools"][$page_name] = $page_link; 35 | } 36 | 37 | /** HOOKTYPE_USER_PERMISSION_LIST 38 | * Add a permission in the Panel Users permission list. 39 | */ 40 | public static function permission_list(&$list) 41 | { 42 | $list["Can manage Remote Configs in Config Blocks"] = PERMISSION_CONFIG_BLOCKS; 43 | } 44 | 45 | /** Creates the SQL table for storing config block information */ 46 | public static function create_sql_table() 47 | { 48 | $conn = sqlnew(); 49 | $conn->query("CREATE TABLE IF NOT EXISTS " . get_config("mysql::table_prefix") . "configblocks ( 50 | block_id int AUTO_INCREMENT NOT NULL, 51 | block_name VARCHAR(255) NOT NULL, 52 | block_value VARCHAR(255) NOT NULL, 53 | added_ts VARCHAR(255), 54 | added_username VARCHAR(255), 55 | PRIMARY KEY (block_id) 56 | )"); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /plugins/config_blocks/index.php: -------------------------------------------------------------------------------- 1 | 12 | 13 |

Configuration Blocks

14 | 15 | 16 | query($query); 8 | $list = []; 9 | while ($row = $result->fetch()) 10 | { 11 | $list[$row['setting_key']] = unserialize($row['setting_value']); 12 | } 13 | return $list; 14 | } 15 | public static function set($key, $val) : int 16 | { 17 | $conn = sqlnew(); 18 | $stmt = $conn->prepare("SELECT * FROM " . get_config("mysql::table_prefix") . "settings WHERE setting_key = :name LIMIT 1"); 19 | $stmt->execute(["name" => $key]); 20 | if ($stmt->rowCount()) // if it already exists update it 21 | $stmt = $conn->prepare("UPDATE " . get_config("mysql::table_prefix") . "settings SET setting_value = :value WHERE setting_key = :name"); 22 | 23 | else // otherwise create it 24 | $stmt = $conn->prepare("INSERT INTO " . get_config("mysql::table_prefix") . "settings (setting_key, setting_value) VALUES (:name, :value)"); 25 | 26 | // make sure it's there/correct 27 | $stmt->execute(["name" => $key, "value" => serialize($val)]); 28 | $stmt = $conn->prepare("SELECT * FROM " . get_config("mysql::table_prefix") . "settings WHERE setting_key = :name LIMIT 1"); 29 | $stmt->execute(["name" => $key]); 30 | return $stmt->rowCount(); // return 1 or 0 bool-like int 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugins/sql_db/SQL/sql.php: -------------------------------------------------------------------------------- 1 | PDO::ERRMODE_EXCEPTION, 18 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 19 | PDO::ATTR_EMULATE_PREPARES => false, 20 | ]; 21 | try { 22 | $pdo = new PDO($dsn, $user, $pass, $options); 23 | } catch (\PDOException $e) { 24 | throw new \PDOException($e->getMessage(), (int)$e->getCode()); 25 | } 26 | return $pdo; 27 | } -------------------------------------------------------------------------------- /plugins/sql_db/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server-bans/name-bans.php: -------------------------------------------------------------------------------- 1 | $value) 17 | { 18 | $tok = base64_decode($value); 19 | $success = false; 20 | $success = $rpc->nameban()->delete($tok); 21 | 22 | 23 | if ($success) 24 | Message::Success("Name Ban has been removed for $tok"); 25 | else 26 | Message::Fail("Unable to remove Name Ban on $tok: $rpc->error"); 27 | } 28 | } 29 | elseif (isset($_POST['tkl_add']) && !empty($_POST['tkl_add'])) 30 | { 31 | if (!current_user_can(PERMISSION_NAME_BAN_ADD)) 32 | Message::Fail("Could not add name ban(s): Permission denied"); 33 | else 34 | { 35 | if (!($iphost = $_POST['tkl_add'])) 36 | Message::Fail("No mask was specified"); 37 | 38 | /* duplicate code for now [= */ 39 | $banlen_w = (isset($_POST['banlen_w'])) ? $_POST['banlen_w'] : NULL; 40 | $banlen_d = (isset($_POST['banlen_d'])) ? $_POST['banlen_d'] : NULL; 41 | $banlen_h = (isset($_POST['banlen_h'])) ? $_POST['banlen_h'] : NULL; 42 | $duration = ""; 43 | if (!$banlen_d && !$banlen_h && !$banlen_w) 44 | $duration .= "0"; 45 | else { 46 | if ($banlen_w) 47 | $duration .= $banlen_w; 48 | if ($banlen_d) 49 | $duration .= $banlen_d; 50 | if ($banlen_h) 51 | $duration .= $banlen_h; 52 | } 53 | $msg_msg = ($duration == "0" || $duration == "0w0d0h") ? "permanently" : "for " . rpc_convert_duration_string($duration); 54 | $reason = (isset($_POST['ban_reason'])) ? $_POST['ban_reason'] : "No reason"; 55 | 56 | if ($rpc->nameban()->add($iphost, $reason, $duration)) 57 | Message::Success("Name Ban set against \"$iphost\": $reason"); 58 | else 59 | Message::Fail("Name Ban could not be set against \"$iphost\": $rpc->error"); 60 | } 61 | } 62 | elseif (isset($_POST['search_types']) && !empty($_POST['search_types'])) 63 | { 64 | 65 | } 66 | } 67 | 68 | 69 | $name_bans = $rpc->nameban()->getAll(); 70 | 71 | ?> 72 |

Name Bans Overview

73 | Here you can essentially forbid the use of a nick or channel name. This is useful to reserve services nicks so they cannot be used by normal users.
74 | You can also forbid the use of channel names. This is useful in such cases where an admin might need to close a channel for reasons relating to their own policy.
75 |
76 |

79 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | set_in_config) && $name_bans->set_in_config) || ($name_bans->set_by == "-config-")) ? true : false; 166 | echo ""; 167 | if ($set_in_config) 168 | echo ""; 169 | else 170 | echo ""; 171 | echo ""; 172 | echo ""; 173 | echo ""; 174 | $set_by = $set_in_config ? "Config" : show_nick_only($name_bans->set_by); 175 | echo ""; 176 | echo ""; 177 | echo ""; 178 | echo ""; 179 | } 180 | ?>
MaskDurationReasonSet BySet OnExpires
".$name_bans->name."".$name_bans->duration_string."".$name_bans->reason."".$set_by."".$name_bans->set_at_string."".$name_bans->expire_at_string."

183 | 204 | 205 | -------------------------------------------------------------------------------- /servers/details.php: -------------------------------------------------------------------------------- 1 | server()->rehash($servID)) 20 | { 21 | $serb = $rpc->server()->get($servID); 22 | do_log($servID, $response); 23 | if ($response->success || (!isset($response->success) != false && $response == true)) 24 | { 25 | $rehash_success[] = $serb->name; 26 | foreach($response->log as $log) 27 | { 28 | do_log($log->level); 29 | if ($log->level == "warn") 30 | $rehash_warnings[$log->log_source][] = $log->msg; 31 | } 32 | } 33 | else if (isset($response->success) && !$response->success) 34 | { 35 | foreach ($response->log as $log) 36 | { 37 | if ($log->level == "error") 38 | $rehash_errors[$log->log_source][] = $log->msg; 39 | } 40 | } 41 | } 42 | } 43 | if (isset($_POST['disconnect']) && $can_rehash) 44 | { 45 | if ($rpc->server()->disconnect($_POST['disconnect'], $_POST['reason'])) 46 | Message::Success("Server \"".$_POST['disconnect']."\" has been successfully disconnected from the network."); 47 | else 48 | Message::Fail((isset($rpc->error)) ? $rpc->error : "No error"); 49 | } 50 | 51 | } 52 | if (isset($_GET['server'])) 53 | { 54 | $servername = $_GET['server']; 55 | $srv = $rpc->server()->get($servername); 56 | 57 | if (!$srv) 58 | { 59 | Message::Fail("Could not find server: \"$servername\""); 60 | } 61 | else { 62 | do_log($srv); 63 | $servername = $srv->name; 64 | $title .= " for \"" . $servername . "\""; 65 | } 66 | } 67 | if (!empty($rehash_success)) { 68 | do_log($rehash_success); 69 | $servlist_bullet = "
    "; 70 | 71 | foreach ($rehash_success as $serv) { 72 | $servlist_bullet .= "
  1. $serv
  2. "; 73 | } 74 | $servlist_bullet .= "
"; 75 | $servlist_err_bullet = ""; 76 | foreach ($rehash_errors as $serv => $err) { 77 | $servlist_err_bullet .= "
$serv
    "; 78 | foreach ($err as $er) 79 | $servlist_err_bullet .= "
  1. $er
  2. "; 80 | echo "
"; 81 | } 82 | $servlist_warn_bullet = ""; foreach ($rehash_warnings as $server => $warning) { 83 | $servlist_warn_bullet .= "
$serv
    "; 84 | foreach ($warning as $w) 85 | $servlist_warn_bullet .= "
  1. $w
  2. "; 86 | $servlist_warn_bullet .= "
"; 87 | } 88 | if (!empty($rehash_success)) 89 | Message::Success( 90 | "The following server(s) were successfully rehashed:", 91 | $servlist_bullet 92 | ); 93 | if (!empty($rehash_warnings)) 94 | Message::Warning( 95 | "The following warning(s) were encountered:", 96 | $servlist_warn_bullet 97 | ); 98 | if (!empty($rehash_errors)) 99 | Message::Fail( 100 | "The following error(s) were encountered and the server(s) failed to rehash:", 101 | $servlist_err_bullet 102 | ); 103 | } 104 | ?> 105 | <?php echo $title; ?> 106 |

107 |
108 |
109 |
110 | > 111 |
112 |
113 |
114 |
115 |
116 | 117 | 123 |
124 |
125 |
126 |
" data-toggle="modal" data-target="#rehash_modal" >Rehash
127 |
" data-toggle="modal" data-target="#disconnect_modal">Disconnect
128 |
129 |
130 |
131 | 154 | 155 | 177 | 178 |
179 |
180 | 181 |
182 |
183 |
184 |
Server Settings
185 | 191 | 192 |
193 |
194 |
195 | 196 |

197 |

198 |

Server information

199 | 200 |

Extra information

201 | 202 |
203 |

204 |
205 |
212 | 213 |
214 |

215 |
216 |
217 |

218 |
219 |
220 |

221 |
222 |
223 |

224 |
225 | 226 |
227 |
228 |
229 | 230 |
231 |
232 | 233 | -------------------------------------------------------------------------------- /servers/index.php: -------------------------------------------------------------------------------- 1 | server()->rehash($servID)) 17 | { 18 | $serb = $rpc->server()->get($servID); 19 | do_log($servID, $response); 20 | if ($response->success || (!isset($response->success) != false && $response == true)) 21 | { 22 | $rehash_success[] = $serb->name; 23 | foreach($response->log as $log) 24 | { 25 | do_log($log->level); 26 | if ($log->level == "warn") 27 | $rehash_warnings[$log->log_source][] = $log->msg; 28 | } 29 | } 30 | else if (isset($response->success) && !$response->success) 31 | { 32 | foreach ($response->log as $log) 33 | { 34 | if ($log->level == "error") 35 | $rehash_errors[$log->log_source][] = $log->msg; 36 | } 37 | } 38 | } 39 | } 40 | $checkforupdates = (isset($_POST['checkforupdates'])) ? true : false; 41 | /* Get the server list */ 42 | $servers = $rpc->server()->getAll(); 43 | $latest = 0; 44 | if ($checkforupdates) 45 | { 46 | $latest = get_unreal_latest_version(); 47 | } 48 | ?> 49 |

Servers Overview

50 | "; 56 | 57 | foreach ($rehash_success as $serv) { 58 | $servlist_bullet .= "
  • $serv
  • "; 59 | } 60 | $servlist_bullet .= ""; 61 | $servlist_err_bullet = ""; 62 | foreach ($rehash_errors as $serv => $err) { 63 | $servlist_err_bullet .= "
    $serv
      "; 64 | foreach ($err as $er) 65 | $servlist_err_bullet .= "
    1. $er
    2. "; 66 | echo "
    "; 67 | } 68 | $servlist_warn_bullet = ""; foreach ($rehash_warnings as $server => $warning) { 69 | $servlist_warn_bullet .= "
    $serv
      "; 70 | foreach ($warning as $w) 71 | $servlist_warn_bullet .= "
    1. $w
    2. "; 72 | $servlist_warn_bullet .= "
    "; 73 | } 74 | if (!empty($rehash_success)) 75 | Message::Success( 76 | "The following server(s) were successfully rehashed:", 77 | $servlist_bullet 78 | ); 79 | if (!empty($rehash_warnings)) 80 | Message::Warning( 81 | "The following warning(s) were encountered:", 82 | $servlist_warn_bullet 83 | ); 84 | if (!empty($rehash_errors)) 85 | Message::Fail( 86 | "The following error(s) were encountered and the server(s) failed to rehash:", 87 | $servlist_err_bullet 88 | ); 89 | } 90 | } 91 | if (isset($_POST['sf_name']) && strlen($_POST['sf_name'])) 92 | Message::Info("Listing servers which match name: \"" . $_POST['sf_name'] . "\""); 93 | 94 | ?> 95 | Click on a server name to view more information. 96 | 97 |
    98 | 99 | 100 | 101 | 102 | 103 | 104 | 106 |
    Filter:
    Name: 105 |
    107 |
    108 |
    " data-toggle="modal" data-target="#rehash_modal"> Rehash Selected
    109 |

    110 | 111 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | name), strtolower($_POST['sf_name'])) !== 0 && 150 | strpos(strtolower($server->name), strtolower($_POST['sf_name'])) == false) 151 | continue; 152 | 153 | $update = ""; 154 | if ($checkforupdates && $latest) 155 | { 156 | 157 | $tok = split($server->server->features->software, "-"); 158 | if (!strcasecmp($tok[0],"unrealircd")) 159 | { 160 | if ($latest > $tok[1]) 161 | $update = " "; 162 | } 163 | } 164 | 165 | echo ""; 166 | echo ""; 167 | echo ""; 168 | echo ""; 169 | 170 | $s = sinfo_conv_version_string($server); 171 | 172 | echo ""; 173 | if (isset($server->server->uplink)) 174 | echo ""; 175 | else 176 | echo ""; /* self */ 177 | echo ""; 178 | } 179 | ?> 180 | 181 |
    NameUsersVersionConnected toUp since
    id."\">$server->name $update".$server->server->num_users."$s".$server->server->uplink."".$server->server->boot_time."
    182 |
    183 | 184 | 185 | -------------------------------------------------------------------------------- /settings/add-plugin.php: -------------------------------------------------------------------------------- 1 | 12 | 13 |

    Add New Plugin

    14 |
    15 | 16 | 20 | We've got two fantastic plugins to kick things off (one practical, one for a playful twist).
    21 | Join us on this exciting journey and unlock new possibilities for your website!

    "; 22 | $p->do_list(); 23 | } else { 24 | echo "Oops! Could not find plugins list. This is an upstream error, which means there is nothing wrong
    25 | on your panel, it just means we can't check the plugins information webpage for some reason.
    26 | Nothing to worry about! Try again later!"; 27 | } 28 | require_once "../inc/footer.php"; 29 | 30 | ?> 31 | 32 | 84 | -------------------------------------------------------------------------------- /settings/index.php: -------------------------------------------------------------------------------- 1 | 12 |

    Panel Accounts

    13 | 14 | id == $user->id) // if it's the current user 28 | { 29 | session_destroy(); 30 | header("Location: " . get_config("base_url") . "plugins/sql_db/login.php"); 31 | die(); 32 | } 33 | $msg = ($deleted = 1) ? "Message::Success" : "Message::Fail"; 34 | } 35 | $msg($info); 36 | unset($info); 37 | } 38 | 39 | if (isset($p['do_add_user']) && current_user_can(PERMISSION_MANAGE_USERS)) 40 | { 41 | $user = []; 42 | $user['user_name'] = $p['user_add']; 43 | $user['user_pass'] = $p['password']; 44 | $user['fname'] = $p['add_first_name']; 45 | $user['lname'] = $p['add_last_name']; 46 | $user['user_email'] = $p['user_email']; 47 | $user['user_bio'] = $p['user_bio']; 48 | $user['err'] = ""; 49 | if (!create_new_user($user)) 50 | { 51 | Message::Fail("Failed to create user: " . $user['user_name'] . " " . $user['err']); 52 | } 53 | else if (($usr_obj = new PanelUser($user['user_name'])) && isset($usr_obj->id)) 54 | { 55 | $usr_obj->add_meta("role", $p['user_role']); 56 | Message::Success("Successfully created user \"" . $user['user_name'] . "\""); 57 | } 58 | else 59 | { 60 | Message::Fail("Failed to create user \"" . $user['user_name'] . "\""); 61 | } 62 | } 63 | } 64 | $userlist = []; 65 | Hook::run(HOOKTYPE_GET_USER_LIST, $userlist); 66 | 67 | ?> 68 | Click on a username to view more information. 69 |

    70 |
    71 |
    72 | 73 |
    74 |
    75 |
    Add New User
    76 | 77 |
    78 | 79 |
    80 |
    81 | 82 | 134 |
    135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | "; 157 | echo ""; 158 | echo ""; 159 | echo ""; 160 | echo ""; 161 | echo ""; 162 | echo ""; 163 | echo ""; 164 | $last = (isset($user->user_meta['last_login'])) ? "".$user->user_meta['last_login'] . "".how_long_ago($user->user_meta['last_login'])."" : "none"; 165 | echo ""; 166 | echo "\n"; 167 | } 168 | ?>
    UsernameRoleFirst NameLast NameEmailCreatedBioLast login
    id\">$user->username".((isset($user->user_meta['role'])) ? $user->user_meta['role'] : "")."".$user->first_name."".$user->last_name."email\">$user->email".$user->created."".$user->bio." $last
    169 | 170 |

    173 | 174 |
    195 | 197 | -------------------------------------------------------------------------------- /settings/plugins.php: -------------------------------------------------------------------------------- 1 | 8 | 9 |

    Active Plugins Add New

    10 |
    11 | Your installed plugins: 12 |
    13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | "; 30 | echo ""; 31 | echo ""; 32 | echo ""; 33 | echo ""; 34 | echo ""; 35 | echo ""; 36 | echo ""; 37 | echo ""; 38 | } 39 | ?> 40 | 41 |
    Plugin NameVersionDescriptionAuthorContactUninstall
    handle."')\">".$plugin->name."".$plugin->version."".$plugin->description."".$plugin->author."".$plugin->email."
    handle."install\" class='text-center btn-sm btn-danger btn-install-plugin'>Uninstall
    42 | 43 | 85 | 86 | id; 9 | $edit_user = new PanelUser(NULL, $id); 10 | $can_edit_profile = (user_can($us, PERMISSION_MANAGE_USERS) || $edit_user->id == $us->id) ? true : false; 11 | $caneditprofile = ($can_edit_profile) ? "" : "disabled"; 12 | $caneditpermissions = (user_can($us, PERMISSION_MANAGE_USERS)) ? true : false; 13 | $can_edit = ($caneditpermissions) ? "" : "disabled"; 14 | $postbutton = (isset($_POST['update_user'])) ? true : false; 15 | $roles_list = get_panel_user_roles_list(); 16 | 17 | if ($postbutton && isset($_POST['user_role']) && $caneditpermissions) 18 | { 19 | if ($_POST['user_role'] != $edit_user->user_meta['role']) 20 | { 21 | $edit_user->add_meta("role", $_POST['user_role']); 22 | $edit_user->delete_meta("permissions"); 23 | Message::Success("Updated the role of $edit_user->username"); 24 | } 25 | } 26 | 27 | if ($postbutton && $can_edit_profile) 28 | { 29 | // Goes via core: 30 | $array['update_fname'] = (isset($_POST['first_name']) && strlen($_POST['first_name'])) ? $_POST['first_name'] : false; 31 | $array['update_lname'] = (isset($_POST['last_name']) && strlen($_POST['last_name'])) ? $_POST['last_name'] : false; 32 | $array['update_bio'] = (isset($_POST['bio']) && strlen($_POST['bio'])) ? $_POST['bio'] : false; 33 | $array['update_email'] = (isset($_POST['email']) && strlen($_POST['email'])) ? $_POST['email'] : false; 34 | $array['update_pass'] = (isset($_POST['password']) && strlen($_POST['password'])) ? $_POST['password'] : false; 35 | $array['update_pass_conf'] = (isset($_POST['passwordconfirm']) && strlen($_POST['passwordconfirm'])) ? $_POST['passwordconfirm'] : false; 36 | // Goes via meta: 37 | $session_timeout = (isset($_POST['session_timeout']) && strlen($_POST['session_timeout'])) ? $_POST['session_timeout'] : 3600; 38 | 39 | if (!$array['update_pass']) 40 | { 41 | unset($array['update_pass']); 42 | unset($array['update_pass_conf']); 43 | } 44 | elseif ($array['update_pass'] == $array['update_pass_conf']) 45 | { 46 | $array['update_pass_conf'] = PanelUser::password_hash($array['update_pass_conf']); 47 | $edit_user->delete_meta("hibp"); 48 | $edit_user->HIBP(sha1($array['update_pass'])); 49 | unset($array['update_pass']); 50 | } 51 | else 52 | { 53 | Message::Fail("Could not update password: Passwords did not match"); 54 | unset($array['update_pass']); 55 | unset($array['update_pass_conf']); 56 | } 57 | $edit_user->update_core_info($array); 58 | $edit_user->add_meta("session_timeout", $session_timeout); 59 | $edit_user = new PanelUser($edit_user->username); 60 | } 61 | ?> 62 |

    Edit User: "username; ?>"

    63 |
    64 | 65 | 66 |
    67 |
    68 | Username 69 |
    70 |
    71 | 72 |
    73 |
    74 | Role 75 |
    84 |
    85 | 86 | 87 | 88 |
    89 |
    90 | First Name 91 |
    type="text" class="form-control" name="first_name" id="first_name" placeholder="first_name; ?>"> 92 |
    93 | 94 | 95 |
    96 |
    97 | Last Name 98 |
    type="text" class="form-control" name="last_name" id="last_name" placeholder="last_name; ?>"> 99 |
    100 | 101 | 102 |
    103 |
    104 | Bio 105 |
    106 |
    107 | 108 | 109 |
    110 |
    111 | Email 112 |
    type="text" class="form-control" name="email" id="email" autocomplete="off" value="email; ?>"> 113 |
    114 | 115 |
    116 |
    117 | Session timeout 118 |
    type="text" class="form-control" name="session_timeout" id="session_timeout" autocomplete="off" value="user_meta['session_timeout'] ?? 3600; ?>"> 119 |
    120 | 121 |
    122 |
    123 | New Password 124 |
    type="password" class="form-control" name="password" id="password" autocomplete="off"> 125 |
    126 |
    127 | Confirm Password 128 |
    type="password" class="form-control" name="passwordconfirm" id="passwordconfirm" autocomplete="off"> 129 |
    130 | 131 |
    132 |
    133 | 134 | Access denied"; 9 | die(); 10 | } 11 | $permissions = get_panel_user_permission_list(); 12 | $list = get_panel_user_roles_list(); 13 | 14 | /** 15 | * Add a new role 16 | */ 17 | $errors = []; 18 | $success = []; 19 | 20 | 21 | 22 | if (isset($_POST['add_role_name']) && $role_name = $_POST['add_role_name']) 23 | { 24 | foreach ($list as $name => $u) // don't add it if it already exists 25 | { 26 | if (!strcmp(to_slug($name),to_slug($role_name))) 27 | { 28 | $errors[] = "Cannot create role \"$role_name\": A role with that name already exists."; 29 | break; 30 | } 31 | } 32 | if (empty($errors)) // so far so good 33 | { 34 | $msg = "Added user role \"$role_name\""; 35 | $permissions = []; 36 | if (isset($_POST['use_dup_role']) && $dup = $_POST['dup_role']) // if they're duplicating a role 37 | { 38 | $permissions = $list[$dup]; 39 | $msg .= ", a duplicate of \"$dup\""; 40 | } 41 | $clean_perms = []; 42 | foreach($permissions as $k => $v) 43 | $clean_perms[] = $v; 44 | 45 | $config['user_roles'][$role_name] = $clean_perms; 46 | write_config('user_roles'); 47 | $success[] = $msg; 48 | $list = get_panel_user_roles_list(); // refresh 49 | 50 | } 51 | } 52 | 53 | 54 | elseif (isset($_POST['del_role_name']) && $role_name = $_POST['del_role_name']) 55 | { 56 | $found = 0; 57 | foreach ($list as $name => $u) // don't add it if it already exists 58 | { 59 | if (!strcmp(to_slug($name),to_slug($role_name))) 60 | { 61 | $found = 1; 62 | break; 63 | } 64 | } 65 | if ($found) // so far so good 66 | { 67 | unset($config['user_roles'][$role_name]); 68 | write_config('user_roles'); 69 | $success[] = "Successfully deleted role \"$role_name\""; 70 | $list = get_panel_user_roles_list(); // refresh 71 | } 72 | else 73 | $errors[] = "Could not delete role \"$role_name\": Role does not exist."; 74 | } 75 | 76 | elseif (isset($_POST['update_role']) && $role_name = $_POST['update_role']) 77 | { 78 | $found = 0; 79 | foreach ($list as $name => $u) // don't add it if it already exists 80 | { 81 | if (!strcmp(to_slug($name),to_slug($role_name))) 82 | { 83 | $found = 1; 84 | break; 85 | } 86 | } 87 | if (!$found) // so far so good 88 | { 89 | $errors[] = "Could not update role \"$role_name\": Role does not exist."; 90 | } 91 | else 92 | { 93 | $config['user_roles'][$role_name] = $_POST['permissions']; 94 | write_config('user_roles'); 95 | $success[] = "Successfully updated role \"$role_name\""; 96 | $list = get_panel_user_roles_list(); // refresh 97 | } 98 | } 99 | ?> 100 | 101 | 102 |
    103 | 104 |
    105 |

    User Role Editor

    106 | 107 | Roles are user categories where each has it's own set of permissions.
    108 | Here, you can easily add and edit User Roles to ensure that your team has the appropriate access and permissions they need.
    109 | Once you've created a role, you can assign it to a user on your panel, and they will have the permissions assigned to their role.

    110 |
    Some roles are built-in and cannot be deleted or modified, specifically "Super Admin" and "Read Only"


    111 | Click a role name to view role permissions. 112 |
    113 |
    114 |
    115 |
    116 |
    Create New Role
    117 |
    You must create a new role before you can add permissions to it.
    118 |
    119 |
    120 | New Role Name 121 |
    122 | 123 | 124 | 125 |
    126 |
    127 |
    128 |
    129 | Duplicate Role 130 |
    131 |
    132 | 139 |
    140 |
    141 | 142 |
    143 | 144 | 145 |
    146 |
    147 |
    148 | 162 | 163 | 164 | 186 | 8 | 9 |

    Network Health Checkup

    10 | 22 | 23 |
    24 |
    25 |
    26 |
    27 | 30 |
    31 |
    32 |
    33 |
    34 | toTable($checkup->problems['usermodes']); ?> 35 |
    36 |
    37 |
    38 |
    39 |
    40 |
    41 | 44 |
    45 |
    46 |
    47 |
    48 | toTable($checkup->problems['chanmodes']); ?> 49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 | 58 |
    59 |
    60 |
    61 |
    62 | toTable($checkup->problems['modules']); ?> 63 |
    64 |
    65 |
    66 |
    67 |
    68 |
    69 | 72 |
    73 |
    74 |
    75 |
    76 | toTable($checkup->problems['servers']); ?> 77 |
    78 |
    79 |
    80 |
    81 | $title"; 19 | 20 | 21 | if (!isset($ip)) 22 | $noip = true; 23 | 24 | else 25 | { 26 | $whois = get_ip_whois($ip); 27 | $file = split($whois, "\n"); 28 | 29 | $i = 0; 30 | 31 | $start = false; 32 | foreach ($file as $line) { 33 | if (!strlen($line) && $start) 34 | $i++; 35 | 36 | if (($line && !ctype_alnum($line[0])) || !$line) // we don't care about your opinion we just want the info 37 | continue; 38 | $start = true; 39 | $tok = split($line); 40 | foreach ($tok as &$t) 41 | if (!strlen($t)) 42 | $t = NULL; 43 | 44 | $resplit = split(glue($tok)); 45 | 46 | $key = trim($resplit[0], ":"); 47 | $resplit[0] = NULL; 48 | $value = glue($resplit); 49 | 50 | if (!isset($ip_info[$i][$key])) 51 | $ip_info[$i][$key] = $value; 52 | else 53 | $ip_info[$i][$key] .= "\n $value"; 54 | } 55 | } 56 | ?> 57 | 58 |
    59 |
    60 |
    61 | > 62 |
    63 |
    64 |
    65 |
    66 |
    67 | 68 | 73 |
    74 |
    75 |
    76 | 77 |
    78 |
    79 |
    View RAW
    80 |
    81 |
    82 |
    83 | 84 | 89 | 90 | 108 | 109 | user()->get($nickname); 13 | if (!$nick) 14 | { 15 | Message::Fail("Could not find user: \"$nickname\""); 16 | } else { 17 | $nickname = $nick->name; 18 | $title .= " for \"" . $nickname . "\""; 19 | } 20 | } 21 | ?> 22 | <?php echo $title; ?> 23 |

    24 |
    25 |
    26 |
    27 | > 28 |
    29 |
    30 |
    31 |
    32 |
    33 | 34 | 41 |
    42 |
    43 |
    44 |
    45 |
    46 |
    47 |
    Basic Information
    48 |

    49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 |
    User Settings
    56 |

    57 |
    58 |
    59 |
    60 |
    61 |
    62 |
    63 |
    Channels
    64 |

    65 |
    66 |
    67 |
    68 |
    69 |
    70 | 71 | 72 | 73 | --------------------------------------------------------------------------------