1: <?php
2:
3: namespace Budabot\Core;
4:
5: use stdClass;
6: use ReflectionAnnotatedMethod;
7: use Exception;
8:
9: 10: 11:
12: class CommandManager {
13:
14:
15: public $db;
16:
17:
18: public $chatBot;
19:
20:
21: public $settingManager;
22:
23:
24: public $accessManager;
25:
26:
27: public $helpManager;
28:
29:
30: public $commandAlias;
31:
32:
33: public $text;
34:
35:
36: public $util;
37:
38:
39: public $subcommandManager;
40:
41:
42: public $commandSearchController;
43:
44:
45: public $usageController;
46:
47:
48: public $logger;
49:
50: public $commands;
51:
52: 53: 54: 55:
56: public function register($module, $channel, $filename, $command, $accessLevel, $description, $help = '', $defaultStatus = null) {
57: $command = strtolower($command);
58: $module = strtoupper($module);
59: $accessLevel = $this->accessManager->getAccessLevel($accessLevel);
60:
61: if (!$this->chatBot->processCommandArgs($channel, $accessLevel)) {
62: $this->logger->log('ERROR', "Invalid args for $module:command($command). Command not registered.");
63: return;
64: }
65:
66: if (empty($filename)) {
67: $this->logger->log('ERROR', "Error registering $module:command($command). Handler is blank.");
68: return;
69: }
70:
71: forEach (explode(',', $filename) as $handler) {
72: list($name, $method) = explode(".", $handler);
73: if (!Registry::instanceExists($name)) {
74: $this->logger->log('ERROR', "Error registering method '$handler' for command '$command'. Could not find instance '$name'.");
75: return;
76: }
77: }
78:
79: if (!empty($help)) {
80: $help = $this->helpManager->checkForHelpFile($module, $help);
81: }
82:
83: if ($defaultStatus === null) {
84: if ($this->chatBot->vars['default_module_status'] == 1) {
85: $status = 1;
86: } else {
87: $status = 0;
88: }
89: } else {
90: $status = $defaultStatus;
91: }
92:
93: for ($i = 0; $i < count($channel); $i++) {
94: $this->logger->log('debug', "Adding Command to list:($command) File:($filename) Admin:({$accessLevel[$i]}) Channel:({$channel[$i]})");
95: $row = $this->db->queryRow("SELECT 1 FROM cmdcfg_<myname> WHERE cmd = ? AND type = ?", $command, $channel[$i]);
96:
97: try {
98: if ($row !== null) {
99: $sql = "UPDATE cmdcfg_<myname> SET `module` = ?, `verify` = ?, `file` = ?, `description` = ?, `help` = ? WHERE `cmd` = ? AND `type` = ?";
100: $this->db->exec($sql, $module, '1', $filename, $description, $help, $command, $channel[$i]);
101: } else {
102: $sql = "INSERT INTO cmdcfg_<myname> (`module`, `type`, `file`, `cmd`, `admin`, `description`, `verify`, `cmdevent`, `status`, `help`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
103: $this->db->exec($sql, $module, $channel[$i], $filename, $command, $accessLevel[$i], $description, '1', 'cmd', $status, $help);
104: }
105: } catch (SQLException $e) {
106: $this->logger->log('ERROR', "Error registering method '$handler' for command '$command': " . $e->getMessage());
107: }
108: }
109: }
110:
111: 112: 113: 114:
115: public function activate($channel, $filename, $command, $accessLevel = 'all') {
116: $command = strtolower($command);
117: $accessLevel = $this->accessManager->getAccessLevel($accessLevel);
118: $channel = strtolower($channel);
119:
120: $this->logger->log('DEBUG', "Activate Command:($command) Admin Type:($accessLevel) File:($filename) Channel:($channel)");
121:
122: forEach (explode(',', $filename) as $handler) {
123: list($name, $method) = explode(".", $handler);
124: if (!Registry::instanceExists($name)) {
125: $this->logger->log('ERROR', "Error activating method $handler for command $command. Could not find instance '$name'.");
126: return;
127: }
128: }
129:
130: $obj = new stdClass;
131: $obj->file = $filename;
132: $obj->admin = $accessLevel;
133:
134: $this->commands[$channel][$command] = $obj;
135: }
136:
137: 138: 139: 140:
141: public function deactivate($channel, $filename, $command) {
142: $command = strtolower($command);
143: $channel = strtolower($channel);
144:
145: $this->logger->log('DEBUG', "Deactivate Command:($command) File:($filename) Channel:($channel)");
146:
147: unset($this->commands[$channel][$command]);
148: }
149:
150: public function updateStatus($channel, $cmd, $module, $status, $admin) {
151: if ($channel == 'all' || $channel == '' || $channel == null) {
152: $type_sql = '';
153: } else {
154: $type_sql = "AND `type` = '$channel'";
155: }
156:
157: if ($cmd == '' || $cmd == null) {
158: $cmd_sql = '';
159: } else {
160: $cmd_sql = "AND `cmd` = '$cmd'";
161: }
162:
163: if ($module == '' || $module == null) {
164: $module_sql = '';
165: } else {
166: $module_sql = "AND `module` = '$module'";
167: }
168:
169: if ($admin == '' || $admin == null) {
170: $adminSql = '';
171: } else {
172: $adminSql = ", admin = '$admin'";
173: }
174:
175: $data = $this->db->query("SELECT * FROM cmdcfg_<myname> WHERE `cmdevent` = 'cmd' $module_sql $cmd_sql $type_sql");
176: if (count($data) == 0) {
177: return 0;
178: }
179:
180: forEach ($data as $row) {
181: if ($status == 1) {
182: $this->activate($row->type, $row->file, $row->cmd, $admin);
183: } else if ($status == 0) {
184: $this->deactivate($row->type, $row->file, $row->cmd);
185: }
186: }
187:
188: return $this->db->exec("UPDATE cmdcfg_<myname> SET status = '$status' $adminSql WHERE `cmdevent` = 'cmd' $module_sql $cmd_sql $type_sql");
189: }
190:
191: 192: 193: 194:
195: public function loadCommands() {
196: $this->logger->log('DEBUG', "Loading enabled commands");
197:
198: $data = $this->db->query("SELECT * FROM cmdcfg_<myname> WHERE `status` = '1' AND `cmdevent` = 'cmd'");
199: forEach ($data as $row) {
200: $this->activate($row->type, $row->file, $row->cmd, $row->admin);
201: }
202: }
203:
204: public function get($command, $channel = null) {
205: $command = strtolower($command);
206:
207: if ($channel !== null) {
208: $type_sql = "AND type = '{$channel}'";
209: }
210:
211: $sql = "SELECT * FROM cmdcfg_<myname> WHERE `cmd` = ? {$type_sql}";
212: return $this->db->query($sql, $command);
213: }
214:
215: private function mapToCmd($sc) {
216: return $sc->cmd;
217: }
218:
219: function process($channel, $message, $sender, CommandReply $sendto) {
220: list($cmd, $params) = explode(' ', $message, 2);
221: $cmd = strtolower($cmd);
222:
223: $commandHandler = $this->getActiveCommandHandler($cmd, $channel, $message);
224:
225:
226: if ($commandHandler === null) {
227:
228: if (($channel == 'guild' && $this->settingManager->get('guild_channel_cmd_feedback') == 0) || ($channel == 'priv' && $this->settingManager->get('private_channel_cmd_feedback') == 0)) {
229: return;
230: }
231:
232: $similarCommands = $this->commandSearchController->findSimilarCommands(array($cmd));
233: $similarCommands = $this->commandSearchController->filterResultsByAccessLevel($sender, $similarCommands);
234: $similarCommands = array_slice($similarCommands, 0, 5);
235: $cmdNames = array_map(array($this, 'mapToCmd'), $similarCommands);
236:
237: $sendto->reply("Error! Unknown command. Did you mean..." . implode(", ", $cmdNames) . '?');
238: return;
239: }
240:
241:
242: if (!$this->checkAccessLevel($channel, $message, $sender, $sendto, $cmd, $commandHandler)) {
243: return;
244: }
245:
246: try {
247: $handler = $this->callCommandHandler($commandHandler, $message, $channel, $sender, $sendto);
248:
249: if ($handler === null) {
250: $help = $this->getHelpForCommand($cmd, $channel, $sender);
251: $sendto->reply($help);
252: }
253: } catch (StopExecutionException $e) {
254: throw $e;
255: } catch (SQLException $e) {
256: $this->logger->log("ERROR", $e->getMessage(), $e);
257: $sendto->reply("There was an SQL error executing your command.");
258: } catch (Exception $e) {
259: $this->logger->log("ERROR", "Error executing '$message': " . $e->getMessage(), $e);
260: $sendto->reply("There was an error executing your command: " . $e->getMessage());
261: }
262:
263: try {
264:
265: if ($this->settingManager->get('record_usage_stats') == 1) {
266: $this->usageController->record($channel, $cmd, $sender, $handler);
267: }
268: } catch (Exception $e) {
269: $this->logger->log("ERROR", $e->getMessage(), $e);
270: }
271: }
272:
273: public function checkAccessLevel($channel, $message, $sender, $sendto, $cmd, $commandHandler) {
274: if ($this->accessManager->checkAccess($sender, $commandHandler->admin) !== true) {
275: if ($channel == 'msg') {
276: if ($this->settingManager->get('access_denied_notify_guild') == 1) {
277: $this->chatBot->sendGuild("Player <highlight>$sender<end> was denied access to command <highlight>$cmd<end>.", true);
278: }
279: if ($this->settingManager->get('access_denied_notify_priv') == 1) {
280: $this->chatBot->sendPrivate("Player <highlight>$sender<end> was denied access to command <highlight>$cmd<end>.", true);
281: }
282: }
283:
284:
285: if (($channel == 'guild' && $this->settingManager->get('guild_channel_cmd_feedback') == 0) || ($channel == 'priv' && $this->settingManager->get('private_channel_cmd_feedback') == 0)) {
286: return false;
287: }
288:
289: $sendto->reply("Error! Access denied.");
290: return false;
291: }
292: return true;
293: }
294:
295: public function callCommandHandler($commandHandler, $message, $channel, $sender, CommandReply $sendto) {
296: $successfulHandler = null;
297:
298: forEach (explode(',', $commandHandler->file) as $handler) {
299: list($name, $method) = explode(".", $handler);
300: $instance = Registry::getInstance($name);
301: if ($instance === null) {
302: $this->logger->log('ERROR', "Could not find instance for name '$name'");
303: } else {
304: $arr = $this->checkMatches($instance, $method, $message);
305: if ($arr !== false) {
306:
307:
308: $syntaxError = ($instance->$method($message, $channel, $sender, $sendto, $arr) === false);
309: if ($syntaxError == false) {
310:
311:
312: $successfulHandler = $handler;
313: break;
314: }
315: }
316: }
317: }
318:
319: return $successfulHandler;
320: }
321:
322: public function getActiveCommandHandler($cmd, $channel, $message) {
323:
324: if (isset($this->subcommandManager->subcommands[$cmd])) {
325: forEach ($this->subcommandManager->subcommands[$cmd] as $row) {
326: if ($row->type == $channel && preg_match("/^{$row->cmd}$/i", $message)) {
327: return $row;
328: }
329: }
330: }
331: return $this->commands[$channel][$cmd];
332: }
333:
334: public function getHelpForCommand($cmd, $channel, $sender) {
335: $results = $this->get($cmd, $channel);
336: $result = $results[0];
337:
338: if ($result->help != '') {
339: $blob = file_get_contents($result->help);
340: } else {
341: $blob = $this->helpManager->find($cmd, $sender);
342: }
343: if (!empty($blob)) {
344: $msg = $this->text->makeBlob("Help ($cmd)", $blob);
345: } else {
346: $msg = "Error! Invalid syntax.";
347: }
348: return $msg;
349: }
350:
351: public function checkMatches($instance, $method, $message) {
352: try {
353: $reflectedMethod = new ReflectionAnnotatedMethod($instance, $method);
354: } catch (ReflectionException $e) {
355:
356: return true;
357: }
358:
359: $regexes = $this->retrieveRegexes($reflectedMethod);
360:
361: if (count($regexes) > 0) {
362: forEach ($regexes as $regex) {
363: if (preg_match($regex, $message, $arr)) {
364: return $arr;
365: }
366: }
367: return false;
368: } else {
369: return true;
370: }
371: }
372:
373: public function retrieveRegexes($reflectedMethod) {
374: $regexes = array();
375: if ($reflectedMethod->hasAnnotation('Matches')) {
376: forEach ($reflectedMethod->getAllAnnotations('Matches') as $annotation) {
377: $regexes []= $annotation->value;
378: }
379: }
380: return $regexes;
381: }
382: }
383: