Showing posts with label APEX. Show all posts
Showing posts with label APEX. Show all posts

Friday, 26 December 2025

Web Tutorial: Ruby On Rails Xmas Poll (Part 4/4)

We'll, of course, want to find a way to view the poll results. To do that, let's first go back to Oracle APEX and create an endpoint for this. Remember the template we created in the first part of this web tutorial? It was called /poll/:id/results/.

Then we create a GET handler for this.


And this is the Pl/SQL code. Do bear in mind that here, averages and medians only make sense because the answers in the poll are numerical. In any case, tables are joined and columns are grouped to get these averages and medians. Before using the AVG() and MEDIAN() functions, we must first run TO_NUMBER() on the data, to convert them to numerical values.
SELECT
pq.TITLE,
AVG(TO_NUMBER(pr.RESULT)) AS AVG_RESULT,
MEDIAN(TO_NUMBER(pr.RESULT)) AS MEDIAN_RESULT
FROM POLL_QUESTIONS pq LEFT JOIN POLL_RESULTS pr
ON pr.QUESTION_SERIAL_NO = pq.SERIAL_NO AND pr.POLL_ID = :id AND pq.POLL_ID = :id
GROUP BY pq.TITLE
ORDER BY AVG_RESULT DESC, MEDIAN_RESULT DESC


The results should be something like this.
{
 "items": [
  {
   "title": "IT CAME UPON THE MIDNIGHT CLEAR",
   "avg_result": 4,
   "median_result": 5
  },
  {
   "title": "JINGLE BELL ROCK",
   "avg_result": 4,
   "median_result": 4.5
  },
  {
   "title": "SILENT NIGHT",
   "avg_result": 3.5,
   "median_result": 3
  },
  {
   "title": "THE FIRST NOEL",
   "avg_result": 3.16666666666666666666666666666666666667,
   "median_result": 3
  },
  {
   "title": "JINGLE BELLS",
   "avg_result": 3,
   "median_result": 3
  },
  {
   "title": "HARK THE HERALD ANGELS SING",
   "avg_result": 2.83333333333333333333333333333333333333,
   "median_result": 3
  },
  {
   "title": "DECK THE HALLS",
   "avg_result": 2.66666666666666666666666666666666666667,
   "median_result": 3
  },
  {
   "title": "JOY TO THE WORLD",
   "avg_result": 2.54545454545454545454545454545454545455,
   "median_result": 3
  },
  {
   "title": "RUDOLPH THE RED-NOSED REINDEER",
   "avg_result": 2.5,
   "median_result": 2.5
  },
  {
   "title": "I SAW MOMMY KISSING SANTA CLAUS",
   "avg_result": 2.27272727272727272727272727272727272727,
   "median_result": 2
  }
 ],
 "hasMore": false,
 "limit": 25,
 "offset": 0,
 "count": 10,
 "links": [
  {
   "rel": "self",
   "href": "https://oracleapex.com/ords/teochewthunder/polls/poll/1/results/"
  },
  {
   "rel": "describedby",
   "href": "https://oracleapex.com/ords/teochewthunder/metadata-catalog/polls/poll/1/results/"
  },
  {
   "rel": "first",
   "href": "https://oracleapex.com/ords/teochewthunder/polls/poll/1/results/"
  }
 ]
}


Back to the Rails server! In the CLI, run this command to create the controller result.
rails generate controller result

Once we've done that, it's time to modify the generated file. Again, we need httparty. We'll reuse the environment variable ORDS_API_URL but append "/results" to it.

app/controllers/result_controller.rb
require "httparty"

class ResultController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"] + "/results"

    def index

    end

end

Of course, the next step is to mirror what we did for the index action of the root page, and use HTTParty's get() method to call the URL endpoint, then have an If block to handle the result.

app/controllers/result_controller.rb
require "httparty"

class ResultController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"] + "/results"

    def index
        response = HTTParty.get(
            ORDS_API_URL
        )

        if response.code == 200


        else

            flash.now[:alert] = "Error fetching data."
            @api_data = {}
        end
    end
end

If successful, we return the result to the view by binding it to api_data.

app/controllers/result_controller.rb
require "httparty"

class ResultController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"] + "/results"

    def index
        response = HTTParty.get(
            ORDS_API_URL
        )

        if response.code == 200
            @api_data = response.parsed_response
        else
            flash.now[:alert] = "Error fetching data."
            @api_data = {}
        end
    end
end

This is the view. We have a header, a div styled using the CSS class pollresult, and a table.

app/views/result/index.html.erb
<h1>Poll Results</h1>
<div class="pollresult">
  <table>
    <tr>
      <td width="300px"><b>Carol</b></td><td width="150px" style="text-align: right;"><b>Average Rating</b></td>
      <td width="150px" style="text-align: right;"><b>Median Rating</b></td>
    </tr>
  </table>
</div>

Refer to the sample JSON result I showed you earlier. We'll use the title, avg_result and median_result properties here.

app/views/result/index.html.erb
<h1>Poll Results</h1>
<div class="pollresult">
  <table>
    <tr>
      <td width="300px"><b>Carol</b></td><td width="150px" style="text-align: right;"><b>Average Rating</b></td>
      <td width="150px" style="text-align: right;"><b>Median Rating</b></td>
    </tr>

    <% @api_data["items"].each do |result| %>
      <tr>
        <td><%= "#{result['title']}" %></td>
        <td style="text-align: right;"><%= "#{result['avg_result']}" %></td>
        <td style="text-align: right;"><%= "#{result['median_result']}" %></td>
      </tr>
    <% end %>

  </table>
</div>


For the CSS, if you're a lazy bastard like me, you might just want to piggyback off this existing class, or create a new one with a custom design.

app/assets/stylesheets/application.css
.pollform, .pollresult
{
  width: 600px;
  padding: 10px;
  border-radius: 10px;
  border: 3px solid rgb(200, 0, 0);
  background-color: rgb(0, 200, 0);
  margin: 5% auto 0 auto;
}


Do you see the problem? The average is way too long and needs to be truncated.

We use sprintf(), passing in "%.1f" to truncate the text to one decimal place.

app/views/result/index.html.erb
<h1>Poll Results</h1>
<div class="pollresult">
  <table>
    <tr>
      <td width="300px"><b>Carol</b></td><td width="150px" style="text-align: right;"><b>Average Rating</b></td>
      <td width="150px" style="text-align: right;"><b>Median Rating</b></td>
    </tr>

    <% @api_data["items"].each do |result| %>
      <tr>
        <td><%= "#{result['title']}" %></td>
        <td style="text-align: right;"><%= "#{sprintf('%.1f', result['avg_result'])}" %></td>
        <td style="text-align: right;"><%= "#{sprintf('%.1f', result['median_result'])}" %></td>
      </tr>
    <% end %>
  </table>
</div>


There you go!


For a final touch, add a nav link to each of these pages.

app/views/poll/index.html.erb
<h1><%= @api_data["items"][0]["name"] %></h1>
<h2><%= link_to "View Results", result_page_path %></h2>
<div class="pollform">


app/views/result/index.html.erb
<h1>Poll Results</h1>
<h2><%= link_to "View Poll", root_path %></h2>
<div class="pollresult">


Then style the h2 tag.

app/assets/stylesheets/application.css
h1
{
  text-align: center;
  color: rgba(255, 255, 255, 0.5);
}


h2
{
  text-align: center;
  font-size: 0.8em;
}


.pollform, .pollresult
{
  width: 600px;
  padding: 10px;
  border-radius: 10px;
  border: 3px solid rgb(200, 0, 0);
  background-color: rgb(0, 200, 0);
  margin: 5% auto 0 auto;
}






Unit Tests

It's time to write a few unit tests! Bear in mind that these are just samples. There are probably better tests one can write. This is the default test that is generated when we create a controller.

test/controllers/poll_controller_test.rb
require "test_helper"

class PollControllerTest < ActionDispatch::IntegrationTest
    test "should get index" do
        get poll_page_url
        assert_response :success
        assert_not_nil assigns(:api_data)
    end
end


And more. Here, we test submitting the form by sending a sample payload, and checking a couple of expected behaviors. We check if there's a redirect to the form page, and that there's a flash notice.

test/controllers/poll_controller_test.rb
require "test_helper"

class PollControllerTest < ActionDispatch::IntegrationTest
    test "should get index" do
        get poll_page_url
        assert_response :success
        assert_not_nil assigns(:api_data)
    end

    test "should submit results" do
        post submit_poll_form_url, params: { answers: { "1" => "3", "2" => "5", "3" => "2"} }
        assert_response :redirect
        assert_redirected_to root_path
        assert_not_nil flash[:notice]
    end
end

The result controller test should look like this as well.

test/controllers/result_controller_test.rb
require "test_helper"

class ResultControllerTest < ActionDispatch::IntegrationTest
    test "should get index" do
        get result_page_url
        assert_response :success
        assert_not_nil assigns(:api_data)
    end
end


When you run these tests, that's what you should get.




Conclusion

Looks like it's the day after Christmas.

This concludes my first Ruby On Rails web tutorial in years. I like to think it's better than my last couple efforts. That's because despite being an old fart, I'm still growing and adapting, and so should you. Enjoy your holidays!

Poll-ite season's greetings!
T___T

Tuesday, 23 December 2025

Web Tutorial: Ruby On Rails Xmas Poll (Part 3/4)

We have a form, and now it's a matter of submitting it. In the Submit action of the Poll controller, we want to collect this data and send it to Oracle APEX.

We'll have to first massage this data into a payload to send. For that, we declare answers as the the collection of all the HTML elements with answers as the name. Then we declare payload as an object with one property, answers. The value of that, will be answers.

app/controllers/poll_controller.rb
def submit
    answers = params[:answers]
    payload = { answers: answers }

end


We then use HTTParty to POST, just like we used it to send a GET request earlier. The base URL is the same - we'll use ORDS_API_URL. For the body, we use payload after running the to_json() method on it, to convert it to a JSON object. And because of this, we should specify that it's JSON in the headers object. The result is returned in the variable response.

app/controllers/poll_controller.rb
def submit
    answers = params[:answers]
    payload = { answers: answers }

    response = HTTParty.post(
        ORDS_API_URL,

        body: payload.to_json,
        headers: {
            "Content-Type" => "application/json"
        }
    )
end


Now, if it's successful, the code property of response will be 200. In that case flash a green success message. If not, flash a red error message.

app/controllers/poll_controller.rb
def submit
    answers = params[:answers]
    payload = { answers: answers }

    response = HTTParty.post(
        ORDS_API_URL,
        body: payload.to_json,
        headers: {
            "Content-Type" => "application/json"
        }
    )

    if response.code == 200
        flash[:notice] = "Submission successful!"

    else
        flash[:alert] = "API error."

    end
end


When all's said and done, use the redirect_to statement to return to root_path, which is the poll form.
app/controllers/poll_controller.rb
def submit
    answers = params[:answers]
    payload = { answers: answers }

    response = HTTParty.post(
        ORDS_API_URL,
        body: payload.to_json,
        headers: {
            "Content-Type" => "application/json"
        }
    )

    if response.code == 200
        flash[:notice] = "Submission successful!"
    else
        flash[:alert] = "API error."
    end

    redirect_to root_path
end


Next, let's go back to Oracle APEX. Remember we created the GET handler for the API endpoint "poll/:id"? Well, now create a POST handler.


Make sure the id variable is defined, and we tell Oracle APEX that it's to be found in the URL.



Before we examine the PL/SQL code for the handler, this is the shape of the data that will be sent, as an example.
{
  "Answers": {
    "1": "4",
    "2": "5",
    "3": "1",
    "4": "2",
    "5": "1",
    "6": "4",
    "7": "4",
    "8": "3",
    "9": "3",
    "10": "5"
  }
}


We have a DECLARE, BEGIN and END statements. After DECLARE, we declare l_request_body_clob as a CLOB object. A CLOB is a Character Large Object, which pretty much describes the data that will be sent to Oracle APEX via the form. Then we declare l_keys as an array used to store strings. (If you're curious, the "l" prefix is used to say "local". Seems superfluous, but it's Oracle's convention, so...)
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN

END;


body_text refers to the data sent in the form, via the API endpoint. This value is assigned to the variable l_request_body_clob.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;
END;


Then we use the parse() method of the APEX_JSON object, passing in l_request_body_clob as the p_source parameter's value. This, in effect, parses the form body data.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);
END;


Now for l_keys. We want to get the keys from the answers object. So we use the get_members() method of the APEX_JSON object (which already parsed the form data) and specify that the name of the object is "answers" by setting that as the parameter value of p_path. This in effect produces an array of all the keys in the form data, and binds that value to the array l_keys.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);

    l_keys := APEX_JSON.get_members(p_path => 'answers');
END;


To be safe, we have an IF block to check that l_keys is a valid non-empty array. Then we iterate through l_keys.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);

    l_keys := APEX_JSON.get_members(p_path => 'answers');

    IF l_keys IS NOT NULL AND l_keys.COUNT > 0 THEN
        FOR i IN 1..l_keys.COUNT LOOP

            DECLARE

            BEGIN


            END;

        END LOOP;
    END IF;
END;


We'll declare the serial number and answer here, in the variables l_serial_no and l_answer_value respectively. We know that those are just numbers and they won't go above 10, so "VARCHAR2(2)" is safe enough.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);

    l_keys := APEX_JSON.get_members(p_path => 'answers');

    IF l_keys IS NOT NULL AND l_keys.COUNT > 0 THEN
        FOR i IN 1..l_keys.COUNT LOOP
            DECLARE
                l_serial_no VARCHAR2(2);
                l_answer_value VARCHAR2(2);
            BEGIN

            END;
        END LOOP;
    END IF;
END;


Then we assign the value of the current element of l_keys, to l_serial_no. And we use the get_varchar2() method of APEX_JSON, again using "answers." and the current value of l_serial_number ("||" is actually concatenation in PL/SQL, so we're trying to read the value of answers.1, answers.2, and so on.) as the value of p_path, and assign the value to l_answer_value. Phew! That was a mouthful. But you get the idea... I hope.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);

    l_keys := APEX_JSON.get_members(p_path => 'answers');

    IF l_keys IS NOT NULL AND l_keys.COUNT > 0 THEN
        FOR i IN 1..l_keys.COUNT LOOP
            DECLARE
                l_serial_no VARCHAR2(2);
                l_answer_value VARCHAR2(2);
            BEGIN
                l_serial_no := l_keys(i);
                l_answer_value := APEX_JSON.get_varchar2(p_path => 'answers.' || l_serial_no);
            END;
        END LOOP;
    END IF;
END;


And we write an INSERT statement that adds a row with the values of l_serial_no and l_answer_value. Because QUESTION_SERIAL_NO is an integer, we need to use the TO_NUMBER() function on l_serial_no. POLL_ID will be the variable id in the POST handler.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);

    l_keys := APEX_JSON.get_members(p_path => 'answers');

    IF l_keys IS NOT NULL AND l_keys.COUNT > 0 THEN
        FOR i IN 1..l_keys.COUNT LOOP
            DECLARE
                l_serial_no VARCHAR2(2);
                l_answer_value VARCHAR2(2);
            BEGIN
                l_serial_no := l_keys(i);
                l_answer_value := APEX_JSON.get_varchar2(p_path => 'answers.' || l_serial_no);

                INSERT INTO POLL_RESULTS (POLL_ID, QUESTION_SERIAL_NO, RESULT)
                VALUES (:id, TO_NUMBER(l_serial_no), l_answer_value);
            END;
        END LOOP;
    END IF;
END;


The we use the open_object(), write() and close_object() of APEX_JSON to set status and message as a response.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);

    l_keys := APEX_JSON.get_members(p_path => 'answers');

    IF l_keys IS NOT NULL AND l_keys.COUNT > 0 THEN
        FOR i IN 1..l_keys.COUNT LOOP
            DECLARE
                l_serial_no VARCHAR2(255);
                l_answer_value VARCHAR2(4000);
            BEGIN
                l_serial_no := l_keys(i);
                l_answer_value := APEX_JSON.get_varchar2(p_path => 'answers.' || l_serial_no);

                INSERT INTO POLL_RESULTS (POLL_ID, QUESTION_SERIAL_NO, RESULT)
                VALUES (:id, TO_NUMBER(l_serial_no), l_answer_value);
            END;
        END LOOP;
    END IF;

    APEX_JSON.open_object;
    APEX_JSON.write('status', 'success');
    APEX_JSON.write('message', 'Answers processed successfully');
    APEX_JSON.close_object;

END;


Then we have a provision for if anything goes wrong.
DECLARE
    l_request_body_clob CLOB;
    l_keys APEX_T_VARCHAR2 := APEX_T_VARCHAR2();
BEGIN
    l_request_body_clob := :body_text;

    APEX_JSON.parse(p_source => l_request_body_clob);

    l_keys := APEX_JSON.get_members(p_path => 'answers');

    IF l_keys IS NOT NULL AND l_keys.COUNT > 0 THEN
        FOR i IN 1..l_keys.COUNT LOOP
            DECLARE
                l_serial_no VARCHAR2(255);
                l_answer_value VARCHAR2(4000);
            BEGIN
                l_serial_no := l_keys(i);
                l_answer_value := APEX_JSON.get_varchar2(p_path => 'answers.' || l_serial_no);

                INSERT INTO POLL_RESULTS (POLL_ID, QUESTION_SERIAL_NO, RESULT)
                VALUES (:id, TO_NUMBER(l_serial_no), l_answer_value);
            END;
        END LOOP;
    END IF;

    APEX_JSON.open_object;
    APEX_JSON.write('status', 'success');
    APEX_JSON.write('message', 'Answers processed successfully');
    APEX_JSON.close_object;

    EXCEPTION
        WHEN OTHERS THEN

            APEX_JSON.open_object;
            APEX_JSON.write("status", "error");
            APEX_JSON.write("message", "PL/SQL Error: " || SQLERRM);

            APEX_JSON.close_object;
END;


Time to test this! Fill in the poll and click SEND.


You should see this!


And the results in Oracle APEX's database, in the POLL_RESULTS table!


One more thing...

An anti-CSFR token is very easy to apply in Ruby On Rails. Just go to this file. When you rerun your form, you should see a hidden field if you view the source. Everything else is taken care of, including the validation.

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
    allow_browser versions: :modern
    protect_from_forgery with: :exception
end


Next

Viewing the results, and testing.

Wednesday, 17 December 2025

Web Tutorial: Ruby On Rails Xmas Poll (Part 1/4)

Merry Christmas season to all!

It's been years since I got my hands dirty with Ruby On Rails, and seeing as I got time, why not now? Ruby, despite me never having had an opportunity to use it professionally, is probably one of my favorite programming languages that I ever picked up for no goddamn practical reason. And, well, this Christmas season, I wanna go back to my guilty pleasure.

Today, we will be creating a form in Ruby On Rails, to submit answers to a poll. This will be a simple Christmas Carol Poll, where the user rates all presented Chsristmas Carols on a scale of 1 to 5.

Normally, a Ruby On Rails project would have its own built-in server capabilities, but I wanted to see how Oracle APEX worked with Rails. Pretty darn well, actually. Thus, what I did was first log onto my Oracle APEX workspace and create some new tables.

The first, obviously, would be a POLLS table. This is the PL/SQL statement for the table, though you can use the UI to create it. It basically is just an auto-increment integer column, ID, and a string, NAME.
CREATE TABLE "POLLS" (
    "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
    "NAME" VARCHAR2(100 CHAR) NOT NULL ENABLE,
    CONSTRAINT "POLLS_PK" PRIMARY KEY ("ID") USING INDEX ENABLE
);


Far easier to just copy this to clipboard, go to SQL > SQL Command and paste into the box, then run it.


There, you have the POLLS table. Ignore the other tables; they're remnants of my last Oracle APEX project.


I'm just going to enter the name of the poll here, manually.


The next table is POLL_QUESTIONS. Here's the SQL. This one is just a bit more complicated. We don't have an ID field here; instead uniqueness is determined by the combination of POLL_ID (which is a foreign key to the POLLS table) and NO. NO is an integer that denotes the serial number of that question within a poll. TITLE is a string which displays the question, and ANSWERS is a comma-separated string of possible values.
CREATE TABLE "POLL_QUESTIONS" (
    "NO" NUMBER(2,0) NOT NULL ENABLE,
    "TITLE" VARCHAR2(100 CHAR),
    "POLL_ID" NUMBER NOT NULL ENABLE,
    "ANSWERS" VARCHAR2(200),
    CONSTRAINT "POLL_QUESTIONS_PK" PRIMARY KEY ("NO", "POLL_ID") USING INDEX ENABLE
);


The easy thing to do would be to run SQL Command with this query.


Again, I'm going to do some manual data entry here. Notice that POLL_ID is always 1, because we only have the one entry in POLLS. For the column ANSWERS, it is a JSON-encoded array of the values 1 to 5.


Finally, we have POLL_RESULTS. ID is a primary key just like in POLLS. Each row will have two foreign keys - POLL_ID and QUESTION_NO which reference POLL_QUESTIONS. RESULT will store the answer for the specific question referenced by the last two fields. POLL_ID and QUESTION_NO.
CREATE TABLE "POLL_RESULTS" (
    "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
    "POLL_ID" NUMBER NOT NULL ENABLE,
    "QUESTION_NO" NUMBER NOT NULL ENABLE,
    "RESULT" VARCHAR2(100 CHAR) NOT NULL ENABLE,
    CONSTRAINT "POLL_RESULTS_PK" PRIMARY KEY ("ID") USING INDEX ENABLE
);


We can create the table the easy way here. We will not be populating this table manually. No, the entire point is to populate this table by means of submission through the Ruby On Rails form.


Time to determine API endpoints!

We've populated the POLLS and POLL_QUESTIONS table because we want to use this data to populate the form. In order for that to happen, we'll need to provide a means of retrieving that data. And that will be REST API endpoints. For now, we will do just a GET endpoint.

Go to SQL Workshop, then go to RESTful Services. Create a new Module and let's call it "polls". The endpoint here will be "/polls/".


Congrats! You have a Module! Now you need to add Templates to this Module. Click on that button, Create Template, near the bottom right side of the screen.

Create two Templates. The first one is poll/:id, and the second is poll/:id/results/.



Go to the first Template you created, poll/:id. You'll need to add a GET handler to it. To do that, click on the Create Handler button somewhere near the bottom right area of the screen. We haven't forgotten about the second Template, but we'll get to it later.


This is the SQL code that will be in that box. It's a simple join between POLLS and POLL_QUESTIONS.
SELECT p.NAME, pq.SERIAL_NO, pq.TITLE FROM POLLS p
LEFT JOIN POLL_QUESTIONS pq ON pq.POLL_ID = p.ID
WHERE p.ID = :id
ORDER BY pq.SERIAL_NO


This is a good place to stop. We've set up some stuff on Oracle APEX that will be used in the next part of this web tutorial.

Next

The Ruby On Rails setup.

Sunday, 26 November 2023

Web Tutorial: The Self-affirmations WordPress Plugin (Part 4/4)

Having written a function to generate email, we will now send it!

Email in WordPress can be a tricky issue. Sure, we can use the native function wp_mail(). However, it is, half the time, going to fail depending on your server settings which you do not always have control over. Thus, it's advised to use an email plugin.

The one I am using for this, is WP Mail SMTP. Feel free to use your own. I used my email account at Google to configure WP Mail SMTP. Again, you may use any other method to configure it; this just happened to be the most convenient for me. Just read through the documentation, do a test send, and Bob's your uncle!


Once you have this out of the way, let's focus on writing the job that will send email on a regular basis. This is the function tt_selfaffirmations(). We begin by declaring list, and using it to store the result returned by running tt_get_readytoreceive(). Then we iterate through each element of list. There should be only one, though if you haven't reset the LAST_SENT column in your Oracle APEX database, it will be 0.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {

    }

}


In the Foreach loop, we get the string name by concatenating the first_name and last_name properties of the current element, with a space in between. Then using the value of name, and the current element's email, gender and dob properties, we get the email array by running tt_generate_mail().

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

    }
}


In this If block, we run wp_mail() using the email property, the title property of email and the body property of email with an "unsubscribe" line appended to the end. The rest of the arguments aren't that crucial. See the wp_email() function description here.

In essence, we're checking if the email was sent with no error.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

        if (wp_mail($l->email, $email["title"], $email["body"] . "\n\nTo unsubscribe to the Self-affirmations Mailing List, please reply to this email with the subject 'UNSUBSCRIBE'.", "", [] ))
        {

        }

    }
}


If so, we should run the tt_set_lastsent() function to set the LAST_SENT column of that record to today's date so the email won't get sent a second time.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

        if (wp_mail($l->email, $email["title"], $email["body"] . "\n\nTo unsubscribe to the Self-affirmations Mailing List, please reply to this email with the subject 'UNSUBSCRIBE'.", "", [] ))
        {
            tt_set_lastsent($l->email);
        }
    }
}


If you run the Test job link, this is what you should see. tt_get_readytoreceive() was run, followed by tt_get_terms() followed by tt_generate_email().


More importantly, this should appear in your inbox!


So the sending of email is good to go. Time to set up the CRON job. For this, I use the WP Crontrol plugin. After activating the plugin, go to Tools, then Cron Events.


Once you click on Add New, you should be redirected to this interface. We'll use "cron_selfaffirmations" as the hook name, and set it to run once daily.


Once you confirm, you should see it in the list.


What next? Well, go back to your plugin file. Add this line. It basically uses the add_action() function to link the tt_selfaffirmations() function with the cron_selfaffirmations hook. Thus when the CRON job kicks in, it will run tt_selfaffirmations()!

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

        if (wp_mail($l->email, $email["title"], $email["body"] . "\n\nTo unsubscribe to the Self-affirmations Mailing List, please reply to this email with the subject 'UNSUBSCRIBE'.", "", [] ))
        {
            tt_set_lastsent($l->email);
        }
    }
}

add_action("cron_selfaffirmations", "tt_selfaffirmations");


If you reset your LAST_SENT column in the Oracle APEX database, the email will be sent when the job next runs. Set it to tun 5 minutes from now and see if it works!

That's all!

Thank you for staying with me! This was fun, y'all. And I didn't even have to write all that much code!


Your Greatest Fan,
T___T

Friday, 24 November 2023

Web Tutorial: The Self-affirmations WordPress Plugin (Part 3/4)

The next function we need to deal with, is tt_generate_email(). Before we begin, you will need a OpenAI Developer Account, and an API key. It doesn't cost all that much, honestly, so I would absolutely recommend putting that credit card to good use.

We begin by adding in parameters for the function. We wat the email address ($id), the name, gender and birthday.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name, $gender, $dob)
{

}


For all the parameters other than the first, we will have default values. Thus, when you run this function without passing any values in, it will use these values.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name = "Teochew Thunder", $gender = "M", $dob = "01-01-1980")
{

}
-

We begin the function by declaring the variable terms, and running tt_get_terms() with the value of id passed in as an argument, to set terms to the returned value. Bear in mind that if there is no value given for id, tt_get_terms() will use "teochewthunder@gmail.com" as a default value.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name = "Teochew Thunder", $gender = "M", $dob = "01-01-1980")
{
    $terms = tt_get_terms($id);
}


We then declare interests as an empty string. And then we check if the interests array of terms is not empty. We'll only alter the value of interests if so.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name = "Teochew Thunder", $gender = "M", $dob = "01-01-1980")
{
    $terms = tt_get_terms($id);

    $interests = "";
    if (count($terms["interests"]) > 0)
    {
    
    }

}


Firstly, we use the value of gender to ensure that the string interests is correct in the gender sense.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name = "Teochew Thunder", $gender = "M", $dob = "01-01-1980")
{
    $terms = tt_get_terms($id);

    $interests = "";
    if (count($terms["interests"]) > 0)
    {
        $interests = ($gender == "M" ? "He" : "She") . " is interested in ";    
    }
}


Then we iterate through the interests array of the terms array using a For loop.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name = "Teochew Thunder", $gender = "M", $dob = "01-01-1980")
{
    $terms = tt_get_terms($id);

    $interests = "";
    if (count($terms["interests"]) > 0)
    {
        $interests = ($gender == "M" ? "He" : "She") . " is interested in ";
        for ($i = 0; $i < sizeof($terms["interests"]); $i++)
        {

        }    
    
    }
}


What we do next, is append the value of the current element to the interests string. Thus, if gender is "M" and the first element of interests is "rollerblading", the resultant string would be "He is interested in rollerblading". After that, we check if the current element is the final element of the interests array.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name = "Teochew Thunder", $gender = "M", $dob = "01-01-1980")
{
    $terms = tt_get_terms($id);

    $interests = "";
    if (count($terms["interests"]) > 0)
    {
        $interests = ($gender == "M" ? "He" : "She") . " is interested in ";
        for ($i = 0; $i < sizeof($terms["interests"]); $i++)
        {
            $interests .= $terms["interests"][$i];
            if ($i == sizeof($terms["interests"]) - 1)
            {

            }
            else
            {

            }
        }   
     
    }
}


If so, append a full-stop. If not, append the string "and" preceded and superseded by a space. Thus, if interests contains the values "jazz music". "spiders" and "surfing", and gender is "F", the resultant string would be "She is interested in jazz music and spiders and surfing". Not a perfect sentence, but it should make sense to ChatGPT.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_generate_mail($id, $name = "Teochew Thunder", $gender = "M", $dob = "01-01-1980")
{
    $terms = tt_get_terms($id);

    $interests = "";
    if (count($terms["interests"]) > 0)
    {
        $interests = ($gender == "M" ? "He" : "She") . " is interested in ";
        for ($i = 0; $i < sizeof($terms["interests"]); $i++)
        {
            $interests .= $terms["interests"][$i];
            if ($i == sizeof($terms["interests"]) - 1)
            {
                $interests .= ". ";
            }
            else
            {
                $interests .= " and ";
            }
        }        
    }
}


We'll do something similar for the descriptions array.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$interests = "";
if (count($terms["interests"]) > 0)
{
    $interests = ($gender == "M" ? "He" : "She") . " is interested in ";
    for ($i = 0; $i < sizeof($terms["interests"]); $i++)
    {
        $interests .= $terms["interests"][$i];
        if ($i == sizeof($terms["interests"]) - 1)
        {
            $interests .= ". ";
        }
        else
        {
            $interests .= " and ";
        }
    }        
}

$descriptions = "";
if (count($terms["descriptions"]) > 0)
{
    $descriptions = ($gender == "M" ? "He" : "She") . " is described as ";        
    for ($i = 0; $i < sizeof($terms["descriptions"]); $i++)
    {
        $descriptions .= $terms["descriptions"][$i];
        if ($i == sizeof($terms["descriptions"]) - 1)
        {
            $descriptions .= ". ";
        }
        else
        {
            $descriptions .= " and ";
        }
    }        
}



After that, we use name, gender and dob to formulate the string about.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$descriptions = "";
if (count($terms["descriptions"]) > 0)
{
    $descriptions = ($gender == "M" ? "He" : "She") . " is described as ";        
    for ($i = 0; $i < sizeof($terms["descriptions"]); $i++)
    {
        $descriptions .= $terms["descriptions"][$i];
        if ($i == sizeof($terms["descriptions"]) - 1)
        {
            $descriptions .= ". ";
        }
        else
        {
            $descriptions .= " and ";
        }
    }        
}

$about = ($gender == "M" ? "man" : "woman");
$about .= " named '" . $name . "'";
$about .= " born on " . explode("T", $dob)[0];



We then declare prompt and title as empty strings, and tokens as an integer with an initial value of 50. prompt_type is declared as a random number from 0 to 20. The future values of prompt, tokens and title will depend on the value of prompt_type.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$about = ($gender == "M" ? "man" : "woman");
$about .= " named '" . $name . "'";
$about .= " born on " . explode("T", $dob)[0];

$prompt_type = rand(0, 20);
$prompt = "";
$tokens = 50;
$title = "";



As you can see, in here I use a Switch statement on prompt_type and use that to determine values.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$prompt_type = rand(0, 20);
$prompt = "";
$tokens = 50;
$title = "";

switch($prompt_type)
{
    case 0: $prompt = "Generate a complimentary poem about"; $title = "A poem for you!"; $tokens = 3000; break;
    case 1: $prompt = "Generate some positive life advice for"; $title = "Some life advice"; $tokens = 1000; break;
    case 2: $prompt = "Generate a sample horoscope for"; $title = "Your Zodiac advice"; $tokens = 3000; break;
    case 3: $prompt = "Generate a Chinese zodiac horoscope for"; $title = "Your Zodiac advice"; $tokens = 3000; break;
    case 4: $prompt = "Generate an encouraging two paragraph letter to self for"; $title = "Your self-affirmation"; $tokens = 2000; break;
    case 5: $prompt = "Generate an encouraging one paragraph letter to self for"; $title = "Your self-affirmation"; $tokens = 1000; break;
    case 6: $prompt = "Generate an encouraging three paragraph letter to self for"; $title = "Your self-affirmation"; $tokens = 3000; break;            
    case 7: $prompt = "Generate a funny and uplifting short story about"; $title = "The Story of You"; $tokens = 3000; break;
    case 8: $prompt = "Generate five inspirational quotes from famous people for"; $title = "Five Quotes to make your day"; $tokens = 2500; break;
    case 9: $prompt = "Generate five fictitious short reviews from fictitious publications about"; $title = "Your reviews from public media"; $tokens = 2500; break;
    case 10: $prompt = "Generate five fictitious one-sentence reviews from fictitious people from diverse races and their occupations about"; $title = "Public Opinion About You"; $tokens = 2500; break;
    case 11: $prompt = "Generate five fictitious one-sentence reviews from fictitious people from diverse races complimenting the personality of"; $title = "Public Opinion About You"; $tokens = 2500; break;                
    case 12: $prompt = "Generate five corny pickup lines from random people for"; $title = "Pickup Lines For You"; $tokens = 2500; break;
    case 13: $prompt = "Generate a character testimonial (from self) for"; $title = "Your testimonial!"; $tokens = 3000; break;
    case 14: $prompt = "Write a love letter (from self) to"; $title = "Some self-love"; $tokens = 3000; break;
    case 15: $prompt = "Create a short movie synopsis with famous actors about"; $title = "A movie was made about you!"; $tokens = 3000; break;            
    case 16: $prompt = "Create a movie role, with famous co-stars, for"; $title = "A movie role for you"; $tokens = 3000; break;            
    case 17: $prompt = "Write a welcoming letter from the President of a Fan Club centered around"; $title = "Welcome Address From " . $name . " Fan Club"; $tokens = 1000; break;
    case 18: $prompt = "Generate a sensational and funny article from a fictitious publication about"; $title = "An article About You"; $tokens = 1000; break;            
    default: $prompt = "Generate a complimentary poem about"; $title = "A poem for you!"; $tokens = 3000; break;            
}



tokens will need to be larger depending on how many values interests and descriptions have.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php

switch($prompt_type)
{
    case 0: $prompt = "Generate a complimentary poem about"; $title = "A poem for you!"; $tokens = 3000; break;
    case 1: $prompt = "Generate some positive life advice for"; $title = "Some life advice"; $tokens = 1000; break;
    case 2: $prompt = "Generate a sample horoscope for"; $title = "Your Zodiac advice"; $tokens = 3000; break;
    case 3: $prompt = "Generate a Chinese zodiac horoscope for"; $title = "Your Zodiac advice"; $tokens = 3000; break;
    case 4: $prompt = "Generate an encouraging two paragraph letter to self for"; $title = "Your self-affirmation"; $tokens = 2000; break;
    case 5: $prompt = "Generate an encouraging one paragraph letter to self for"; $title = "Your self-affirmation"; $tokens = 1000; break;
    case 6: $prompt = "Generate an encouraging three paragraph letter to self for"; $title = "Your self-affirmation"; $tokens = 3000; break;            
    case 7: $prompt = "Generate a funny and uplifting short story about"; $title = "The Story of You"; $tokens = 3000; break;
    case 8: $prompt = "Generate five inspirational quotes from famous people for"; $title = "Five Quotes to make your day"; $tokens = 2500; break;
    case 9: $prompt = "Generate five fictitious short reviews from fictitious publications about"; $title = "Your reviews from public media"; $tokens = 2500; break;
    case 10: $prompt = "Generate five fictitious one-sentence reviews from fictitious people from diverse races and their occupations about"; $title = "Public Opinion About You"; $tokens = 2500; break;
    case 11: $prompt = "Generate five fictitious one-sentence reviews from fictitious people from diverse races complimenting the personality of"; $title = "Public Opinion About You"; $tokens = 2500; break;                
    case 12: $prompt = "Generate five corny pickup lines from random people for"; $title = "Pickup Lines For You"; $tokens = 2500; break;
    case 13: $prompt = "Generate a character testimonial (from self) for"; $title = "Your testimonial!"; $tokens = 3000; break;
    case 14: $prompt = "Write a love letter (from self) to"; $title = "Some self-love"; $tokens = 3000; break;
    case 15: $prompt = "Create a short movie synopsis with famous actors about"; $title = "A movie was made about you!"; $tokens = 3000; break;            
    case 16: $prompt = "Create a movie role, with famous co-stars, for"; $title = "A movie role for you"; $tokens = 3000; break;            
    case 17: $prompt = "Write a welcoming letter from the President of a Fan Club centered around"; $title = "Welcome Address From " . $name . " Fan Club"; $tokens = 1000; break;
    case 18: $prompt = "Generate a sensational and funny article from a fictitious publication about"; $title = "An article About You"; $tokens = 1000; break;            
    default: $prompt = "Generate a complimentary poem about"; $title = "A poem for you!"; $tokens = 3000; break;            
}

$tokens += (100 * count($terms["interests"]));
$tokens += (100 * count($terms["descriptions"]));



And here, we declare final_prompt. We use the values of the strings prompt, about, interests and descriptions. Thus, a typical string would be "Generate a complimentary poem about a woman named 'Venus Chen', born on 02-11-1988. She is interested in positive thinking and flowers. She is described as happy and optimistic and emotional.". Add a line that displays final_prompt on screen.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$tokens += (100 * count($terms["interests"]));
$tokens += (100 * count($terms["descriptions"]));

$final_prompt = $prompt . " " . $about . ". " . $interests . $descriptions;

echo $final_prompt;


Test this!


This was the text returned. Because we first ran tt_get_terms(), it returned us the list of terms. Then you see that we have the prompt at the end.
{"items":[{"type":"DESCRIPTIONS","term":"acidic"},{"type":"DESCRIPTIONS","term":"cynical"},{"type":"DESCRIPTIONS","term":"jolly"},{"type":"DESCRIPTIONS","term":"patient"},{"type":"DESCRIPTIONS","term":"pragmatic"},{"type":"INTERESTS","term":"comedy"},{"type":"INTERESTS","term":"programming"},{"type":"INTERESTS","term":"soccer"},{"type":"INTERESTS","term":"superheroes"}],"hasMore":false,"limit":25,"offset":0,"count":9,"links":[{"rel":"self","href":"https://apex.oracle.com/pls/apex/teochewthunder/mailinglist/terms/teochewthunder@gmail.com"},{"rel":"describedby","href":"https://apex.oracle.com/pls/apex/teochewthunder/metadata-catalog/mailinglist/terms/item"},{"rel":"first","href":"https://apex.oracle.com/pls/apex/teochewthunder/mailinglist/terms/teochewthunder@gmail.com"}]}Generate a Chinese zodiac horoscope for man named 'Teochew Thunder' born on 01-01-1980. He is interested in programming and soccer. He is described as acidic and cynical and jolly and pragmatic.


Now that you have a prompt, it's time to send it to ChatGPT. For this, you will need an account and a key, Declare the variables key, org and url. key is the key provided for your ChatGPT developer account. org is the account id. url is the endpoint for ChatGPT. Of course your key isn't going to be "sk=xxx", but I'm not about to share my key in public. That would defeat the entire purpose of having a key, amirite?

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$final_prompt = $prompt . " " . $about . ". " . $interests . $descriptions;

echo $final_prompt;

$key = "sk-xxx";
$org = "org-FUOhDblZb1pxvaY6YylF54gl";
$url = "https://api.openai.com/v1/chat/completions";



Declare headers as an array with three strings. The first string is for authorization and includes key. The next one is to identify the account, and includes org. And the last line explicitly states that the content type is JSON.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$key = "sk-xxx";
$org = "org-FUOhDblZb1pxvaY6YylF54gl";
$url = "https://api.openai.com/v1/chat/completions";

$headers = [
    "Authorization: Bearer " . $key,
    "OpenAI-Organization: " . $org,
    "Content-Type: application/json"
];



Declare messages and obj as empty arrays. Declare keys role and content in obj. The value of role should be "user", while the value of content should be final_prompt. And then make obj the first element of messages.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php

$headers = [
    "Authorization: Bearer " . $key,
    "OpenAI-Organization: " . $org,
    "Content-Type: application/json"
];

$messages = [];
$obj = [];
$obj["role"] = "user";
$obj["content"] = $final_prompt;
$messages[] = $obj;



Then declare data as an empty array. Add keys model, messages and max_tokens. The value of model is the ChatGPT version. The value of messages is messages. The value of max_tokens is tokens, which we defined earlier based on the amount of information given.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$messages = [];
$obj = [];
$obj["role"] = "user";
$obj["content"] = $final_prompt;
$messages[] = $obj;

$data = [];
$data["model"] = "gpt-3.5-turbo";
$data["messages"] = $messages;
$data["max_tokens"] = $tokens;


Now begin cURL. It should be using the POST method. We send in data in JSON string format using the json_encode() function. We also set the headers to headers, and ensure that it returns a value.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php

$data = array();
$data["model"] = "gpt-3.5-turbo";
$data["messages"] = $messages;
$data["max_tokens"] = $tokens;

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);



Then we run curl_exec() passing in curl as an argument, and assign the returned value to result. If there's an error, display it. If there's no error, print result. Finally, clean up using curl_close().

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

$result = curl_exec($curl);
if (curl_errno($curl))
{
    echo "Error:" . curl_error($curl);
}
else
{
    echo print_r($result);
}

curl_close($curl);    



Now test this!


Try it again! Is the prompt and result different?


That's it for the test, but we still need to return a value. So use json_decode() on result.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
curl_close($curl);    
    
$result = json_decode($result);


Then declare sanitized_content. It should be the value of the content property of the message object of the first element in choices, of result. If that sounds convoluted, refer to the displayed printout on screen earlier and you'll see the hierarchy.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
curl_close($curl);    

$result = json_decode($result);
$sanitized_content = $result->choices[0]->message->content;


And then we use str_replace() to replace all the template strings in the output with reasonable data. There's no real exhaustive list of this.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
curl_close($curl);    

$result = json_decode($result);
$sanitized_content = $result->choices[0]->message->content;
$sanitized_content = str_replace("[Your Name]", $name, $sanitized_content);
$sanitized_content = str_replace("[President's Name]", $name, $sanitized_content);
$sanitized_content = str_replace("[Fan Club Name]", $name . " Fan Club", $sanitized_content);
$sanitized_content = str_replace("[Date]", date("j F Y"), $sanitized_content);
$sanitized_content = str_replace("[Insert Date]", date("j F Y"), $sanitized_content);
$sanitized_content = str_replace("[President's Logo]", "", $sanitized_content);
$sanitized_content = str_replace("[P.O Box]", "", $sanitized_content);
$sanitized_content = str_replace("[City, State, Zip Code]", "", $sanitized_content);
$sanitized_content = str_replace("[Email Address]", "", $sanitized_content);
$sanitized_content = str_replace("[Website]", "", $sanitized_content);
$sanitized_content = str_replace("[Social Media Handles]", "", $sanitized_content);    


And finally, return an array with two keys - title and body. The value of title is title, which we defined earlier. And the value of body is sanitized_content.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
$result = json_decode($result);
$sanitized_content = $result->choices[0]->message->content;
$sanitized_content = str_replace("[Your Name]", $name, $sanitized_content);
$sanitized_content = str_replace("[President's Name]", $name, $sanitized_content);
$sanitized_content = str_replace("[Fan Club Name]", $name . " Fan Club", $sanitized_content);
$sanitized_content = str_replace("[Date]", date("j F Y"), $sanitized_content);
$sanitized_content = str_replace("[Insert Date]", date("j F Y"), $sanitized_content);
$sanitized_content = str_replace("[President's Logo]", "", $sanitized_content);
$sanitized_content = str_replace("[P.O Box]", "", $sanitized_content);
$sanitized_content = str_replace("[City, State, Zip Code]", "", $sanitized_content);
$sanitized_content = str_replace("[Email Address]", "", $sanitized_content);
$sanitized_content = str_replace("[Website]", "", $sanitized_content);
$sanitized_content = str_replace("[Social Media Handles]", "", $sanitized_content);    

return ["title" => $title, "body" => $sanitized_content];


We have written a function to generate email content. Now we need to send the email, and schedule the task!

Next

Setting up the CRON job.