{"id":856,"date":"2023-04-05T19:16:50","date_gmt":"2023-04-05T17:16:50","guid":{"rendered":"https:\/\/betalord.com\/code\/?p=856"},"modified":"2023-04-05T20:12:24","modified_gmt":"2023-04-05T18:12:24","slug":"in-app-user-reports","status":"publish","type":"post","link":"https:\/\/betalord.com\/code\/2023\/04\/05\/in-app-user-reports\/","title":{"rendered":"In-app user reports"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"856\" class=\"elementor elementor-856\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-a5c4f4d elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"a5c4f4d\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-fecec18\" data-id=\"fecec18\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-c580a22 elementor-widget elementor-widget-text-editor\" data-id=\"c580a22\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tRecently I had to implement user bug reporting feature. What it does is it makes possible for the user to report bugs (or make suggestions, etc.) from within a java app, which then sends the report over to the web server (via POST data through php script), which in turns sends an email to the developer containing the user report.\n\nThis system is made out of two parts:\n<ol>\n \t<li>Java code that sends the data to the server<\/li>\n \t<li>PHP script that reads sent data via POST and sends an email to local email server<\/li>\n<\/ol>\nThe code presented below was implemented using libGDX framework (plus some my personal stuff), so it won&#8217;t compile off the bat, but it should serve as an example on how to solve this problem. Just reimplement the GUI part and the rest that is based off the libGDX library.\n\nNote: Java code was implemented using Apache&#8217;s HttpComponents library. I had to add this line to my gradle script in order to include it:\n\n<code class=\"EnlighterJSRAW\" data-enlighter-language=\"groovy\">implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'<\/code>\n\nAlso note, that this didn&#8217;t work on Android for some reason. It was throwing <code class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">com.android.builder.merge.DuplicateRelativeFileException: 3 files found with path 'META-INF\/DEPENDENCIES' <\/code>exception. I solved this problem by adding exclude to the Android&#8217;s gradle build script:\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"groovy\">packagingOptions {\n\tresources {\n\t\texcludes += ['META-INF\/robovm\/ios\/robovm.xml']\n\t\texcludes += ['META-INF\/DEPENDENCIES']\n\t}\n}<\/pre>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-aa3e716 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"aa3e716\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-d618453\" data-id=\"d618453\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-42cf8df elementor-widget elementor-widget-text-editor\" data-id=\"42cf8df\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h6>Usage demonstration:<\/h6>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-eac6385 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"eac6385\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-8865eb6\" data-id=\"8865eb6\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-e52b55f elementor-widget elementor-widget-video\" data-id=\"e52b55f\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;youtube_url&quot;:&quot;https:\\\/\\\/youtu.be\\\/ntNAKWzcDhY&quot;,&quot;video_type&quot;:&quot;youtube&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-wrapper elementor-open-inline\">\n\t\t\t<div class=\"elementor-video\"><\/div>\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-878fc0d elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"878fc0d\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-7947e2d\" data-id=\"7947e2d\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-6abf5d8 elementor-widget elementor-widget-text-editor\" data-id=\"6abf5d8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h6>Java Report class:<\/h6>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-90b6291 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"90b6291\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-7d69fa5\" data-id=\"7d69fa5\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-950c5fa elementor-widget elementor-widget-code-block-for-elementor\" data-id=\"950c5fa\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-block-for-elementor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre class='line-numbers theme-okaidia' data-show-toolbar='yes'><code class='language-java'>package com.betalord.sgx.core;\r\n\r\nimport com.badlogic.gdx.Gdx;\r\nimport com.badlogic.gdx.Input;\r\nimport com.badlogic.gdx.scenes.scene2d.ui.Table;\r\nimport com.badlogic.gdx.scenes.scene2d.ui.TextArea;\r\nimport com.betalord.sgx.ui.DialogUtils;\r\nimport com.betalord.sgx.ui.SGXDialog;\r\nimport com.betalord.sgx.ui.SGXLabel;\r\nimport com.betalord.sgx.ui.SGXTextArea;\r\nimport com.betalord.sgx.ui.SGXTextField;\r\nimport com.betalord.sgx.util.ConsistencyResult;\r\nimport com.betalord.sgx.util.SGXCallableObj;\r\n\r\nimport org.apache.hc.client5.http.classic.methods.HttpPost;\r\nimport org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;\r\nimport org.apache.hc.client5.http.entity.mime.StringBody;\r\nimport org.apache.hc.client5.http.impl.classic.CloseableHttpClient;\r\nimport org.apache.hc.client5.http.impl.classic.HttpClients;\r\nimport org.apache.hc.core5.http.ContentType;\r\nimport org.apache.hc.core5.http.HttpEntity;\r\nimport org.apache.hc.core5.http.io.entity.EntityUtils;\r\nimport org.apache.hc.core5.http.message.StatusLine;\r\n\r\nimport java.io.IOException;\r\n\r\n\/**\r\n * Class representing user report (bug report, suggestion or other).\r\n *\r\n * @author Betalord\r\n *\/\r\npublic class Report {\r\n\tprivate final String type;\r\n\t\/**\r\n\t * Dialog that we open which contains edit box and &quot;Send&quot; button, among other things.\r\n\t *\/\r\n\tprivate final SGXDialog reportDialog;\r\n\t\/**\r\n\t * Dialog that we show upon receiving a response from the server. We need a reference to it as we access it from the result() method.\r\n\t *\/\r\n\tprivate SGXDialog resultDialog;\r\n\t\r\n\t\/**\r\n\t * Creates the report widget. Call {@link #show()} to actually open the dialog so that user can fill in the report.\r\n\t *\r\n\t * @param type type of this report (e.g. bug report, suggestion, ...). Will be sent to the server (may be any string really).\r\n\t *\/\r\n\tpublic Report(String type) {\r\n\t\tSGXGame game = SGXGame.get(); \/\/ a shortcut\r\n\t\tthis.type = type;\r\n\t\t\r\n\t\tTable tabEmail = new Table();\r\n\t\ttabEmail.add(new SGXLabel(&quot;Contact e-mail:&quot;, game.uiSkin5, &quot;small&quot;));\r\n\t\tSGXTextField editEmail = new SGXTextField(&quot;&quot;, game.uiSkin5, &quot;default&quot;);\r\n\t\teditEmail.setMessageText(&quot;Click to type...&quot;);\r\n\t\ttabEmail.add(editEmail).growX();\r\n\t\t\r\n\t\tTable tab = new Table();\r\n\t\ttab.add(tabEmail).growX();\r\n\t\ttab.row();\r\n\t\tTextArea taReport = new SGXTextArea(&quot;&quot;, game.uiSkin5);\r\n\t\ttaReport.setMessageText(&quot;Enter your report&quot;);\r\n\t\ttab.add(taReport).growX().height(350);\r\n\t\ttab.row();\r\n\t\ttab.add(new SGXLabel(&quot;Alternatively you can submit your report at https:\/\/sgx.betalord.com site.&quot;, game.uiSkin5, &quot;small&quot;));\r\n\t\t\r\n\t\treportDialog = DialogUtils.DialogSetup.setup(&quot;Create report&quot;)\r\n\t\t\t\t.table(tab)\r\n\t\t\t\t.button(&quot;Send&quot;, &quot;SEND&quot;)\r\n\t\t\t\t.button(&quot;Cancel&quot;, Input.Keys.ESCAPE, &quot;CANCEL&quot;)\r\n\t\t\t\t.addCloseButtonInTitleBar(&quot;CANCEL&quot;)\r\n\t\t\t\t.width(600)\r\n\t\t\t\t.quick()\r\n\t\t\t\t.result(new SGXCallableObj() {\r\n\t\t\t\t\t@Override\r\n\t\t\t\t\tpublic void call(Object obj) {\r\n\t\t\t\t\t\tif (obj == &quot;SEND&quot;) {\r\n\t\t\t\t\t\t\treportDialog.cancel(); \/\/ don&#039;t hide the dialog when user presses &quot;SEND&quot; button\r\n\t\t\t\t\t\t\tString reportData = taReport.getText(); \/\/ never null, may be empty (&quot;&quot;)\r\n\t\t\t\t\t\t\tString senderEmail = editEmail.getText(); \/\/ never null, may be empty (&quot;&quot;)\r\n\t\t\t\t\t\t\tboolean emailValid = Misc.emailPattern.matcher(senderEmail).matches();\r\n\t\t\t\t\t\t\tif (reportData.trim().equals(&quot;&quot;)) {\r\n\t\t\t\t\t\t\t\t\/\/ report is empty!\r\n\t\t\t\t\t\t\t\tDialogUtils.DialogSetup.setup(&quot;Unable to submit the report&quot;, &quot;Your report is empty. Please fill it in before sending it.&quot;)\r\n\t\t\t\t\t\t\t\t\t\t.quick()\r\n\t\t\t\t\t\t\t\t\t\t.addOKButton()\r\n\t\t\t\t\t\t\t\t\t\t.show();\r\n\t\t\t\t\t\t\t} else if (senderEmail.trim().equals(&quot;&quot;) || !emailValid) {\r\n\t\t\t\t\t\t\t\t\/\/ sender email is empty!\r\n\t\t\t\t\t\t\t\tDialogUtils.DialogSetup.setup(&quot;Unable to submit the report&quot;, &quot;Sender e-mail address is empty or invalid. Please fill it in as we need it in case we need to contact you for further info. Thank you!&quot;)\r\n\t\t\t\t\t\t\t\t\t\t.quick()\r\n\t\t\t\t\t\t\t\t\t\t.addOKButton()\r\n\t\t\t\t\t\t\t\t\t\t.show();\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\/\/ all OK, file the report!\r\n\t\t\t\t\t\t\t\tfileReport(reportData, senderEmail);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\t.create();\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Open the report dialog so that the user can fill in the report and send it to the server.\r\n\t *\/\r\n\tpublic void show() {\r\n\t\treportDialog.show();\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Will send what user wrote to the server and show a dialog telling user that we are contacting the server and will also notify user of success or failure.\r\n\t *\/\r\n\tprivate void fileReport(String reportData, String senderEmail) {\r\n\t\tfinal SGXDialog dialog = DialogUtils.DialogSetup.setup(&quot;Reporting&quot;, &quot;Contacting server and filing the report...&quot;)\r\n\t\t\t\t.quick()\r\n\t\t\t\t.show();\r\n\t\t\r\n\t\tfinal Thread thread = new Thread(() -&gt; {\r\n\t\t\tConsistencyResult result = ConsistencyResult.consistent();\r\n\t\t\ttry {\r\n\t\t\t\tString response = sendReportToServer(reportData, senderEmail);\r\n\t\t\t\tif (response == null)\r\n\t\t\t\t\tresult = ConsistencyResult.inconsistent(&quot;No response from the server.&quot;);\r\n\t\t\t\telse if (response.startsWith(&quot;OK&quot;)) {\r\n\t\t\t\t\t\/\/ All fine - no need to do anything!\r\n\t\t\t\t} else {\r\n\t\t\t\t\t\/\/ Server response starts with &quot;FAIL&quot; or some other error string that precedes it (e.g. &quot;&lt;b&gt;Warning&lt;\/b&gt;:  mail(): Failed to connect to mailserver...&quot;)\r\n\t\t\t\t\tresult = ConsistencyResult.inconsistent(&quot;Server responded: &#039;&quot; + response + &quot;&#039;&quot;);\r\n\t\t\t\t}\r\n\t\t\t} catch (IOException e) {\r\n\t\t\t\tresult = ConsistencyResult.inconsistent(&quot;Unable to contact server.&quot;);\r\n\t\t\t\tMisc.err(&quot;Error filing report: &quot; + e.getLocalizedMessage());\r\n\t\t\t\te.printStackTrace();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfinal ConsistencyResult finalResult = result;\r\n\t\t\t\r\n\t\t\tGdx.app.postRunnable(() -&gt; {\r\n\t\t\t\tdialog.hide();\r\n\t\t\t\t\r\n\t\t\t\tif (finalResult.isConsistent()) { \/\/ success\r\n\t\t\t\t\treportDialog.hide();\r\n\t\t\t\t\t\r\n\t\t\t\t\tresultDialog = DialogUtils.DialogSetup.setup(&quot;Report submitted&quot;, &quot;Your report has been successfully filed. Thank you!&quot;)\r\n\t\t\t\t\t\t\t.addOKButton(&quot;Close&quot;)\r\n\t\t\t\t\t\t\t.quick()\r\n\t\t\t\t\t\t\t.create();\r\n\t\t\t\t\tresultDialog.show();\r\n\t\t\t\t} else { \/\/ failed\r\n\t\t\t\t\tresultDialog = DialogUtils.DialogSetup.setup(&quot;Report failed&quot;, &quot;Unable to submit report: an error occurred. Please try again later or submit your report manually at sgx.betalord.com web site.&quot;)\r\n\t\t\t\t\t\t\t.addOKButton(&quot;Close&quot;)\r\n\t\t\t\t\t\t\t.button(&quot;Error log&quot;, &quot;LOG&quot;)\r\n\t\t\t\t\t\t\t.addCloseButtonInTitleBar()\r\n\t\t\t\t\t\t\t.quick()\r\n\t\t\t\t\t\t\t.result(obj -&gt; {\r\n\t\t\t\t\t\t\t\tif (obj == &quot;LOG&quot;) { \/\/ we clicked the &quot;Error log&quot; button\r\n\t\t\t\t\t\t\t\t\tresultDialog.cancel(); \/\/ don&#039;t close the dialog upon clicking on &quot;Error log&quot; button\r\n\t\t\t\t\t\t\t\t\tDialogUtils.DialogSetup.setup(&quot;Error log&quot;, finalResult.getReason())\r\n\t\t\t\t\t\t\t\t\t\t\t.quick()\r\n\t\t\t\t\t\t\t\t\t\t\t.addOKButton()\r\n\t\t\t\t\t\t\t\t\t\t\t.show();\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t.create();\r\n\t\t\t\t\t\r\n\t\t\t\t\tresultDialog.show();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}, &quot;File Report Thread&quot;);\r\n\t\t\r\n\t\t\/\/ start:\r\n\t\tthread.start();\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Low-level method that sends report data to the server.\r\n\t * This method is thread-safe.\r\n\t *\r\n\t * @return response from the server (the generated page), or null in case of an error\r\n\t *\/\r\n\tprivate String sendReportToServer(String reportData, String senderEmail) throws IOException {\r\n\t\t\/\/ Partially based on this example: https:\/\/github.com\/apache\/httpcomponents-client\/blob\/master\/httpclient5\/src\/test\/java\/org\/apache\/hc\/client5\/http\/examples\/ClientMultipartFormPost.java\r\n\t\t\r\n\t\tString result;\r\n\t\ttry (final CloseableHttpClient httpclient = HttpClients.createDefault()) {\r\n\t\t\t\/\/final HttpPost httppost = new HttpPost(&quot;http:\/\/localhost:80\/web_reporting\/report.php&quot;);\r\n\t\t\tfinal HttpPost httppost = new HttpPost(&quot;https:\/\/sgx.betalord.com\/admin\/report.php&quot;); \/\/ note: putting &quot;http:\/\/&quot; instead of &quot;https:\/\/&quot; will cause $_POST to be empty (due to rewrite rules for the apache web server). This was actually a problem I was having (see: https:\/\/stackoverflow.com\/questions\/1282909\/php-post-array-empty-upon-form-submission)\r\n\t\t\t\r\n\t\t\tfinal StringBody type = new StringBody(Report.this.type, ContentType.TEXT_PLAIN);\r\n\t\t\tfinal StringBody sender = new StringBody(senderEmail, ContentType.TEXT_PLAIN);\r\n\t\t\tfinal StringBody contents = new StringBody(reportData, ContentType.TEXT_PLAIN);\r\n\t\t\t\r\n\t\t\tfinal HttpEntity reqEntity = MultipartEntityBuilder.create()\r\n\t\t\t\t\t\/\/.addPart(&quot;bin&quot;, bin)\r\n\t\t\t\t\t.addPart(&quot;type&quot;, type)\r\n\t\t\t\t\t.addPart(&quot;sender&quot;, sender)\r\n\t\t\t\t\t.addPart(&quot;report&quot;, contents)\r\n\t\t\t\t\t.addPart(&quot;auth&quot;, new StringBody(&quot;password&quot;, ContentType.TEXT_PLAIN))\r\n\t\t\t\t\t.build();\r\n\t\t\t\r\n\t\t\thttppost.setEntity(reqEntity);\r\n\t\t\t\r\n\t\t\tresult = httpclient.execute(httppost, response -&gt; {\r\n\t\t\t\tif (Version.areWeDeveloping())\r\n\t\t\t\t\tSystem.out.println(httppost + &quot;-&gt;&quot; + new StatusLine(response));\r\n\t\t\t\tfinal HttpEntity resEntity = response.getEntity();\r\n\t\t\t\tString serverResponse = null;\r\n\t\t\t\tif (resEntity != null) {\r\n\t\t\t\t\tserverResponse = EntityUtils.toString(response.getEntity()); \/\/ process response message and convert it into a value object\r\n\t\t\t\t}\r\n\t\t\t\tEntityUtils.consume(response.getEntity());\r\n\t\t\t\treturn serverResponse; \/\/ we must not return &quot;EntityUtils.toString(response.getEntity())&quot; at this point as the stream is already closed (by Entityutils.consume() method), or else we&#039;ll get this exception: org.apache.hc.core5.http.StreamClosedException: Stream already closed\r\n\t\t\t});\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n}\r\n<\/code><\/pre>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-f941c47 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"f941c47\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-5c1e7ad\" data-id=\"5c1e7ad\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-7c994ea elementor-widget elementor-widget-text-editor\" data-id=\"7c994ea\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h6>PHP report.php file:<\/h6>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-7ade15c elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"7ade15c\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-305c4e4\" data-id=\"305c4e4\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-caf0da6 elementor-widget elementor-widget-code-block-for-elementor\" data-id=\"caf0da6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-block-for-elementor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre class='line-numbers theme-okaidia' data-show-toolbar='yes'><code class='language-php'>&lt;?php\r\n\/*\r\nScript that sends an email upon receiving a user report from the SGX application through POST mechanism.\r\n\r\n(c) Betalord 2023\r\n*\/\r\n\r\n\t$AUTH_CODE = &quot;password&quot;; \/\/ some password just to make sure some hacker doesn&#039;t abuse of this email sending script\r\n\r\n\tif (!isset($_POST)) die(&quot;FAIL (no POST data)&quot;);\r\n\t$auth = $_POST[&quot;auth&quot;]; \/\/ authentication pass code\r\n\t$type = $_POST[&quot;type&quot;];\r\n\t$sender = $_POST[&quot;sender&quot;]; \/\/ may be empty string, but not undefined\r\n\t$report = $_POST[&quot;report&quot;];\r\n\r\n\tif (!isset($auth) || $auth != $AUTH_CODE)\r\n\t\tdie(&quot;FAIL (wrong auth)&quot;);\r\n\r\n\tif (!isset($sender) || !isset($type) || empty($type) || !isset($report) || empty($report))\r\n\t\tdie(&quot;FAIL (missing POST data)&quot;);\r\n\r\n\t$to = &quot;sgx@betalord.com&quot;;\r\n\t$subject = &quot;SGX IN-GAME REPORT (&quot; . $type . &quot;)&quot;;\r\n\r\n\t$message = &quot;SENDER: &quot; . $sender . &quot;\\r\\n&quot;;\r\n\t$message .= &quot;TYPE: &quot; . $type . &quot;\\r\\n&quot;;\r\n\t$message .= &quot;\\r\\n&quot;;\r\n\t$message .= $report;\r\n\r\n\t$headers = array(\r\n\t\t&#039;From&#039; =&gt; &#039;sgx@betalord.com&#039;,\r\n\t\t&#039;Reply-To&#039; =&gt; $sender,\r\n\t\t&#039;X-Mailer&#039; =&gt; &#039;PHP\/&#039; . phpversion()\r\n\t);\r\n\t\r\n\t$retval = mail($to, $subject, $message, $headers);\r\n\t\r\n\tif ($retval == true ) {\r\n\t\tdie(&quot;OK&quot;);\r\n\t} else {\r\n\t\tdie(&quot;FAIL (mail function failed)&quot;);\r\n\t}\r\n\t\r\n\tdie(&quot;OK&quot;);\r\n?&gt;<\/code><\/pre>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Recently I had to implement user bug reporting feature. What it does is it makes possible for the user to report bugs [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17],"tags":[12,10,13],"class_list":["post-856","post","type-post","status-publish","format-standard","hentry","category-article","tag-java","tag-libgdx","tag-php"],"_links":{"self":[{"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/posts\/856","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/comments?post=856"}],"version-history":[{"count":7,"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/posts\/856\/revisions"}],"predecessor-version":[{"id":871,"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/posts\/856\/revisions\/871"}],"wp:attachment":[{"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/media?parent=856"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/categories?post=856"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/betalord.com\/code\/wp-json\/wp\/v2\/tags?post=856"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}