Showing posts with label cURL. Show all posts
Showing posts with label cURL. Show all posts

Monday, 24 March 2025

Web Tutorial: The Movie Poster Generator

Hi, readers. Do I have a fun one today!

We will be leveraging on the code I wrote for the Nike Meme Generator, to generate something else that may require an image upload - a Movie Poster Generator! This one is going to require some ChatGPT finangling as well.

Let's begin by setting up the uploads directory, and adding a default image - an AI-generated portrait of Angelina Jolie.
uploads/angelinajolie.jpg

Then, in the parent folder, we copy over the code from the Nike Meme Generator in index.php. We'll be removing quite a lot of lines. You'll see what we've retained is the file upload functions and the form. We've also made a few text changes as well. The div memeContainer has been replaced by posterContainer, along with styling.
<?php
$filecode = "angelinajolie";
$filetype = "jpg";
//$line1 = "Believe in something.";
//$line2 = "Even if it means sacrificing everything.";
//$slogan = "Just Do It.";

$strmessage="";

if (isset($_POST["btSubmit"]))
{
    //$line1 = $_POST["txtLine1"];
    //$line2 = $_POST["txtLine2"];
    //$slogan = $_POST["txtSlogan"];

    if (basename($_FILES["flUpload"]["name"]) != "")
    {
        $uploadsize = intval($_POST["hidUploadSize"]);
         $filetype = pathinfo($_FILES["flUpload"]["name"],PATHINFO_EXTENSION);
         $filetype = strtolower($filetype);

         if ($_FILES["flUpload"]["size"] > $uploadsize)
         {
             $strmessage = "Error was encountered while uploading file. File cannot exceed " . ($uploadsize/1000) . "kb";
        }
        else
        {
          if (!is_array(getimagesize($_FILES["flUpload"]["tmp_name"])))
          {
           $strmessage = "File type invalid";
          }
          else
          {
              $filecode=strtotime("now").rand();
        
              if (move_uploaded_file($_FILES["flUpload"]["tmp_name"], "uploads/" . $filecode . "." . $filetype))
              {
               $strmessage = "File uploaded.";
              }
              else
              {
                  $strmessage = "Error was encountered while uploading file.";
              }  
          }
         }
    }
    else
    {
         $strmessage="No file selected.";
    }
}
?>

<!DOCTYPE html>
<html>
    <head>
        <title>Movie Poster Generator</title>

        <style>
        #pnlMessage
        {
            width: 100%;
            height: 50px;
            color: #FF0000;
            outline: 0px solid #DDDDDD;
        }

        #formContainer
        {
            width: 400px;
            padding: 5px;
            margin: 5px;
            float: left;
            outline: 0px solid #DDDDDD;
        }

        /*
        #memeContainer
        {
            width: 500px;
            height: 500px;
            padding: 5px;
            margin: 5px;
            float: left;
            outline: 1px solid #DDDDDD;
            background: url(<?php echo "uploads/" . $filecode . "." . $filetype; ?>) center center no-repeat;
            background-size: cover;
            font-family: georgia;
            color: #FFFFFF;
            font-size: 25px;
            -webkit-filter: grayscale(100%);
            filter: grayscale(100%);
            text-align: center;
        }
         */

        #posterContainer
        {
            width: 800px;

            height: 500px;
            margin: 5px;
            float: left;
            text-align: center;
        }

        @media print
        {
               #formContainer, #pnlMessage
               {
                   display: none;
               }
    
               #posterContainer
               {
                    margin: 10% auto 0 auto;
                    float: none;
               }
        }
        </style>
    </head>

    <body>
        <div id="pnlMessage"><?php echo $strmessage; ?></div>

        <div id="formContainer">
            <form id="frmUpload" name="frmUpload" action="" method="POST" enctype="multipart/form-data">
                  <label for="flUpload">File</label>
                  <input type="file" name="flUpload" id="flUpload">
                  <input type="hidden" name="hidUploadSize" id="hidUploadSize" value="50000000">
                  <br /><br />
                  <!---
                 <label for="txtLine1">Line 1</label>
                  <input name="txtLine1" id="txtLine1" maxlength="50" value="<?php echo $line1; ?>" />
                  <br /><br />
                 <label for="txtLine2">Line 2</label>
                  <input name="txtLine2" id="txtLine2" maxlength="50" value="<?php echo $line2; ?>" />
                  <br /><br />
                  <label for="txtSlogan">Slogan</label>
                  <input name="txtSlogan" id="txtSlogan" maxlength="20" value="<?php echo $slogan; ?>" />
                  --->
                  <br /><br />
                  <input type="submit" name="btSubmit" id="btSubmit" value="Create your Movie Poster!">
            </form>
        </div>

        <div id="posterContainer">
             <!---
             <p style="margin-top:50%"><?php echo $line1;?><br /><?php echo $line2;?></p>
             <p style="margin-top:30%"><img src="nikelogo.png"> <?php echo $slogan;?></p>
             --->
        </div>
    </body>
</html>


Because I plan on using jQuery in here, let's include the library as well. And a script tag for custom JavaScript.
<head>
  <title>Movie Poster Generator</title>

  <style>
    #pnlMessage
    {
      width: 100%;
      height: 50px;
      color: #FF0000;
      outline: 0px solid #DDDDDD;
    }

    #formContainer
    {
      width: 400px;
      padding: 5px;
      margin: 5px;
      float: left;
      outline: 0px solid #DDDDDD;
    }

    #posterContainer
    {
      width: 800px;
      height: 500px;
      margin: 5px;
      float: left;
      text-align: center;
    }

    @media print
    {
        #formContainer, #pnlMessage
        {
          display: none;
        }

        #posterContainer
        {
          margin: 10% auto 0 auto;
          float: none;
        }
    }
  </style>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <script>

  </script>

</head>


We are going to begin the PHP with some variables other than strmessage, filecode and filetype. You'll see that I've specified some default values too.

space_from_top: controls the vertical position that the block of text occupies.
movie_title: self-explanatory.
movie_title_color: what color it appears in.
movie_title_size: what font size to use for the title.
movie_tagline: the tagline that appears beneath the title.
movie_tagline_color: what color it appears in.
movie_tagline_size: what font size to use for the tagline.
movie_starring: the movie star name(s). Since I'm using a picture of Angeline Jolie, that's her name I'm using.
movie_starring_color: what color it appears in.
movie_starring_size: what font size to use.
reviews: this is an array which will contain the stuff we get from ChatGPT.
reviews_color: what color they appear in.
reviews_bgcolor: what background color the reviews will use.

<?php
$filecode = "angelinajolie";
$filetype = "jpg";

$space_from_top = "20";

$movie_title = "Modern-day Maleficent";
$movie_title_color = "#FFFFFF";
$movie_title_size = "30";

$movie_tagline = "A re-imagining of the original tale of darkness";
$movie_tagline_color = "#FFFFFF";
$movie_tagline_size = "15";

$movie_starring = "Angelina Jolie";
$movie_starring_color = "#FFFFFF";
$movie_starring_size = "10";

$reviews = [];
$reviews_color = "#FFFFFF";
$reviews_bgcolor = "#000000";


$strmessage="";


Inside the posterContainer div, add three divs, left, middle and right. left and right are styled using the CSS class review.
<div id="posterContainer">
    <div id="left" class="review">

    </div>
    <div id="middle">

    </div>
    <div id="right" class="review">

    </div>

</div>


In the styles, both left and right have different text alignments. As they are both styled by the CSS class review, they have a certain width and height, they're floated left with a little padding, and color and background-color properties are determined by reviews_color and reviews_bgcolor respectively. The font type is less important and it's a personal choice.
#posterContainer
{
    width: 800px;
    height: 500px;
    margin: 5px;
    float: left;
     text-align: center;
}

#left
{
    text-align: right;
}

#right
{
    text-align: left;
}

.review
{
    width: 180px;
    height: 480px;
    float: left;
    padding: 10px;
    color: <?php echo $reviews_color;?>;
    background-color: <?php echo $reviews_bgcolor;?>;
    font-family: Georgia;
}

@media print
{
    #formContainer, #pnlMessage
    {
        display: none;
    }

    #posterContainer
    {
        margin: 10% auto 0 auto;
        float: none;
    }
}


Now we have middle. Like the reviews CSS class, it has a certain width and height, and is floated left. The background is determined by filecode, which currently points to angelinajolie.jpg in the uploads folder.
#posterContainer
{
    width: 800px;
    height: 500px;
    margin: 5px;
    float: left;
     text-align: center;
}

#middle
{
    width: 380px;
    height: 500px;
    background: url(<?php echo "uploads/" . $filecode . "." . $filetype; ?>) center center no-repeat;
    background-size: cover;
    float: left;
}

#left
{
    text-align: right;
}

#right
{
    text-align: left;
}


Here's a preview!

In the middle div, add a paragraph tag with the id title_and_tagline. In there, we have span tags with the ids movie_title and movie_tagline respectively.
<div id="middle">
    <p id="title_and_tagline">
    
    <span id="movie_title"></span>
        <br />
        <span id="movie_tagline"></span>
    </p>
</div>


Populate these span tags with the strings for movie_title and movie_tagline.
<div id="middle">
    <p id="title_and_tagline">
        <span id="movie_title"><?php echo $movie_title;?></span>
        <br />
        <span id="movie_tagline"><?php echo $movie_tagline;?></span>
    </p>
</div>


Now add another paragraph with id movie_starring.
<div id="middle">
    <p id="title_and_tagline">
        <span id="movie_title"><?php echo $movie_title;?></span>
        <br />
        <span id="movie_tagline"><?php echo $movie_tagline;?></span>
    </p>
    <p id="movie_starring">

    </p>
</div>


In there, have some PHP. It's possible that movie_starring is an empty string, in which case we want no text in that paragraph. But if it's not an empty string, we want it to say "starring" followed by movie_starring.
<div id="middle">
    <p id="title_and_tagline">
        <span id="movie_title"><?php echo $movie_title;?></span>
        <br />
        <span id="movie_tagline"><?php echo $movie_tagline;?></span>
    </p>
    <p id="movie_starring">
        <?php echo ($movie_starring == "" ? "" : "starring ");?>
        <?php echo $movie_starring;?>
    </p>
</div>


Let's do some more styling. We have styles for title_and_tagline, movie_title, movie_tagline and movie_starring. We're doing largely what we did for the review CSS class, with font-size and color property values determined by the PHP variables. For title_and_tagline, the margin-top property is determined by space_from_top.
.review
{
    width: 180px;
    height: 480px;
    float: left;
    padding: 10px;
    color: <?php echo $reviews_color;?>;
    background-color: <?php echo $reviews_bgcolor;?>;
    font-family: Georgia;
}

#title_and_tagline
{
    margin-top: <?php echo $space_from_top;?>px;
}

#movie_title
{
    color: <?php echo $movie_title_color;?>;
    font-size: <?php echo $movie_title_size;?>px;
    font-weight: bold;
    font-family: Impact, Verdana;
}

#movie_tagline
{
    color: <?php echo $movie_tagline_color;?>;
    font-size: <?php echo $movie_tagline_size;?>px;
    font-family: Arial, Helvetica, Verdana;
}

#movie_starring
{
    color: <?php echo $movie_starring_color;?>;
    font-size: <?php echo $movie_starring_size;?>px;
    font-family: Arial, Helvetica, Verdana;
}

@media print
{
    #formContainer, #pnlMessage
    {
        display: none;
    }

    #posterContainer
    {
        margin: 10% auto 0 auto;
        float: none;
    }
}


You see the text appears!

Now we are going to make randomly-generated reviews appear. Remember the empty array reviews? Basically, we're about to populate it. The code will run only if the form has been submitted, so put it inside the If block. You'll need your own OpenAI Developer Account, so replace "xxx" with your credentials. Then define headers with the appropriate properties for authentication.
if (isset($_POST["btSubmit"]))
{
    if (basename($_FILES["flUpload"]["name"]) != "")
    {
         $uploadsize = intval($_POST["hidUploadSize"]);
         $filetype = pathinfo($_FILES["flUpload"]["name"],PATHINFO_EXTENSION);
         $filetype = strtolower($filetype);

         if ($_FILES["flUpload"]["size"] > $uploadsize)
         {
             $strmessage = "Error was encountered while uploading file. File cannot exceed " . ($uploadsize/1000) . "kb";
         }
         else
         {
             if (!is_array(getimagesize($_FILES["flUpload"]["tmp_name"])))
             {
                  $strmessage = "File type invalid";
             }
             else
             {
                  $filecode=strtotime("now").rand();
        
                  if (move_uploaded_file($_FILES["flUpload"]["tmp_name"], "uploads/" . $filecode . "." . $filetype))
                  {
                       $strmessage = "File uploaded.";
                  }
                  else
                  {
                       $strmessage = "Error was encountered while uploading file.";
                  }  
              }
         }
    }
    else
    {
         $strmessage="No file selected.";
    }

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

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


Here, we're defining the prompt. We use movie_title (and movie_starring, if it's not an empty string), and generate an array, FakeReviews, of 10 objects. Each object will have a one-sentence comment and a string to determine the "source" of the comment.
$key = "xxx";
$org = "org-xxx";
$url = 'https://api.openai.com/v1/chat/completions';  

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

$messages = [];
$obj = [];
$obj["role"] = "user";
$obj["content"] = "Give me a JSON object with one property. The property should be named 'FakeReviews', and should be an array of ten objects. Each object should have the property 'review', which is a random fictional complimentary about the movie '" . $movie_title . "'" . ($movie_starring == "" ? "" : " or celebrity '" . $movie_starring . "'") .  " (range between three to ten words) sentence in a string, and the property 'critic' which contains the fictional publication for that quote.";
$messages[] = $obj;


The rest of the code we've gone through before in The Random Christmas Card and The Self-affirmations Wordpress Plugin
$messages = [];
$obj = [];
$obj["role"] = "user";
$obj["content"] = "Give me a JSON object with one property. The property should be named 'FakeReviews', and should be an array of ten objects. Each object should have the property 'review', which is a random fictional complimentary about the movie '" . $movie_title . "'" . ($movie_starring == "" ? "" : " or celebrity '" . $movie_starring . "'") .  " (range between three to ten words) sentence in a string, and the property 'critic' which contains the fictional publication for that quote.";
$messages[] = $obj;

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


We use cURL to send the data to the API endpoint. The returned response is in result, and we handle any errors before closing out with curl_close().
$data = [];
$data["model"] = "gpt-3.5-turbo";
$data["messages"] = $messages;
$data["max_tokens"] = 1000;

$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);
}

curl_close($curl);


Then we'll use json_decode() on result, and then extract FakeReviews from it.
$result = curl_exec($curl);
if (curl_errno($curl)) 
{
    echo 'Error:' . curl_error($curl);
}

curl_close($curl);

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

$reviews = $content->FakeReviews;


In the left div, use a PHP If block to check if reviews has 10 elements, just to filter out any nasty surprises. Then use a For loop to go through the first 5 elements.
<div id="left" class="review">
<?php
if (count($reviews) == 10) 

{
    for ($i = 0; $i < 5; $i++)
    {

    }
}
?>

</div>


For each element, you want a span element, and a nicely formatted review property. Be sure to use htmlspecialchars() on it.
<div id="left" class="review">
<?php
if (count($reviews) == 10) 
{
    for ($i = 0; $i < 5; $i++)
    {
?>
    <span>
    <b>&ldquo;<?php echo htmlspecialchars($reviews[$i]->review); ?>&rdquo;</b>
    </span>
<?php

    }
}
?>
</div>


We want the font size to be inversely proportional to the length of the string. Thus, if the review was "Go watch it!", it would be in a significantly larger font than "This movie will bring you through a roller-coaster ride of emotions!".
<div id="left" class="review">
<?php
if (count($reviews) == 10) 
{
    for ($i = 0; $i < 5; $i++)
    {
?>
    <span style="font-size:<?php echo (1.5 - (strlen($reviews[$i]->review) / 10)); ?>em">
    <b>&ldquo;<?php echo htmlspecialchars($reviews[$i]->review); ?>&rdquo;</b>
    </span>
<?php
    }
}
?>
</div>


After that, we have a small tag and the critic property in italics.
<div id="left" class="review">
<?php
if (count($reviews) == 10) 
{
    for ($i = 0; $i < 5; $i++)
    {
?>
    <span style="font-size:<?php echo (1.5 - (strlen($reviews[$i]->review) / 10)); ?>em">
    <b>&ldquo;<?php echo htmlspecialchars($reviews[$i]->review); ?>&rdquo;</b>
    </span>
    <br />
    <small>
    <i><?php echo $reviews[$i]->critic; ?></i>
    </small>
    <br />
    <br />

<?php
    }
}
?>
</div>


We then want there to be between 3 to 5 stars. So we use the HTML symbol 3 times...
<small>
<i><?php echo $reviews[$i]->critic; ?></i>
&nbsp;&#9733;&#9733;&#9733;
</small>


Then use a For loop and the rand() function to potentially put down a maximum of 2 more stars.
<small>
<i><?php echo $reviews[$i]->critic; ?></i>
&nbsp;&#9733;&#9733;&#9733;
<?php 
for ($j = 0; $j <= 1; $j++)
{
    if (rand(1, 2) == 1) echo "&#9733;";
}
?>

</small>


You have to click the "Create your Movie Poster" button to test this.

Repeat for the other side. This time, set the For loop to iterate through elements 5 to 10 of reviews.
<div id="right" class="review">
<?php
if (count($reviews) == 10) 

{
    for ($i = 5; $i < 10; $i++)
    {
?>
    <span style="font-size:<?php echo (2 - (strlen($reviews[$i]->review) / 10)); ?>em"><b>&ldquo;<?php echo htmlspecialchars($reviews[$i]->review); ?>&rdquo;</b>
    </span>
    <br />
    <small>
    <i><?php echo $reviews[$i]->critic; ?></i>
    &nbsp;&#9733;&#9733;&#9733;
    <?php 
    for ($j = 0; $j <= 1; $j++)
    {
        if (rand(1, 2) == 1) echo "&#9733;";
    }
    ?>
    </small>
    <br />
    <br />
<?php
    }
}
?>

</div>


Looks like the other side is done!


We're not quite done yet...

We want to make a dashboard to manipulate customizable variables. The good news is, we've done that already before in The Easter Egg Generator, all the way back in 2016. Can't really reuse any code, but the principles are the same.

Let's define some fieldsets, with legends.
<form id="frmUpload" name="frmUpload" action="" method="POST" enctype="multipart/form-data">
    <label for="flUpload">File</label>
    <input type="file" name="flUpload" id="flUpload">
    <input type="hidden" name="hidUploadSize" id="hidUploadSize" value="50000000">
    <br /><br />
    <fieldset>
        <legend>Movie Title</legend>            

    </fieldset>

    <fieldset>
        <legend>Movie Tagline</legend>

    </fieldset>

    <fieldset>
        <legend>Starring</legend>

    </fieldset>           

    <fieldset>
        <legend>Reviews</legend>

    </fieldset>
    <br /><br />

    <input type="submit" name="btSubmit" id="btSubmit" value="Create your Movie Poster!">
</form>


Taking shape!


We'll want controls that the user can use to change the PHP variables. For numeric values, we'll use a range input. For colors, we'll use color inputs. And for text values, we will just have vanilla text inputs. For the latter, I've included maxlength attributes out of sheer habit. Take a note of name and id attributes - those will be relevant real soon.
<input type="hidden" name="hidUploadSize" id="hidUploadSize" value="50000000">
<br /><br />
<label for="txtSpace_from_top">Space From Top</label>
<input type="range" min="10" max="400" name="txtSpace_from_top" id="txtSpace_from_top" value="" />

<br /><br />
<fieldset>
        <legend>Movie Title</legend>
        <label for="txtMovie_title">Text</label>
        <input name="txtMovie_title" id="txtMovie_title" maxlength="20" value="" />
        <br />
        <label for="txtMovie_title_color">Color</label>
        <input type="color" name="txtMovie_title_color" id="txtMovie_title_color" value="" />
        <br />
        <label for="txtMovie_title_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_title_size" id="txtMovie_title_size" value="" />
            
</fieldset>

<fieldset>
        <legend>Movie Tagline</legend>
        <label for="txtMovie_tagline">Text</label>
        <input name="txtMovie_tagline" id="txtMovie_tagline" maxlength="50" value="" />
        <br />
        <label for="txtMovie_tagline_color">Color</label>
        <input type="color" name="txtMovie_tagline_color" id="txtMovie_tagline_color" value="" />
        <br />
        <label for="txtMovie_tagline_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_tagline_size" id="txtMovie_tagline_size" value="" />

</fieldset>

<fieldset>
        <legend>Starring</legend>
        <label for="txtMovie_starring">Text</label>
        <input name="txtMovie_starring" id="txtMovie_starring" maxlength="100" value="" />
        <br />
        <label for="txtMovie_starring_color">Color</label>
        <input type="color" name="txtMovie_starring_color" id="txtMovie_starring_color" value="" />
        <br />
        <label for="txtMovie_starring_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_starring_size" id="txtMovie_starring_size" value="" />

</fieldset>           

<fieldset>
        <legend>Reviews</legend>
        <label for="txtReviews_color">Color</label>
        <input type="color" name="txtReviews_color" id="txtReviews_color" value="" />
        <br />
        <label for="txtReviews_bgcolor">Background Color</label>
        <input type="color" name="txtReviews_bgcolor" id="txtReviews_bgcolor" value="" />

</fieldset>


And here are the inputs. Just a bit messy, so let's clean stuff up.


In the styles, style labels to have a fixed width. I've also styled font. While we're at it, let's have the submit button also given a fixed width, a bit of spacing at the top, and float it right.
#formContainer
{
        width: 400px;
        padding: 5px;
        margin: 5px;
        float: left;
        outline: 0px solid #DDDDDD;
}

label
{
        display: inline-block;
        font-family: arial;
        font-size: 12px;
        width: 10em;
}


#btSubmit
{
        width: 15em;
        margin-top: 1em;
        float: right;
}

#posterContainer
{
        width: 800px;
        height: 500px;
        margin: 5px;
        float: left;
        text-align: center;
}


And then let's populate the values of all these controls with the PHP variables.
<label for="txtSpace_from_top">Space From Top</label>
<input type="range" min="10" max="400" name="txtSpace_from_top" id="txtSpace_from_top" value="<?php echo $space_from_top; ?>" />
<br /><br />
<fieldset>
        <legend>Movie Title</legend>
        <label for="txtMovie_title">Text</label>
        <input name="txtMovie_title" id="txtMovie_title" maxlength="20" value="<?php echo $movie_title; ?>" />
        <br />
        <label for="txtMovie_title_color">Color</label>
        <input type="color" name="txtMovie_title_color" id="txtMovie_title_color" value="<?php echo $movie_title_color; ?>" />
        <br />
        <label for="txtMovie_title_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_title_size" id="txtMovie_title_size" value="<?php echo $movie_title_size; ?>" />             
</fieldset>

<fieldset>
        <legend>Movie Tagline</legend>
        <label for="txtMovie_tagline">Text</label>
        <input name="txtMovie_tagline" id="txtMovie_tagline" maxlength="50" value="<?php echo $movie_tagline; ?>" />
        <br />
        <label for="txtMovie_tagline_color">Color</label>
        <input type="color" name="txtMovie_tagline_color" id="txtMovie_tagline_color" value="<?php echo $movie_tagline_color; ?>" />
        <br />
        <label for="txtMovie_tagline_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_tagline_size" id="txtMovie_tagline_size" value="<?php echo $movie_tagline_size; ?>" />
</fieldset>

<fieldset>
        <legend>Starring</legend>
        <label for="txtMovie_starring">Text</label>
        <input name="txtMovie_starring" id="txtMovie_starring" maxlength="100" value="<?php echo $movie_starring; ?>" />
        <br />
        <label for="txtMovie_starring_color">Color</label>
        <input type="color" name="txtMovie_starring_color" id="txtMovie_starring_color" value="<?php echo $movie_tagline_color; ?>" />
        <br />
        <label for="txtMovie_starring_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_starring_size" id="txtMovie_starring_size" value="<?php echo $movie_starring_size; ?>" />
</fieldset>           

<fieldset>
        <legend>Reviews</legend>
        <label for="txtReviews_color">Color</label>
        <input type="color" name="txtReviews_color" id="txtReviews_color" value="<?php echo $reviews_color; ?>" />
        <br />
        <label for="txtReviews_bgcolor">Background Color</label>
<input type="color" name="txtReviews_bgcolor" id="txtReviews_bgcolor" value="<?php echo $reviews_bgcolor; ?>" />
</fieldset>


There you go.


Now, here's some more PHP code. This ensures that if you change any of the variables in the form and then submit the form, the changes take effect.
if (isset($_POST["btSubmit"]))
{
    if (basename($_FILES["flUpload"]["name"]) != "")
    {
         $uploadsize = intval($_POST["hidUploadSize"]);
         $filetype = pathinfo($_FILES["flUpload"]["name"],PATHINFO_EXTENSION);
         $filetype = strtolower($filetype);

         if ($_FILES["flUpload"]["size"] > $uploadsize)
         {
             $strmessage = "Error was encountered while uploading file. File cannot exceed " . ($uploadsize/1000) . "kb";
         }
         else
         {
             if (!is_array(getimagesize($_FILES["flUpload"]["tmp_name"])))
             {
                  $strmessage = "File type invalid";
             }
             else
             {
                  $filecode=strtotime("now").rand();
        
                  if (move_uploaded_file($_FILES["flUpload"]["tmp_name"], "uploads/" . $filecode . "." . $filetype))
                  {
                      $strmessage = "File uploaded.";
                  }
                  else
                  {
                      $strmessage = "Error was encountered while uploading file.";
                  }  
             }
         }
    }
    else
    {
         $strmessage="No file selected.";
    }

    $space_from_top = $_POST["txtSpace_from_top"];

    $movie_title = $_POST["txtMovie_title"];
    $movie_title_color = $_POST["txtMovie_title_color"];
    $movie_title_size = $_POST["txtMovie_title_size"];

    $movie_tagline = $_POST["txtMovie_tagline"];
    $movie_tagline_color = $_POST["txtMovie_tagline_color"];
    $movie_tagline_size = $_POST["txtMovie_tagline_size"];

    $movie_starring = $_POST["txtMovie_starring"];
    $movie_starring_color = $_POST["txtMovie_starring_color"];
    $movie_starring_size = $_POST["txtMovie_starring_size"];

    $reviews = [];
    $reviews_color = $_POST["txtReviews_color"];
    $reviews_bgcolor = $_POST["txtReviews_bgcolor"];


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

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

    $messages = [];
    $obj = [];
    $obj["role"] = "user";
    $obj["content"] = "Give me a JSON object with one property. The property should be named 'FakeReviews', and should be an array of ten objects. Each object should have the property 'review', which is a random fictional complimentary about the movie '" . $movie_title . "'" . ($movie_starring == "" ? "" : " or celebrity '" . $movie_starring . "'") .  " (range between three to ten words) sentence in a string, and the property 'critic' which contains the fictional publication for that quote.";
    $messages[] = $obj;

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

    $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);
    }

    curl_close($curl);

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

    $reviews = $content->FakeReviews;
}


See what I mean?


Now, it would be nice to have whatever changes you make, be reflected in "real-time" instead of having to submit the form. Well, image changes have to involve submitting the form, but not the rest! So, in the fields, add the oninput attribute and set it to call the function useVariables().
<label for="txtSpace_from_top">Space From Top</label>
<input type="range" min="10" max="400" name="txtSpace_from_top" id="txtSpace_from_top" value="<?php echo $space_from_top; ?>" oninput="useVariables()" />
<br /><br />
<fieldset>
        <legend>Movie Title</legend>
        <label for="txtMovie_title">Text</label>
        <input name="txtMovie_title" id="txtMovie_title" maxlength="20" value="<?php echo $movie_title; ?>" oninput="useVariables()" />
        <br />
        <label for="txtMovie_title_color">Color</label>
        <input type="color" name="txtMovie_title_color" id="txtMovie_title_color" value="<?php echo $movie_title_color; ?>" oninput="useVariables()" />
        <br />
        <label for="txtMovie_title_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_title_size" id="txtMovie_title_size" value="<?php echo $movie_title_size; ?>" oninput="useVariables()" />             
</fieldset>

<fieldset>
        <legend>Movie Tagline</legend>
        <label for="txtMovie_tagline">Text</label>
        <input name="txtMovie_tagline" id="txtMovie_tagline" maxlength="50" value="<?php echo $movie_tagline; ?>" oninput="useVariables()" />
        <br />
        <label for="txtMovie_tagline_color">Color</label>
        <input type="color" name="txtMovie_tagline_color" id="txtMovie_tagline_color" value="<?php echo $movie_tagline_color; ?>" oninput="useVariables()" />
        <br />
        <label for="txtMovie_tagline_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_tagline_size" id="txtMovie_tagline_size" value="<?php echo $movie_tagline_size; ?>" oninput="useVariables()" />
</fieldset>

<fieldset>
        <legend>Starring</legend>
        <label for="txtMovie_starring">Text</label>
        <input name="txtMovie_starring" id="txtMovie_starring" maxlength="100" value="<?php echo $movie_starring; ?>" oninput="useVariables()" />
        <br />
        <label for="txtMovie_starring_color">Color</label>
        <input type="color" name="txtMovie_starring_color" id="txtMovie_starring_color" value="<?php echo $movie_tagline_color; ?>" oninput="useVariables()" />
        <br />
        <label for="txtMovie_starring_size">Size</label>
        <input type="range" min="10" max="50" name="txtMovie_starring_size" id="txtMovie_starring_size" value="<?php echo $movie_starring_size; ?>" oninput="useVariables()" />
</fieldset>           

<fieldset>
        <legend>Reviews</legend>
        <label for="txtReviews_color">Color</label>
        <input type="color" name="txtReviews_color" id="txtReviews_color" value="<?php echo $reviews_color; ?>" oninput="useVariables()" />
        <br />
        <label for="txtReviews_bgcolor">Background Color</label>
<input type="color" name="txtReviews_bgcolor" id="txtReviews_bgcolor" value="<?php echo $reviews_bgcolor; ?>" oninput="useVariables()" />
</fieldset>


And then we create the useVariables() function in the JavaScript.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
function useVariables()
{

}

</script>


We will be making changes to these elements...
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
function useVariables()
{
    $("#title_and_tagline")

    $(".review")

    $("#movie_title")

    $("#movie_tagline")

    $("#movie_starring")

}
</script>


All of these elements will have changes made to the style attribute. As you can see, the changes are mostly about font size and color. In the case of title_and_tagline, the margin-top property is changed. In the case of divs styled using the review CSS class, it's color and background color that's changed.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
function useVariables()
{
    $("#title_and_tagline")
    .attr("style", "margin-top:" + $("#txtSpace_from_top").val() + "px");

    $(".review")
    .attr("style", "color:" + $("#txtReviews_color").val() + ";background-color: " +       $("#txtReviews_bgcolor").val());

    $("#movie_title")
    .attr("style", "color:" + $("#txtMovie_title_color").val() + ";font-size: " + $("#txtMovie_title_size").val() + "px");

    $("#movie_tagline")
    .attr("style", "color:" + $("#txtMovie_tagline_color").val() + ";font-size: " + $("#txtMovie_tagline_size").val() + "px");

    $("#movie_starring")
    .attr("style", "color:" + $("#txtMovie_starring_color").val() + ";font-size: " + $("#txtMovie_starring_size").val() + "px");
}
</script>


For movie_title, movie_tagline and movie_starring, we use the html() method to change the text. Note that for movie_starring, the same rules apply as they did with the PHP script - if the contents of the txtMovie_starring text box is an empty string, this placeholder will be empty as well; otherwise, prepend with "starring".
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
function useVariables()
{
    $("#title_and_tagline")
    .attr("style", "margin-top:" + $("#txtSpace_from_top").val() + "px");

    $(".review")
    .attr("style", "color:" + $("#txtReviews_color").val() + ";background-color: " + $("#txtReviews_bgcolor").val());

    $("#movie_title")
    .attr("style", "color:" + $("#txtMovie_title_color").val() + ";font-size: " + $("#txtMovie_title_size").val() + "px")
    .html($("#txtMovie_title").val());

    $("#movie_tagline")
    .attr("style", "color:" + $("#txtMovie_tagline_color").val() + ";font-size: " + $("#txtMovie_tagline_size").val() + "px")
    .html($("#txtMovie_tagline").val());

    $("#movie_starring")
    .attr("style", "color:" + $("#txtMovie_starring_color").val() + ";font-size: " + $("#txtMovie_starring_size").val() + "px")
    .html(($("#txtMovie_starring").val() == "" ? "" : "starring " + $("#txtMovie_starring").val()));
}
</script>


You can't really tell from here, but the screen is reflecting the changes I'm making right now.


Before I forget...

You can even print out your poster. The media queries I specified in the CSS ensure that the dashboard is not visible in print view.

Now, wasn't that fun?!

Generative Artificial Intelligence really injects a little bit of random craziness into tiny projects like these. Love it!

Getting the picture? Hur hur,
T___T

Sunday, 24 December 2023

Web Tutorial: The Random Christmas Card (Part 2/2)

We have created three different themes for three different pictures. Naturally, we are also going to want the text to match.

For that, we've already prepared those prompts in the themes array. Right now, index is still set to 2, so let's try sending the prompt to ChatGPT. For that, you will need a ChatGPT Developer Account and the key that comes with it.

Create three variables - key, org and url. key is your API key (It's represented here as "sk-xxx". That's not my actual API key but I'm not about to disclose it!). org is the identifier of your account. url is the ChatGPT endpoint.

index.php
$index = rand(0, 2);
$index = 2;
$content = $themes[$index]["prompt"];

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

Create array headers. In there, we have three elements which are all strings. key and org are used in the headers array, and it's used to identify the user.

index.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"
];

So here, we create the object needed to pass into the endpoint call. We use the appropriate prompt and set the maximum number of tokens at 1000.

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

$messages = [];
$obj = [];
$obj["role"] = "user";
$obj["content"] = $themes[$index]["prompt"];
$messages[] = $obj;

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

Here, we use data and headers in cURL. Bear in mind we have to do a json_encode() on data in order to turn it into a string.

index.php
$data = [];
$data["model"] = "gpt-3.5-turbo";
$data["messages"] = $messages;
$data["max_tokens"] = 1000;

$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);

Run curl_exec() with curl as an argument, and assign the returned result to result. Use curl_errno() to see if there's an error, and print it if so. At the end of it, sew things up with curl_close().

index.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);
}


curl_close($curl);

Turn result from a string back to an object using json_decode(). There may be breaks in the content. Use nl2br() to convert them to HTML breaks, and assign the result to the content string.

index.php
curl_close($curl);

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

Here's some more sanitization you may want to implement.

index.php
$result = json_decode($result);
$content = nl2br($result->choices[0]->message->content);
$content = str_replace("[Recipient]", $name, $content);
$content = str_replace("[Recipient's Name]", $name, $content);
$content = str_replace("[Your Name]", "Teochew Thunder", $content);

Now let's see what we're getting!




If you refresh, it should be different!




Change the value and test again.

index.php
$index = 1;

Nice poem!




Last test!

index.php

$index = 0;

And you see the content is different now. It's snow-based.




Now comment this line off. We don't need it anymore.

index.php
//$index = 0;

And now it should be totally random.




Try it with a value in the name parameter.



That's it...

Enjoy sending your Christmas card to your friends. This is just a beginning; feel free to add more themes if you wish.

Merry Christmas, Human Being!
T___T

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