XF 2.1 Trying to fix an error in my add-on

Matt C.

Well-known member
In my Twitter Widget add-on, I keep getting the following error every once in a while. It doesn't seem to affect anything because everything seems to work, but I'd like to fix it if possible.

This is the error I'm getting:
Code:
AH\TwitterWidget\Vendor\Moment\MomentException: Template public:forum_list error: Locale string does not exist for key: relativeTime > ss src/addons/AH/TwitterWidget/Vendor/Moment/MomentLocale.php:85
Generated by: Unknown account Nov 27, 2018 at 11:49 AM

Stack trace

#0 src/addons/AH/TwitterWidget/Vendor/Moment/MomentLocale.php(103): AH\TwitterWidget\Vendor\Moment\MomentLocale::getLocaleString(Array)
#1 src/addons/AH/TwitterWidget/Vendor/Moment/MomentFromVo.php(294): AH\TwitterWidget\Vendor\Moment\MomentLocale::renderLocaleString(Array, Array)
#2 src/addons/AH/TwitterWidget/Api/Twitter.php(44): AH\TwitterWidget\Vendor\Moment\MomentFromVo->getRelative()
#3 src/addons/AH/TwitterWidget/Widget/TwitterWidget.php(11): AH\TwitterWidget\Api\Twitter->getTweets()
#4 src/XF/Template/Templater.php(1526): AH\TwitterWidget\Widget\TwitterWidget->render()
#5 internal_data/code_cache/templates/l1/s26/public/forum_list.php(206): XF\Template\Templater->widgetPosition('forum_list_side...', Array)
#6 src/XF/Template/Templater.php(1250): XF\Template\Templater->{closure}(Object(ThemeHouse\Reactions\XF\Template\Templater), Array)
#7 src/XF/Template/Template.php(24): XF\Template\Templater->renderTemplate('forum_list', Array)
#8 src/XF/Mvc/Renderer/Html.php(48): XF\Template\Template->render()
#9 src/XF/Mvc/Dispatcher.php(332): XF\Mvc\Renderer\Html->renderView('XF:Forum\\Listin...', 'public:forum_li...', Array)
#10 src/XF/Mvc/Dispatcher.php(303): XF\Mvc\Dispatcher->renderView(Object(XF\Mvc\Renderer\Html), Object(XF\Mvc\Reply\View))
#11 src/XF/Mvc/Dispatcher.php(44): XF\Mvc\Dispatcher->render(Object(XF\Mvc\Reply\View), 'html')
#12 src/XF/App.php(1931): XF\Mvc\Dispatcher->run()
#13 src/XF.php(329): XF\App->run()
#14 index.php(13): XF::runApp('XF\\Pub\\App')
#15 {main}

Request state

array(4) {
  ["url"] => string(1) "/"
  ["referrer"] => bool(false)
  ["_GET"] => array(0) {
  }
  ["_POST"] => array(0) {
  }
}

I'm using the TwitterOAuth library to connect to twitter and get tweets from a list, and I'm using the Moment.php library to convert the tweet's timestamp to relative time, so instead of "Thu Apr 06 15:24:15 +0000 2017" (example) it shows "1 day ago".

Here is the entire code for connecting to Twitter:
PHP:
<?php

namespace AH\TwitterWidget\Api;

require(dirname(__DIR__) . '/Vendor/TwitterOAuth/autoload.php');

use XF\Mvc\Entity\AbstractCollection;
use XF\Mvc\Entity\Repository;
use Abraham\TwitterOAuth\TwitterOAuth;

class Twitter extends Repository
{
    public function getTweets()
    {
        $consumerKey = \XF::options()->ahTW_consumerKey;
        $consumerSecret = \XF::options()->ahTW_consumerSecret;
        $accessToken = \XF::options()->ahTW_accessToken;
        $accessTokenSecret = \XF::options()->ahTW_accessTokenSecret;

        $account = \XF::options()->ahTW_account;
        $listSlug = \XF::options()->ahTW_listSlug;
        $count = \XF::options()->ahTW_count;
       
        $connection = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);
        $tweets = $connection->get("lists/statuses", array('count' => $count, 'slug' => $listSlug, 'owner_screen_name' => $account));
   
        if ($connection->getLastHttpCode() == 200)
        {
            foreach ($tweets as $tweet)
            {
                // Make normal links clickable
                $tweet->text = preg_replace('/$timestamp = strtotime($tweet->created_at);(\b(www\.|http\:\/\/)\S+\b)/', "<a target='_blank' href='$1'>$1</a>", $tweet->text);
                // Make twitter links clickable
                $tweet->text = preg_replace("~[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]~", "<a target='_blank' href=\"\\0\">\\0</a>", $tweet->text);
                // Make hash tags clickable
                $tweet->text = preg_replace('/(?<!\S)#([0-9a-zA-Z]+)/', '<a target=\'_blank\' href="https://twitter.com/hashtag/$1">#$1</a>', $tweet->text);
                // Make users clickable
                $tweet->text = preg_replace('/\@(\w+)/', "<a target='_blank' href='http://twitter.com/$1'>@$1</a>", $tweet->text);
                   
               
                // Format the time to relative
                $timestamp = strtotime($tweet->created_at);
                $m = new \AH\TwitterWidget\Vendor\Moment\Moment($timestamp);
                   $tweet->created_at = $m->fromNow()->getRelative();
            }
        } else {
            // error
        }
   
        return [
            'tweets' => $tweets,
            'connection' => $connection
        ];
    }
}

This is the piece of code that converts the time to relative:
Code:
$timestamp = strtotime($tweet->created_at);
$m = new \AH\TwitterWidget\Vendor\Moment\Moment($timestamp);
$tweet->created_at = $m->fromNow()->getRelative();

Since the error says "Locale string does not exist for key: relativeTime > ss src/addons/AH/TwitterWidget/Vendor/Moment/MomentLocale.php:85" here is the code from that file on line 85.

PHP:
/**
* @param array $keys
*
* @return array|string|\Closure
* @throws MomentException
*/
public static function getLocaleString(array $keys)
{
    $string = self::$localeContent;

    foreach ($keys as $key)
    {
        if (isset($string[$key]) === false)
        {
            throw new MomentException('Locale string does not exist for key: ' . join(' > ', $keys));
        }

        $string = $string[$key];
    }

    return $string;
}

I'm sorry if I'm not explaining the situation enough, I hope I've given enough information. Thank you to anyone who can help.
 
This doesn't address your problem directly, but why not convert the timestamp into a Unix timestamp and display it with <xf:date time="{$timestamp}" /> instead? XF will display it in a relative format for you, in a way which is more consistent with other timestamps throughout the platform. Plus it does this on the client-side, so the relative timestamp will update without the user having to refresh the page.
 
This doesn't address your problem directly, but why not convert the timestamp into a Unix timestamp and display it with <xf:date time="{$timestamp}" /> instead? XF will display it in a relative format for you, in a way which is more consistent with other timestamps throughout the platform. Plus it does this on the client-side, so the relative timestamp will update without the user having to refresh the page.

I never thought about that. Thank you!

Edit: Are there any other parameters I can use on that template tag? Because it will come out as "Today at 3:02am" instead of "X hours ago."
 
Last edited:
Nothing special beyond the usual parameters (class, etc). XF only supports displaying them in minute form (X minutes ago) for one hour, before it becomes 'Today at X'. Personally I think displaying them in a different format would be inconsistent to end-users. And it may be especially confusing if the parsing is done on the server, as that means these timestamps would remain static while all the native timestamps on the page are updating client-side. If supporting this format is a must, I would consider using Moment.js (from which the PHP library was inspired) to handle it client-side instead.
 
Nothing special beyond the usual parameters (class, etc). XF only supports displaying them in minute form (X minutes ago) for one hour, before it becomes 'Today at X'. Personally I think displaying them in a different format would be inconsistent to end-users. And it may be especially confusing if the parsing is done on the server, as that means these timestamps would remain static while all the native timestamps on the page are updating client-side. If supporting this format is a must, I would consider using Moment.js (from which the PHP library was inspired) to handle it client-side instead.

Thanks for the help Jeremy. I updated my Twitter Widget add-on using the default XF date system.
 
Top Bottom