Adding BB Code Media Sites Using oEmbed

Adding BB Code Media Sites Using oEmbed

I realized there was an error in my cache code. When the cache expired, it would fail to update the cache properly and produce an error instead...

These are my fixed codes:
Code:
<?php

class EWRcustom_BbCode_Embed
{
    public static function embedTwitter($mediaKey, array $site)
    {
        $tweetModel = XenForo_Model::create('EWRcustom_Model_Tweets');
       
        if (!$tweet = $tweetModel->getTweetByID($mediaKey, 28))
        {
            $client = new Zend_Http_Client('https://api.twitter.com/1/statuses/oembed.json');
            $client->setParameterGet(array(
                'id' => $mediaKey,
                'maxwidth' => '550',
                'hide_media' => 'false',
                'hide_thread' => 'false',
                'omit_script' => 'true',
                'align' => 'center',
            ));
               
            try
            {
                $feed = $client->request()->getBody();
            }
            catch (Exception $e)
            {
                return false;
            }
               
            return $tweetModel->cacheTweet($mediaKey, $feed);
        }
        else
        {
            return $tweet['tweet_html'];
        }
    }
}

Code:
<?php

class EWRcustom_Model_Tweets extends XenForo_Model
{
    public function getTweetByID($tweetID, $limit = 0)
    {
        $limit = $limit ? XenForo_Application::$time - (86400 * $limit) : 0;
       
        if (!$tweet = $this->_getDb()->fetchRow("
            SELECT *
                FROM EWRcustom_tweets
            WHERE tweet_id = ?
                AND tweet_date > ?
        ", array($tweetID, $limit)))
        {
            return false;
        }

        return $tweet;
    }
   
    public function cacheTweet($tweetID, $json)
    {
        $json = json_decode($json, true);
        $json['html'] = !empty($json['html']) ? $json['html'] : '';
       
        $dw = XenForo_DataWriter::create('EWRcustom_DataWriter_Tweets');
       
        if ($tweet = $this->getTweetByID($tweetID))
        {
            $dw->setExistingData($tweet);
        }
       
        $dw->bulkSet(array(
            'tweet_id' => $tweetID,
            'tweet_html' => $json['html'],
            'tweet_data' => $json,
        ));
        $dw->save();
       
        return $json['html'];
    }
}
So the guide already shows you exactly how to use oEmbed to embed tweets on your forum. However, with the use of oEmbed, there are a few problems.

The first problem is rate-limiting. Twitter will automatically rate-limit queries to their API, so that websites and users don't bang on their servers too much from outside sources. The second is the inevitability of the Fail Whale. When Twitter goes down, the Zend_Http request will stall as it tries to fetch the oEmbed information; and this can cause slow your website down.

Thankfully, caching tweets solves all these issues. Since we're caching tweets, we need to create a table in our database. I made one called "EWRcustom_tweets"
Code:
CREATE TABLE IF NOT EXISTS `EWRcustom_tweets` (
    `tweet_id` bigint(20) unsigned NOT NULL,
    `tweet_date` int(10) unsigned NOT NULL,
    `tweet_html` text NOT NULL,
    `tweet_data` blob NOT NULL,
    PRIMARY KEY (`tweet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
As you can see, we store the ID of the tweet, the date we are caching the tweet, the html of the tweet, and the raw data of the tweet.

Then we write a datawriter for saving to this database:
Code:
<?php

class EWRcustom_DataWriter_Tweets extends XenForo_DataWriter
{
    protected $_existingDataErrorPhrase = 'requested_page_not_found';

    protected function _getFields()
    {
        return array(
            'EWRcustom_tweets' => array(
                'tweet_id'        => array('type' => self::TYPE_STRING, 'required' => true),
                'tweet_date'    => array('type' => self::TYPE_UINT, 'required' => true),
                'tweet_html'    => array('type' => self::TYPE_STRING, 'required' => false),
                'tweet_data'    => array('type' => self::TYPE_JSON, 'required' => false),
            )
        );
    }

    protected function _getExistingData($data)
    {
        if (!$tweetID = $this->_getExistingPrimaryKey($data, 'tweet_id'))
        {
            return false;
        }

        return array('EWRcustom_tweets' => $this->getModelFromCache('EWRcustom_Model_Tweets')->getTweetByID($tweetID));
    }

    protected function _getUpdateCondition($tableName)
    {
        return 'tweet_id = ' . $this->_db->quote($this->getExisting('tweet_id'));
    }

    protected function _preSave()
    {
        $this->set('tweet_date', XenForo_Application::$time);
    }
}
This is a very basic datawriter; it handles existing data, update conditions and automatically updates the date/time on a save. One of the things you may notice in this datawriter is that I am submitting the "tweet_id" as a TYPE_STRING instead of a TYPE_UINT. This is because the length of the tweet_ids as required by twitter is actually outside of the range of integers and thus must be casted into the database as a string.

Then of course, we need the model to handle the fetching of cached tweets, and of course writing tweets to the cache:
Code:
<?php

class EWRcustom_Model_Tweets extends XenForo_Model
{
    public function getTweetByID($tweetID)
    {
        $limit = XenForo_Application::$time - (86400 * 7);
       
        if (!$tweet = $this->_getDb()->fetchRow("
            SELECT *
                FROM EWRcustom_tweets
            WHERE tweet_id = ?
                AND tweet_date > ?
        ", array($tweetID, $limit)))
        {
            return false;
        }

        return $tweet;
    }
   
    public function cacheTweet($tweetID, $json)
    {
        $dw = XenForo_DataWriter::create('EWRcustom_DataWriter_Tweets');
       
        if ($tweet = $this->getTweetByID($tweetID))
        {
            $dw->setExistingData($tweet);
        }
       
        $dw->bulkSet(array(
            'tweet_id' => $tweetID,
            'tweet_html' => !empty($json['html']) ? $json['html'] : '',
            'tweet_data' => $json,
        ));
        $dw->save();
       
        return true;
    }
}
Again, very simple code. You'll notice the getTweetByID function has a query limit on tweet_date at (86400 * 7). This is simply to make it so that once a cache is over 7 days old, the system returns a false and pretends the cache doesn't exist. Thus expiring the cache item after 7 days and refreshing it.

So now we have the database table, the datawriter and the model... we just need to implement all these items directly into the embed function which we already have...
Code:
<?php

class EWRcustom_BbCode_Embed
{
    public static function embedTwitter($mediaKey, array $site)
    {
        $tweetModel = XenForo_Model::create('EWRcustom_Model_Tweets');
       
        if (!$tweet = $tweetModel->getTweetByID($mediaKey))
        {
            $client = new Zend_Http_Client('https://api.twitter.com/1/statuses/oembed.json');
            $client->setParameterGet(array(
                'id' => $mediaKey,
                'maxwidth' => '500',
                'hide_media' => 'false',
                'hide_thread' => 'false',
                'omit_script' => 'true',
                'align' => 'none',
            ));
               
            try
            {
                $feed = $client->request()->getBody();
            }
            catch (Exception $e)
            {
                return false;
            }
               
            $json = json_decode($feed, true);
            $tweet = $tweetModel->cacheTweet($mediaKey, $json);
            return $json['html'];
        }
        else
        {
            return $tweet['tweet_html'];
        }
    }
}
The first the function does is check to see if a relevant tweet exists. If it does; it returns the cached tweet_html and calls it a day. Done!

However, if it doesn't find a tweet, it will then run though the operations to get the tweet information from the Twitter API. If it fails to connect to the API, it will exit out with a false; which will return the URL back to the reader.

If it does connect to the API, it will store the information in the cache and return the HTML to the reader.
  • Like
Reactions: EQnoble
Top Bottom