And one notice removed for twitter
[friendica-addons.git/.git] / twitter / twitter.php
index 9c2804b..8b71664 100644 (file)
  *     we do not need "Twitter as login". When you've registered the app you get the
  *     OAuth Consumer key and secret pair for your application/site.
  *
- *     Add this key pair to your global .htconfig.php or use the admin panel.
+ *     Add this key pair to your global config/addon.ini.php or use the admin panel.
  *
- *     $a->config['twitter']['consumerkey'] = 'your consumer_key here';
- *     $a->config['twitter']['consumersecret'] = 'your consumer_secret here';
+ *     [twitter]
+ *     consumerkey = your consumer_key here
+ *     consumersecret = your consumer_secret here
  *
- *     To activate the addon itself add it to the $a->config['system']['addon']
+ *     To activate the addon itself add it to the [system] addon
  *     setting. After this, your user can configure their Twitter account settings
  *     from "Settings -> Addon Settings".
  *
  */
 
 use Abraham\TwitterOAuth\TwitterOAuth;
+use Abraham\TwitterOAuth\TwitterOAuthException;
 use Friendica\App;
 use Friendica\Content\OEmbed;
-use Friendica\Content\Text\BBCode;
 use Friendica\Content\Text\Plaintext;
 use Friendica\Core\Addon;
 use Friendica\Core\Config;
 use Friendica\Core\L10n;
 use Friendica\Core\PConfig;
+use Friendica\Core\Protocol;
 use Friendica\Core\Worker;
+use Friendica\Database\DBA;
+use Friendica\Model\Contact;
+use Friendica\Model\Conversation;
 use Friendica\Model\GContact;
 use Friendica\Model\Group;
 use Friendica\Model\Item;
-use Friendica\Model\Photo;
+use Friendica\Model\ItemContent;
 use Friendica\Model\Queue;
 use Friendica\Model\User;
 use Friendica\Object\Image;
+use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Network;
-use Friendica\Util\Temporal;
 
 require_once 'boot.php';
 require_once 'include/dba.php';
@@ -92,47 +97,54 @@ define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes
 function twitter_install()
 {
        //  we need some hooks, for the configuration and for sending tweets
-       Addon::registerHook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
-       Addon::registerHook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
-       Addon::registerHook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
-       Addon::registerHook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
-       Addon::registerHook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
-       Addon::registerHook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
-       Addon::registerHook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
-       Addon::registerHook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
-       Addon::registerHook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
-       Addon::registerHook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
-       Addon::registerHook('check_item_notification', 'addon/twitter/twitter.php', 'twitter_check_item_notification');
+       Addon::registerHook('load_config'            , __FILE__, 'twitter_load_config');
+       Addon::registerHook('connector_settings'     , __FILE__, 'twitter_settings');
+       Addon::registerHook('connector_settings_post', __FILE__, 'twitter_settings_post');
+       Addon::registerHook('post_local'             , __FILE__, 'twitter_post_local');
+       Addon::registerHook('notifier_normal'        , __FILE__, 'twitter_post_hook');
+       Addon::registerHook('jot_networks'           , __FILE__, 'twitter_jot_nets');
+       Addon::registerHook('cron'                   , __FILE__, 'twitter_cron');
+       Addon::registerHook('queue_predeliver'       , __FILE__, 'twitter_queue_hook');
+       Addon::registerHook('follow'                 , __FILE__, 'twitter_follow');
+       Addon::registerHook('expire'                 , __FILE__, 'twitter_expire');
+       Addon::registerHook('prepare_body'           , __FILE__, 'twitter_prepare_body');
+       Addon::registerHook('check_item_notification', __FILE__, 'twitter_check_item_notification');
        logger("installed twitter");
 }
 
 function twitter_uninstall()
 {
-       Addon::unregisterHook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
-       Addon::unregisterHook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
-       Addon::unregisterHook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
-       Addon::unregisterHook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
-       Addon::unregisterHook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
-       Addon::unregisterHook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
-       Addon::unregisterHook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
-       Addon::unregisterHook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
-       Addon::unregisterHook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
-       Addon::unregisterHook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
-       Addon::unregisterHook('check_item_notification', 'addon/twitter/twitter.php', 'twitter_check_item_notification');
+       Addon::unregisterHook('load_config'            , __FILE__, 'twitter_load_config');
+       Addon::unregisterHook('connector_settings'     , __FILE__, 'twitter_settings');
+       Addon::unregisterHook('connector_settings_post', __FILE__, 'twitter_settings_post');
+       Addon::unregisterHook('post_local'             , __FILE__, 'twitter_post_local');
+       Addon::unregisterHook('notifier_normal'        , __FILE__, 'twitter_post_hook');
+       Addon::unregisterHook('jot_networks'           , __FILE__, 'twitter_jot_nets');
+       Addon::unregisterHook('cron'                   , __FILE__, 'twitter_cron');
+       Addon::unregisterHook('queue_predeliver'       , __FILE__, 'twitter_queue_hook');
+       Addon::unregisterHook('follow'                 , __FILE__, 'twitter_follow');
+       Addon::unregisterHook('expire'                 , __FILE__, 'twitter_expire');
+       Addon::unregisterHook('prepare_body'           , __FILE__, 'twitter_prepare_body');
+       Addon::unregisterHook('check_item_notification', __FILE__, 'twitter_check_item_notification');
 
        // old setting - remove only
-       Addon::unregisterHook('post_local_end', 'addon/twitter/twitter.php', 'twitter_post_hook');
-       Addon::unregisterHook('addon_settings', 'addon/twitter/twitter.php', 'twitter_settings');
-       Addon::unregisterHook('addon_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
+       Addon::unregisterHook('post_local_end'     , __FILE__, 'twitter_post_hook');
+       Addon::unregisterHook('addon_settings'     , __FILE__, 'twitter_settings');
+       Addon::unregisterHook('addon_settings_post', __FILE__, 'twitter_settings_post');
 }
 
-function twitter_check_item_notification(App $a, &$notification_data)
+function twitter_load_config(App $a)
+{
+       $a->loadConfigFile(__DIR__ . '/config/twitter.ini.php');
+}
+
+function twitter_check_item_notification(App $a, array &$notification_data)
 {
        $own_id = PConfig::get($notification_data["uid"], 'twitter', 'own_id');
 
        $own_user = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
                        intval($notification_data["uid"]),
-                       dbesc("twitter::".$own_id)
+                       DBA::escape("twitter::".$own_id)
        );
 
        if ($own_user) {
@@ -140,7 +152,7 @@ function twitter_check_item_notification(App $a, &$notification_data)
        }
 }
 
-function twitter_follow(App $a, &$contact)
+function twitter_follow(App $a, array &$contact)
 {
        logger("twitter_follow: Check if contact is twitter contact. " . $contact["url"], LOGGER_DEBUG);
 
@@ -173,8 +185,8 @@ function twitter_follow(App $a, &$contact)
        $r = q("SELECT name,nick,url,addr,batch,notify,poll,request,confirm,poco,photo,priority,network,alias,pubkey
                FROM `contact` WHERE `uid` = %d AND `nick` = '%s'",
                                intval($uid),
-                               dbesc($nickname));
-       if (count($r)) {
+                               DBA::escape($nickname));
+       if (DBA::isResult($r)) {
                $contact["contact"] = $r[0];
        }
 }
@@ -194,17 +206,17 @@ function twitter_jot_nets(App $a, &$b)
        }
 }
 
-function twitter_settings_post(App $a, $post)
+function twitter_settings_post(App $a)
 {
        if (!local_user()) {
                return;
        }
        // don't check twitter settings if twitter submit button is not clicked
-       if (!x($_POST, 'twitter-submit')) {
+       if (empty($_POST['twitter-disconnect']) && empty($_POST['twitter-submit'])) {
                return;
        }
 
-       if (isset($_POST['twitter-disconnect'])) {
+       if (!empty($_POST['twitter-disconnect'])) {
                /*               * *
                 * if the twitter-disconnect checkbox is set, clear the OAuth key/secret pair
                 * from the user configuration
@@ -229,12 +241,22 @@ function twitter_settings_post(App $a, $post)
                        //  the token and secret for which the PIN was generated were hidden in the settings
                        //  form as token and token2, we need a new connection to Twitter using these token
                        //  and secret to request a Access Token with the PIN
-                       $connection = new TwitterOAuth($ckey, $csecret, $_POST['twitter-token'], $_POST['twitter-token2']);
-                       $token = $connection->oauth("oauth/access_token", ["oauth_verifier" => $_POST['twitter-pin']]);
-                       //  ok, now that we have the Access Token, save them in the user config
-                       PConfig::set(local_user(), 'twitter', 'oauthtoken', $token['oauth_token']);
-                       PConfig::set(local_user(), 'twitter', 'oauthsecret', $token['oauth_token_secret']);
-                       PConfig::set(local_user(), 'twitter', 'post', 1);
+                       try {
+                               if (empty($_POST['twitter-pin'])) {
+                                       throw new Exception(L10n::t('You submitted an empty PIN, please Sign In with Twitter again to get a new one.'));
+                               }
+
+                               $connection = new TwitterOAuth($ckey, $csecret, $_POST['twitter-token'], $_POST['twitter-token2']);
+                               $token = $connection->oauth("oauth/access_token", ["oauth_verifier" => $_POST['twitter-pin']]);
+                               //  ok, now that we have the Access Token, save them in the user config
+                               PConfig::set(local_user(), 'twitter', 'oauthtoken', $token['oauth_token']);
+                               PConfig::set(local_user(), 'twitter', 'oauthsecret', $token['oauth_token_secret']);
+                               PConfig::set(local_user(), 'twitter', 'post', 1);
+                       } catch(Exception $e) {
+                               info($e->getMessage());
+                       } catch(TwitterOAuthException $e) {
+                               info($e->getMessage());
+                       }
                        //  reload the Addon Settings page, if we don't do it see Bug #42
                        goaway('settings/connectors');
                } else {
@@ -288,84 +310,84 @@ function twitter_settings(App $a, &$s)
        $s .= '</span>';
 
        if ((!$ckey) && (!$csecret)) {
-               /*               * *
-                * no global consumer keys
+               /* no global consumer keys
                 * display warning and skip personal config
                 */
                $s .= '<p>' . L10n::t('No consumer key pair for Twitter found. Please contact your site administrator.') . '</p>';
        } else {
-               /*               * *
-                * ok we have a consumer key pair now look into the OAuth stuff
-                */
+               // ok we have a consumer key pair now look into the OAuth stuff
                if ((!$otoken) && (!$osecret)) {
-                       /*                       * *
-                        * the user has not yet connected the account to twitter...
+                       /* the user has not yet connected the account to twitter...
                         * get a temporary OAuth key/secret pair and display a button with
                         * which the user can request a PIN to connect the account to a
                         * account at Twitter.
                         */
                        $connection = new TwitterOAuth($ckey, $csecret);
-                       $result = $connection->oauth('oauth/request_token', ['oauth_callback' => 'oob']);
-                       /*                       * *
-                        *  make some nice form
-                        */
-                       $s .= '<p>' . L10n::t('At this Friendica instance the Twitter addon was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter.') . '</p>';
-                       $s .= '<a href="' . $connection->url('oauth/authorize', ['oauth_token' => $result->oauth_token]) . '" target="_twitter"><img src="addon/twitter/lighter.png" alt="' . L10n::t('Log in with Twitter') . '"></a>';
-                       $s .= '<div id="twitter-pin-wrapper">';
-                       $s .= '<label id="twitter-pin-label" for="twitter-pin">' . L10n::t('Copy the PIN from Twitter here') . '</label>';
-                       $s .= '<input id="twitter-pin" type="text" name="twitter-pin" />';
-                       $s .= '<input id="twitter-token" type="hidden" name="twitter-token" value="' . $result->oauth_token . '" />';
-                       $s .= '<input id="twitter-token2" type="hidden" name="twitter-token2" value="' . $result->oauth_token_secret . '" />';
-                       $s .= '</div><div class="clear"></div>';
-                       $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
+                       try {
+                               $result = $connection->oauth('oauth/request_token', ['oauth_callback' => 'oob']);
+                               $s .= '<p>' . L10n::t('At this Friendica instance the Twitter addon was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter.') . '</p>';
+                               $s .= '<a href="' . $connection->url('oauth/authorize', ['oauth_token' => $result['oauth_token']]) . '" target="_twitter"><img src="addon/twitter/lighter.png" alt="' . L10n::t('Log in with Twitter') . '"></a>';
+                               $s .= '<div id="twitter-pin-wrapper">';
+                               $s .= '<label id="twitter-pin-label" for="twitter-pin">' . L10n::t('Copy the PIN from Twitter here') . '</label>';
+                               $s .= '<input id="twitter-pin" type="text" name="twitter-pin" />';
+                               $s .= '<input id="twitter-token" type="hidden" name="twitter-token" value="' . $result['oauth_token'] . '" />';
+                               $s .= '<input id="twitter-token2" type="hidden" name="twitter-token2" value="' . $result['oauth_token_secret'] . '" />';
+                               $s .= '</div><div class="clear"></div>';
+                               $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
+                       } catch (TwitterOAuthException $e) {
+                               $s .= '<p>' . L10n::t('An error occured: ') . $e->getMessage() . '</p>';
+                       }
                } else {
                        /*                       * *
                         *  we have an OAuth key / secret pair for the user
                         *  so let's give a chance to disable the postings to Twitter
                         */
                        $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
-                       $details = $connection->get('account/verify_credentials');
-
-                       $field_checkbox = get_markup_template('field_checkbox.tpl');
-
-                       $s .= '<div id="twitter-info" >
-                               <p>' . L10n::t('Currently connected to: ') . '<a href="https://twitter.com/' . $details->screen_name . '" target="_twitter">' . $details->screen_name . '</a>
-                                       <button type="submit" name="twitter-disconnect" value="1">' . L10n::t('Disconnect') . '</button>
-                               </p>
-                               <p id="twitter-info-block">
-                                       <a href="https://twitter.com/' . $details->screen_name . '" target="_twitter"><img id="twitter-avatar" src="' . $details->profile_image_url . '" /></a>
-                                       <em>' . $details->description . '</em>
-                               </p>
-                       </div>';
-                       $s .= '<div class="clear"></div>';
-
-                       $s .= replace_macros($field_checkbox, [
-                               '$field' => ['twitter-enable', L10n::t('Allow posting to Twitter'), $enabled, L10n::t('If enabled all your <strong>public</strong> postings can be posted to the associated Twitter account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.')]
-                       ]);
-                       if ($a->user['hidewall']) {
-                               $s .= '<p>' . L10n::t('<strong>Note</strong>: Due to your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') . '</p>';
+                       try {
+                               $details = $connection->get('account/verify_credentials');
+
+                               $field_checkbox = get_markup_template('field_checkbox.tpl');
+
+                               $s .= '<div id="twitter-info" >
+                                       <p>' . L10n::t('Currently connected to: ') . '<a href="https://twitter.com/' . $details->screen_name . '" target="_twitter">' . $details->screen_name . '</a>
+                                               <button type="submit" name="twitter-disconnect" value="1">' . L10n::t('Disconnect') . '</button>
+                                       </p>
+                                       <p id="twitter-info-block">
+                                               <a href="https://twitter.com/' . $details->screen_name . '" target="_twitter"><img id="twitter-avatar" src="' . $details->profile_image_url . '" /></a>
+                                               <em>' . $details->description . '</em>
+                                       </p>
+                               </div>';
+                               $s .= '<div class="clear"></div>';
+
+                               $s .= replace_macros($field_checkbox, [
+                                       '$field' => ['twitter-enable', L10n::t('Allow posting to Twitter'), $enabled, L10n::t('If enabled all your <strong>public</strong> postings can be posted to the associated Twitter account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.')]
+                               ]);
+                               if ($a->user['hidewall']) {
+                                       $s .= '<p>' . L10n::t('<strong>Note</strong>: Due to your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') . '</p>';
+                               }
+                               $s .= replace_macros($field_checkbox, [
+                                       '$field' => ['twitter-default', L10n::t('Send public postings to Twitter by default'), $defenabled, '']
+                               ]);
+                               $s .= replace_macros($field_checkbox, [
+                                       '$field' => ['twitter-mirror', L10n::t('Mirror all posts from twitter that are no replies'), $mirrorenabled, '']
+                               ]);
+                               $s .= replace_macros($field_checkbox, [
+                                       '$field' => ['twitter-import', L10n::t('Import the remote timeline'), $importenabled, '']
+                               ]);
+                               $s .= replace_macros($field_checkbox, [
+                                       '$field' => ['twitter-create_user', L10n::t('Automatically create contacts'), $create_userenabled, L10n::t('This will automatically create a contact in Friendica as soon as you receive a message from an existing contact via the Twitter network. If you do not enable this, you need to manually add those Twitter contacts in Friendica from whom you would like to see posts here. However if enabled, you cannot merely remove a twitter contact from the Friendica contact list, as it will recreate this contact when they post again.')]
+                               ]);
+                               $s .= '<div class="clear"></div>';
+                               $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
+                       } catch (TwitterOAuthException $e) {
+                               $s .= '<p>' . L10n::t('An error occured: ') . $e->getMessage() . '</p>';
                        }
-                       $s .= replace_macros($field_checkbox, [
-                               '$field' => ['twitter-default', L10n::t('Send public postings to Twitter by default'), $defenabled, '']
-                       ]);
-                       $s .= replace_macros($field_checkbox, [
-                               '$field' => ['twitter-mirror', L10n::t('Mirror all posts from twitter that are no replies'), $mirrorenabled, '']
-                       ]);
-                       $s .= replace_macros($field_checkbox, [
-                               '$field' => ['twitter-import', L10n::t('Import the remote timeline'), $importenabled, '']
-                       ]);
-                       $s .= replace_macros($field_checkbox, [
-                               '$field' => ['twitter-create_user', L10n::t('Automatically create contacts'), $create_userenabled, '']
-                       ]);
-
-                       $s .= '<div class="clear"></div>';
-                       $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
                }
        }
        $s .= '</div><div class="clear"></div>';
 }
 
-function twitter_post_local(App $a, &$b)
+function twitter_post_local(App $a, array &$b)
 {
        if ($b['edit']) {
                return;
@@ -410,6 +432,7 @@ function twitter_action(App $a, $uid, $pid, $action)
        switch ($action) {
                case "delete":
                        // To-Do: $result = $connection->post('statuses/destroy', $post);
+                       $result = [];
                        break;
                case "like":
                        $result = $connection->post('favorites/create', $post);
@@ -417,11 +440,14 @@ function twitter_action(App $a, $uid, $pid, $action)
                case "unlike":
                        $result = $connection->post('favorites/destroy', $post);
                        break;
+               default:
+                       logger('Unhandled action ' . $action, LOGGER_DEBUG);
+                       $result = [];
        }
        logger("twitter_action '" . $action . "' send, result: " . print_r($result, true), LOGGER_DEBUG);
 }
 
-function twitter_post_hook(App $a, &$b)
+function twitter_post_hook(App $a, array &$b)
 {
        // Post to Twitter
        if (!PConfig::get($b["uid"], 'twitter', 'import')
@@ -441,16 +467,13 @@ function twitter_post_hook(App $a, &$b)
                        return;
                }
 
-               $r = q("SELECT * FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
-                       dbesc($b["thr-parent"]),
-                       intval($b["uid"]));
-
-               if (!count($r)) {
+               $condition = ['uri' => $b["thr-parent"], 'uid' => $b["uid"]];
+               $orig_post = Item::selectFirst([], $condition);
+               if (!DBA::isResult($orig_post)) {
                        logger("twitter_post_hook: no parent found " . $b["thr-parent"]);
                        return;
                } else {
                        $iscomment = true;
-                       $orig_post = $r[0];
                }
 
 
@@ -473,7 +496,7 @@ function twitter_post_hook(App $a, &$b)
 
                // Dont't post if the post doesn't belong to us.
                // This is a check for forum postings
-               $self = dba::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
+               $self = DBA::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
                if ($b['contact-id'] != $self['id']) {
                        return;
                }
@@ -499,7 +522,7 @@ function twitter_post_hook(App $a, &$b)
        }
 
        // if post comes from twitter don't send it back
-       if ($b['extid'] == NETWORK_TWITTER) {
+       if ($b['extid'] == Protocol::TWITTER) {
                return;
        }
 
@@ -526,8 +549,11 @@ function twitter_post_hook(App $a, &$b)
 
                $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
 
+               // Set the timeout for upload to 30 seconds
+               $connection->setTimeouts(10, 30);
+
                $max_char = 280;
-               $msgarr = BBCode::toPlaintext($b, $max_char, true, 8);
+               $msgarr = ItemContent::getPlaintextPost($b, $max_char, true, 8);
                $msg = $msgarr["text"];
 
                if (($msg == "") && isset($msgarr["title"])) {
@@ -538,94 +564,76 @@ function twitter_post_hook(App $a, &$b)
 
                if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
                        $msg .= "\n" . $msgarr["url"];
+                       $url_added = true;
+               } else {
+                       $url_added = false;
                }
 
                if (isset($msgarr["image"]) && ($msgarr["type"] != "video")) {
                        $image = $msgarr["image"];
                }
 
-               // and now tweet it :-)
-               if (strlen($msg) && ($image != "")) {
-                       $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
-                       $media = $connection->upload('media/upload', ['media' => $image]);
+               if (empty($msg)) {
+                       return;
+               }
 
-                       $post = ['status' => $msg, 'media_ids' => $media->media_id_string];
+               // and now tweet it :-)
+               $post = [];
 
-                       if ($iscomment) {
-                               $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
-                       }
+               if (!empty($image)) {
+                       try {
+                               $img_str = Network::fetchUrl($image);
 
-                       $result = $connection->post('statuses/update', $post);
+                               $tempfile = tempnam(get_temppath(), 'cache');
+                               file_put_contents($tempfile, $img_str);
 
-                       logger('twitter_post_with_media send, result: ' . print_r($result, true), LOGGER_DEBUG);
+                               $media = $connection->upload('media/upload', ['media' => $tempfile]);
 
-                       if ($result->source) {
-                               Config::set("twitter", "application_name", strip_tags($result->source));
-                       }
+                               unlink($tempfile);
 
-                       if ($result->errors || $result->error) {
-                               logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
+                               $post['media_ids'] = $media->media_id_string;
+                       } catch (Exception $e) {
+                               logger('Exception when trying to send to Twitter: ' . $e->getMessage());
 
                                // Workaround: Remove the picture link so that the post can be reposted without it
-                               $msg .= " " . $image;
+                               // When there is another url already added, a second url would be superfluous.
+                               if (!$url_added) {
+                                       $msg .= "\n" . $image;
+                               }
+
                                $image = "";
-                       } elseif ($iscomment) {
-                               logger('twitter_post: Update extid ' . $result->id_str . " for post id " . $b['id']);
-                               q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
-                                       dbesc("twitter::" . $result->id_str),
-                                       dbesc($result->text),
-                                       intval($b['id'])
-                               );
                        }
                }
 
-               if (strlen($msg) && ($image == "")) {
-// -----------------
-                       $max_char = 280;
-                       $msgarr = BBCode::toPlaintext($b, $max_char, true, 8);
-                       $msg = $msgarr["text"];
+               $post['status'] = $msg;
 
-                       if (($msg == "") && isset($msgarr["title"])) {
-                               $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
-                       }
+               if ($iscomment) {
+                       $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
+               }
 
-                       if (isset($msgarr["url"])) {
-                               $msg .= "\n" . $msgarr["url"];
-                       }
-// -----------------
-                       $url = 'statuses/update';
-                       $post = ['status' => $msg, 'weighted_character_count' => 'true'];
+               $url = 'statuses/update';
+               $result = $connection->post($url, $post);
+               logger('twitter_post send, result: ' . print_r($result, true), LOGGER_DEBUG);
 
-                       if ($iscomment) {
-                               $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
-                       }
+               if (!empty($result->source)) {
+                       Config::set("twitter", "application_name", strip_tags($result->source));
+               }
 
-                       $result = $connection->post($url, $post);
-                       logger('twitter_post send, result: ' . print_r($result, true), LOGGER_DEBUG);
+               if (!empty($result->errors)) {
+                       logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
 
-                       if ($result->source) {
-                               Config::set("twitter", "application_name", strip_tags($result->source));
+                       $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid']));
+                       if (DBA::isResult($r)) {
+                               $a->contact = $r[0]["id"];
                        }
 
-                       if ($result->errors) {
-                               logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
-
-                               $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid']));
-                               if (count($r)) {
-                                       $a->contact = $r[0]["id"];
-                               }
+                       $s = serialize(['url' => $url, 'item' => $b['id'], 'post' => $post]);
 
-                               $s = serialize(['url' => $url, 'item' => $b['id'], 'post' => $post]);
-
-                               Queue::add($a->contact, NETWORK_TWITTER, $s);
-                               notice(L10n::t('Twitter post failed. Queued for retry.') . EOL);
-                       } elseif ($iscomment) {
-                               logger('twitter_post: Update extid ' . $result->id_str . " for post id " . $b['id']);
-                               q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d",
-                                       dbesc("twitter::" . $result->id_str),
-                                       intval($b['id'])
-                               );
-                       }
+                       Queue::add($a->contact, Protocol::TWITTER, $s);
+                       notice(L10n::t('Twitter post failed. Queued for retry.') . EOL);
+               } elseif ($iscomment) {
+                       logger('twitter_post: Update extid ' . $result->id_str . " for post id " . $b['id']);
+                       Item::update(['extid' => "twitter::" . $result->id_str], ['id' => $b['id']]);
                }
        }
 }
@@ -651,7 +659,7 @@ function twitter_addon_admin(App $a, &$o)
        ]);
 }
 
-function twitter_cron(App $a, $b)
+function twitter_cron(App $a)
 {
        $last = Config::get('twitter', 'last_poll');
 
@@ -670,7 +678,7 @@ function twitter_cron(App $a, $b)
        logger('twitter: cron_start');
 
        $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'mirror_posts' AND `v` = '1'");
-       if (count($r)) {
+       if (DBA::isResult($r)) {
                foreach ($r as $rr) {
                        logger('twitter: fetching for user ' . $rr['uid']);
                        Worker::add(PRIORITY_MEDIUM, "addon/twitter/twitter_sync.php", 1, (int) $rr['uid']);
@@ -682,14 +690,14 @@ function twitter_cron(App $a, $b)
                $abandon_days = 0;
        }
 
-       $abandon_limit = date(Temporal::MYSQL, time() - $abandon_days * 86400);
+       $abandon_limit = date(DateTimeFormat::MYSQL, time() - $abandon_days * 86400);
 
        $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1'");
-       if (count($r)) {
+       if (DBA::isResult($r)) {
                foreach ($r as $rr) {
                        if ($abandon_days != 0) {
                                $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
-                               if (!count($user)) {
+                               if (!DBA::isResult($user)) {
                                        logger('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
                                        continue;
                                }
@@ -719,7 +727,7 @@ function twitter_cron(App $a, $b)
        Config::set('twitter', 'last_poll', time());
 }
 
-function twitter_expire(App $a, $b)
+function twitter_expire(App $a)
 {
        $days = Config::get('twitter', 'expire');
 
@@ -727,34 +735,30 @@ function twitter_expire(App $a, $b)
                return;
        }
 
-       if (method_exists('dba', 'delete')) {
-               $r = dba::select('item', ['id'], ['deleted' => true, 'network' => NETWORK_TWITTER]);
-               while ($row = dba::fetch($r)) {
-                       dba::delete('item', ['id' => $row['id']]);
-               }
-               dba::close($r);
-       } else {
-               $r = q("DELETE FROM `item` WHERE `deleted` AND `network` = '%s'", dbesc(NETWORK_TWITTER));
+       $r = Item::select(['id'], ['deleted' => true, 'network' => Protocol::TWITTER]);
+       while ($row = DBA::fetch($r)) {
+               DBA::delete('item', ['id' => $row['id']]);
        }
+       DBA::close($r);
 
        require_once "include/items.php";
 
        logger('twitter_expire: expire_start');
 
        $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
-       if (count($r)) {
+       if (DBA::isResult($r)) {
                foreach ($r as $rr) {
                        logger('twitter_expire: user ' . $rr['uid']);
-                       Item::expire($rr['uid'], $days, NETWORK_TWITTER, true);
+                       Item::expire($rr['uid'], $days, Protocol::TWITTER, true);
                }
        }
 
        logger('twitter_expire: expire_end');
 }
 
-function twitter_prepare_body(App $a, &$b)
+function twitter_prepare_body(App $a, array &$b)
 {
-       if ($b["item"]["network"] != NETWORK_TWITTER) {
+       if ($b["item"]["network"] != Protocol::TWITTER) {
                return;
        }
 
@@ -763,13 +767,9 @@ function twitter_prepare_body(App $a, &$b)
                $item = $b["item"];
                $item["plink"] = $a->get_baseurl() . "/display/" . $a->user["nickname"] . "/" . $item["parent"];
 
-               $r = q("SELECT `author-link` FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
-                       dbesc($item["thr-parent"]),
-                       intval(local_user()));
-
-               if (count($r)) {
-                       $orig_post = $r[0];
-
+               $condition = ['uri' => $item["thr-parent"], 'uid' => local_user()];
+               $orig_post = Item::selectFirst(['author-link'], $condition);
+               if (DBA::isResult($orig_post)) {
                        $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
                        $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
                        $nicknameplain = "@" . $nicknameplain;
@@ -779,7 +779,7 @@ function twitter_prepare_body(App $a, &$b)
                        }
                }
 
-               $msgarr = BBCode::toPlaintext($item, $max_char, true, 8);
+               $msgarr = ItemContent::getPlaintextPost($item, $max_char, true, 8);
                $msg = $msgarr["text"];
 
                if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
@@ -805,23 +805,27 @@ function twitter_prepare_body(App $a, &$b)
  */
 function twitter_do_mirrorpost(App $a, $uid, $post)
 {
-       $datarray["type"] = "wall";
-       $datarray["api_source"] = true;
-       $datarray["profile_uid"] = $uid;
-       $datarray["extid"] = NETWORK_TWITTER;
-       $datarray['message_id'] = item_new_uri($a->get_hostname(), $uid, NETWORK_TWITTER . ":" . $post->id);
-       $datarray['object'] = json_encode($post);
-       $datarray["title"] = "";
-
-       if (is_object($post->retweeted_status)) {
+       $datarray['api_source'] = true;
+       $datarray['profile_uid'] = $uid;
+       $datarray['extid'] = Protocol::TWITTER;
+       $datarray['message_id'] = Item::newURI($uid, Protocol::TWITTER . ':' . $post->id);
+       $datarray['protocol'] = Conversation::PARCEL_TWITTER;
+       $datarray['source'] = json_encode($post);
+       $datarray['title'] = '';
+
+       if (!empty($post->retweeted_status)) {
                // We don't support nested shares, so we mustn't show quotes as shares on retweets
                $item = twitter_createpost($a, $uid, $post->retweeted_status, ['id' => 0], false, false, true);
 
+               if (empty($item['body'])) {
+                       return [];
+               }
+
                $datarray['body'] = "\n" . share_header(
                        $item['author-name'],
                        $item['author-link'],
                        $item['author-avatar'],
-                       "",
+                       '',
                        $item['created'],
                        $item['plink']
                );
@@ -830,18 +834,22 @@ function twitter_do_mirrorpost(App $a, $uid, $post)
        } else {
                $item = twitter_createpost($a, $uid, $post, ['id' => 0], false, false, false);
 
+               if (empty($item['body'])) {
+                       return [];
+               }
+
                $datarray['body'] = $item['body'];
        }
 
-       $datarray["source"] = $item['app'];
-       $datarray["verb"] = $item['verb'];
+       $datarray['source'] = $item['app'];
+       $datarray['verb'] = $item['verb'];
 
-       if (isset($item["location"])) {
-               $datarray["location"] = $item["location"];
+       if (isset($item['location'])) {
+               $datarray['location'] = $item['location'];
        }
 
-       if (isset($item["coord"])) {
-               $datarray["coord"] = $item["coord"];
+       if (isset($item['coord'])) {
+               $datarray['coord'] = $item['coord'];
        }
 
        return $datarray;
@@ -877,7 +885,12 @@ function twitter_fetchtimeline(App $a, $uid)
                $parameters["since_id"] = $lastid;
        }
 
-       $items = $connection->get('statuses/user_timeline', $parameters);
+       try {
+               $items = $connection->get('statuses/user_timeline', $parameters);
+       } catch (TwitterOAuthException $e) {
+               logger('twitter_fetchtimeline: Error fetching timeline for user ' . $uid . ': ' . $e->getMessage());
+               return;
+       }
 
        if (!is_array($items)) {
                return;
@@ -902,6 +915,10 @@ function twitter_fetchtimeline(App $a, $uid)
 
                                $_REQUEST = twitter_do_mirrorpost($a, $uid, $post);
 
+                               if (empty($_REQUEST['body'])) {
+                                       continue;
+                               }
+
                                logger('twitter: posting for user ' . $uid);
 
                                item_post($a);
@@ -911,17 +928,17 @@ function twitter_fetchtimeline(App $a, $uid)
        PConfig::set($uid, 'twitter', 'lastid', $lastid);
 }
 
-function twitter_queue_hook(App $a, &$b)
+function twitter_queue_hook(App $a)
 {
        $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
-               dbesc(NETWORK_TWITTER)
+               DBA::escape(Protocol::TWITTER)
        );
-       if (!count($qi)) {
+       if (!DBA::isResult($qi)) {
                return;
        }
 
        foreach ($qi as $x) {
-               if ($x['network'] !== NETWORK_TWITTER) {
+               if ($x['network'] !== Protocol::TWITTER) {
                        continue;
                }
 
@@ -931,7 +948,7 @@ function twitter_queue_hook(App $a, &$b)
                        WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
                        intval($x['cid'])
                );
-               if (!count($r)) {
+               if (!DBA::isResult($r)) {
                        continue;
                }
 
@@ -983,136 +1000,85 @@ function twitter_fix_avatar($avatar)
        return $new_avatar;
 }
 
-function twitter_fetch_contact($uid, $contact, $create_user)
+function twitter_fetch_contact($uid, $data, $create_user)
 {
-       if ($contact->id_str == "") {
+       if (empty($data->id_str)) {
                return -1;
        }
 
-       $avatar = twitter_fix_avatar($contact->profile_image_url_https);
+       $avatar = twitter_fix_avatar($data->profile_image_url_https);
+       $url = "https://twitter.com/" . $data->screen_name;
+       $addr = $data->screen_name . "@twitter.com";
 
-       GContact::update(["url" => "https://twitter.com/" . $contact->screen_name,
-               "network" => NETWORK_TWITTER, "photo" => $avatar, "hide" => true,
-               "name" => $contact->name, "nick" => $contact->screen_name,
-               "location" => $contact->location, "about" => $contact->description,
-               "addr" => $contact->screen_name . "@twitter.com", "generation" => 2]);
+       GContact::update(["url" => $url, "network" => Protocol::TWITTER,
+               "photo" => $avatar, "hide" => true,
+               "name" => $data->name, "nick" => $data->screen_name,
+               "location" => $data->location, "about" => $data->description,
+               "addr" => $addr, "generation" => 2]);
 
-       $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
-               intval($uid),
-               dbesc("twitter::" . $contact->id_str));
+       $fields = ['url' => $url, 'network' => Protocol::TWITTER,
+               'name' => $data->name, 'nick' => $data->screen_name, 'addr' => $addr,
+                'location' => $data->location, 'about' => $data->description];
 
-       if (!count($r) && !$create_user) {
-               return 0;
+       $cid = Contact::getIdForURL($url, 0, true, $fields);
+       if (!empty($cid)) {
+               DBA::update('contact', $fields, ['id' => $cid]);
+               Contact::updateAvatar($avatar, 0, $cid);
        }
 
-       if (count($r) && ($r[0]["readonly"] || $r[0]["blocked"])) {
-               logger("twitter_fetch_contact: Contact '" . $r[0]["nick"] . "' is blocked or readonly.", LOGGER_DEBUG);
-               return -1;
+       $contact = DBA::selectFirst('contact', [], ['uid' => $uid, 'alias' => "twitter::" . $data->id_str]);
+       if (!DBA::isResult($contact) && !$create_user) {
+               return 0;
        }
 
-       if (!count($r)) {
+       if (!DBA::isResult($contact)) {
                // create contact record
-               q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
-                                       `name`, `nick`, `photo`, `network`, `rel`, `priority`,
-                                       `location`, `about`, `writable`, `blocked`, `readonly`, `pending`)
-                                       VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, 0, 0, 0)",
-                       intval($uid),
-                       dbesc(Temporal::convert()),
-                       dbesc("https://twitter.com/" . $contact->screen_name),
-                       dbesc(normalise_link("https://twitter.com/" . $contact->screen_name)),
-                       dbesc($contact->screen_name."@twitter.com"),
-                       dbesc("twitter::" . $contact->id_str),
-                       dbesc(''),
-                       dbesc("twitter::" . $contact->id_str),
-                       dbesc($contact->name),
-                       dbesc($contact->screen_name),
-                       dbesc($avatar),
-                       dbesc(NETWORK_TWITTER),
-                       intval(CONTACT_IS_FRIEND),
-                       intval(1),
-                       dbesc($contact->location),
-                       dbesc($contact->description),
-                       intval(1)
-               );
-
-               $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d LIMIT 1",
-                       dbesc("twitter::".$contact->id_str),
-                       intval($uid)
-               );
-
-               if (!count($r)) {
+               $fields['uid'] = $uid;
+               $fields['created'] = DateTimeFormat::utcNow();
+               $fields['nurl'] = normalise_link($url);
+               $fields['alias'] = 'twitter::' . $data->id_str;
+               $fields['poll'] = 'twitter::' . $data->id_str;
+               $fields['rel'] = Contact::FRIEND;
+               $fields['priority'] = 1;
+               $fields['writable'] = true;
+               $fields['blocked'] = false;
+               $fields['readonly'] = false;
+               $fields['pending'] = false;
+
+               if (!DBA::insert('contact', $fields)) {
                        return false;
                }
 
-               $contact_id = $r[0]['id'];
+               $contact_id = DBA::lastInsertId();
 
                Group::addMember(User::getDefaultGroup($uid), $contact_id);
 
-               $photos = Photo::importProfilePhoto($avatar, $uid, $contact_id, true);
-
-               if ($photos) {
-                       q("UPDATE `contact` SET `photo` = '%s',
-                                               `thumb` = '%s',
-                                               `micro` = '%s',
-                                               `name-date` = '%s',
-                                               `uri-date` = '%s',
-                                                       `avatar-date` = '%s'
-                                       WHERE `id` = %d",
-                               dbesc($photos[0]),
-                               dbesc($photos[1]),
-                               dbesc($photos[2]),
-                               dbesc(Temporal::convert()),
-                               dbesc(Temporal::convert()),
-                               dbesc(Temporal::convert()),
-                               intval($contact_id)
-                       );
-               }
+               Contact::updateAvatar($avatar, $uid, $contact_id);
        } else {
-               // update profile photos once every two weeks as we have no notification of when they change.
-               //$update_photo = (($r[0]['avatar-date'] < Temporal::convert('now -2 days', '', '', )) ? true : false);
-               $update_photo = ($r[0]['avatar-date'] < Temporal::convert('now -12 hours'));
+               if ($contact["readonly"] || $contact["blocked"]) {
+                       logger("twitter_fetch_contact: Contact '" . $contact["nick"] . "' is blocked or readonly.", LOGGER_DEBUG);
+                       return -1;
+               }
+
+               $contact_id = $contact['id'];
+
+               // update profile photos once every twelve hours as we have no notification of when they change.
+               $update_photo = ($contact['avatar-date'] < DateTimeFormat::utc('now -12 hours'));
 
                // check that we have all the photos, this has been known to fail on occasion
-               if ((!$r[0]['photo']) || (!$r[0]['thumb']) || (!$r[0]['micro']) || ($update_photo)) {
-                       logger("twitter_fetch_contact: Updating contact " . $contact->screen_name, LOGGER_DEBUG);
-
-                       $photos = Photo::importProfilePhoto($avatar, $uid, $r[0]['id'], true);
-
-                       if ($photos) {
-                               q("UPDATE `contact` SET `photo` = '%s',
-                                                       `thumb` = '%s',
-                                                       `micro` = '%s',
-                                                       `name-date` = '%s',
-                                                       `uri-date` = '%s',
-                                                       `avatar-date` = '%s',
-                                                       `url` = '%s',
-                                                       `nurl` = '%s',
-                                                       `addr` = '%s',
-                                                       `name` = '%s',
-                                                       `nick` = '%s',
-                                                       `location` = '%s',
-                                                       `about` = '%s'
-                                               WHERE `id` = %d",
-                                       dbesc($photos[0]),
-                                       dbesc($photos[1]),
-                                       dbesc($photos[2]),
-                                       dbesc(Temporal::convert()),
-                                       dbesc(Temporal::convert()),
-                                       dbesc(Temporal::convert()),
-                                       dbesc("https://twitter.com/".$contact->screen_name),
-                                       dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
-                                       dbesc($contact->screen_name."@twitter.com"),
-                                       dbesc($contact->name),
-                                       dbesc($contact->screen_name),
-                                       dbesc($contact->location),
-                                       dbesc($contact->description),
-                                       intval($r[0]['id'])
-                               );
-                       }
+               if (empty($contact['photo']) || empty($contact['thumb']) || empty($contact['micro']) || $update_photo) {
+                       logger("twitter_fetch_contact: Updating contact " . $data->screen_name, LOGGER_DEBUG);
+
+                       Contact::updateAvatar($avatar, $uid, $contact['id']);
+
+                       $fields['name-date'] = DateTimeFormat::utcNow();
+                       $fields['uri-date'] = DateTimeFormat::utcNow();
+
+                       DBA::update('contact', $fields, ['id' => $contact['id']]);
                }
        }
 
-       return $r[0]["id"];
+       return $contact_id;
 }
 
 function twitter_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
@@ -1125,7 +1091,7 @@ function twitter_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
        $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
                intval($uid));
 
-       if (count($r)) {
+       if (DBA::isResult($r)) {
                $self = $r[0];
        } else {
                return;
@@ -1143,7 +1109,12 @@ function twitter_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
 
        // Fetching user data
        $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
-       $user = $connection->get('users/show', $parameters);
+       try {
+               $user = $connection->get('users/show', $parameters);
+       } catch (TwitterOAuthException $e) {
+               logger('twitter_fetchuser: Error fetching user ' . $uid . ': ' . $e->getMessage());
+               return;
+       }
 
        if (!is_object($user)) {
                return;
@@ -1154,12 +1125,24 @@ function twitter_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
        return $contact_id;
 }
 
-function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $picture)
+function twitter_expand_entities(App $a, $body, $item, $picture)
 {
-       $tags = "";
-
        $plain = $body;
 
+       $tags_arr = [];
+
+       foreach ($item->entities->hashtags AS $hashtag) {
+               $url = "#[url=" . $a->get_baseurl() . "/search?tag=" . rawurlencode($hashtag->text) . "]" . $hashtag->text . "[/url]";
+               $tags_arr["#" . $hashtag->text] = $url;
+               $body = str_replace("#" . $hashtag->text, $url, $body);
+       }
+
+       foreach ($item->entities->user_mentions AS $mention) {
+               $url = "@[url=https://twitter.com/" . rawurlencode($mention->screen_name) . "]" . $mention->screen_name . "[/url]";
+               $tags_arr["@" . $mention->screen_name] = $url;
+               $body = str_replace("@" . $mention->screen_name, $url, $body);
+       }
+
        if (isset($item->entities->urls)) {
                $type = "";
                $footerurl = "";
@@ -1174,6 +1157,10 @@ function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $pictur
 
                                $oembed_data = OEmbed::fetchURL($expanded_url);
 
+                               if (empty($oembed_data) || empty($oembed_data->type)) {
+                                       continue;
+                               }
+
                                // Quickfix: Workaround for URL with "[" and "]" in it
                                if (strpos($expanded_url, "[") || strpos($expanded_url, "]")) {
                                        $expanded_url = $url->url;
@@ -1189,7 +1176,7 @@ function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $pictur
                                        //$dontincludemedia = true;
                                        $type = $oembed_data->type;
                                        $footerurl = $expanded_url;
-                                       $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
+                                       $footerlink = "[url=" . $expanded_url . "]" . $url->display_url . "[/url]";
 
                                        $body = str_replace($url->url, $footerlink, $body);
                                        //} elseif (($oembed_data->type == "photo") AND isset($oembed_data->url) AND !$dontincludemedia) {
@@ -1197,13 +1184,20 @@ function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $pictur
                                        $body = str_replace($url->url, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
                                        //$dontincludemedia = true;
                                } elseif ($oembed_data->type != "link") {
-                                       $body = str_replace($url->url, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
+                                       $body = str_replace($url->url, "[url=" . $expanded_url . "]" . $url->display_url . "[/url]", $body);
                                } else {
                                        $img_str = Network::fetchUrl($expanded_url, true, $redirects, 4);
 
                                        $tempfile = tempnam(get_temppath(), "cache");
                                        file_put_contents($tempfile, $img_str);
-                                       $mime = image_type_to_mime_type(exif_imagetype($tempfile));
+
+                                       // See http://php.net/manual/en/function.exif-imagetype.php#79283
+                                       if (filesize($tempfile) > 11) {
+                                               $mime = image_type_to_mime_type(exif_imagetype($tempfile));
+                                       } else {
+                                               $mime = false;
+                                       }
+
                                        unlink($tempfile);
 
                                        if (substr($mime, 0, 6) == "image/") {
@@ -1213,7 +1207,7 @@ function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $pictur
                                        } else {
                                                $type = $oembed_data->type;
                                                $footerurl = $expanded_url;
-                                               $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
+                                               $footerlink = "[url=" . $expanded_url . "]" . $url->display_url . "[/url]";
 
                                                $body = str_replace($url->url, $footerlink, $body);
                                        }
@@ -1240,66 +1234,49 @@ function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $pictur
                } elseif (($footer == "") && ($picture == "")) {
                        $body = add_page_info_to_body($body);
                }
+       }
 
-               if ($no_tags) {
-                       return ["body" => $body, "tags" => "", "plain" => $plain];
-               }
-
-               $tags_arr = [];
+       // it seems as if the entities aren't always covering all mentions. So the rest will be checked here
+       $tags = get_tags($body);
 
-               foreach ($item->entities->hashtags AS $hashtag) {
-                       $url = "#[url=" . $a->get_baseurl() . "/search?tag=" . rawurlencode($hashtag->text) . "]" . $hashtag->text . "[/url]";
-                       $tags_arr["#" . $hashtag->text] = $url;
-                       $body = str_replace("#" . $hashtag->text, $url, $body);
-               }
-
-               foreach ($item->entities->user_mentions AS $mention) {
-                       $url = "@[url=https://twitter.com/" . rawurlencode($mention->screen_name) . "]" . $mention->screen_name . "[/url]";
-                       $tags_arr["@" . $mention->screen_name] = $url;
-                       $body = str_replace("@" . $mention->screen_name, $url, $body);
-               }
-
-               // it seems as if the entities aren't always covering all mentions. So the rest will be checked here
-               $tags = get_tags($body);
+       if (count($tags)) {
+               foreach ($tags as $tag) {
+                       if (strstr(trim($tag), " ")) {
+                               continue;
+                       }
 
-               if (count($tags)) {
-                       foreach ($tags as $tag) {
-                               if (strstr(trim($tag), " ")) {
+                       if (strpos($tag, '#') === 0) {
+                               if (strpos($tag, '[url=')) {
                                        continue;
                                }
 
-                               if (strpos($tag, '#') === 0) {
-                                       if (strpos($tag, '[url=')) {
-                                               continue;
-                                       }
-
-                                       // don't link tags that are already embedded in links
-                                       if (preg_match('/\[(.*?)' . preg_quote($tag, '/') . '(.*?)\]/', $body)) {
-                                               continue;
-                                       }
-                                       if (preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag, '/') . '(.*?)\)/', $body)) {
-                                               continue;
-                                       }
-
-                                       $basetag = str_replace('_', ' ', substr($tag, 1));
-                                       $url = '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
-                                       $body = str_replace($tag, $url, $body);
-                                       $tags_arr["#" . $basetag] = $url;
-                               } elseif (strpos($tag, '@') === 0) {
-                                       if (strpos($tag, '[url=')) {
-                                               continue;
-                                       }
+                               // don't link tags that are already embedded in links
+                               if (preg_match('/\[(.*?)' . preg_quote($tag, '/') . '(.*?)\]/', $body)) {
+                                       continue;
+                               }
+                               if (preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag, '/') . '(.*?)\)/', $body)) {
+                                       continue;
+                               }
 
-                                       $basetag = substr($tag, 1);
-                                       $url = '@[url=https://twitter.com/' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
-                                       $body = str_replace($tag, $url, $body);
-                                       $tags_arr["@" . $basetag] = $url;
+                               $basetag = str_replace('_', ' ', substr($tag, 1));
+                               $url = '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
+                               $body = str_replace($tag, $url, $body);
+                               $tags_arr["#" . $basetag] = $url;
+                       } elseif (strpos($tag, '@') === 0) {
+                               if (strpos($tag, '[url=')) {
+                                       continue;
                                }
+
+                               $basetag = substr($tag, 1);
+                               $url = '@[url=https://twitter.com/' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
+                               $body = str_replace($tag, $url, $body);
+                               $tags_arr["@" . $basetag] = $url;
                        }
                }
-
-               $tags = implode($tags_arr, ",");
        }
+
+       $tags = implode($tags_arr, ",");
+
        return ["body" => $body, "tags" => $tags, "plain" => $plain];
 }
 
@@ -1309,12 +1286,12 @@ function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $pictur
  * @param object $post Twitter object with the post
  * @param array $postarray Array of the item that is about to be posted
  *
- * @return $picture string Returns a a single picture string if it isn't a media post
+ * @return $picture string Image URL or empty string
  */
-function twitter_media_entities($post, &$postarray)
+function twitter_media_entities($post, array &$postarray)
 {
        // There are no media entities? So we quit.
-       if (!is_array($post->extended_entities->media)) {
+       if (empty($post->extended_entities->media)) {
                return "";
        }
 
@@ -1334,6 +1311,9 @@ function twitter_media_entities($post, &$postarray)
        // This is a pure media post, first search for all media urls
        $media = [];
        foreach ($post->extended_entities->media AS $medium) {
+               if (!isset($media[$medium->url])) {
+                       $media[$medium->url] = '';
+               }
                switch ($medium->type) {
                        case 'photo':
                                $media[$medium->url] .= "\n[img]" . $medium->media_url_https . "[/img]";
@@ -1367,23 +1347,18 @@ function twitter_media_entities($post, &$postarray)
        return "";
 }
 
-function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_existing_contact, $noquote)
+function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $only_existing_contact, $noquote)
 {
        $postarray = [];
-       $postarray['network'] = NETWORK_TWITTER;
-       $postarray['gravity'] = 0;
+       $postarray['network'] = Protocol::TWITTER;
        $postarray['uid'] = $uid;
        $postarray['wall'] = 0;
        $postarray['uri'] = "twitter::" . $post->id_str;
-       $postarray['object'] = json_encode($post);
+       $postarray['protocol'] = Conversation::PARCEL_TWITTER;
+       $postarray['source'] = json_encode($post);
 
        // Don't import our own comments
-       $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
-               dbesc($postarray['uri']),
-               intval($uid)
-       );
-
-       if (count($r)) {
+       if (Item::exists(['extid' => $postarray['uri'], 'uid' => $uid])) {
                logger("Item with extid " . $postarray['uri'] . " found.", LOGGER_DEBUG);
                return [];
        }
@@ -1393,30 +1368,21 @@ function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_exis
        if ($post->in_reply_to_status_id_str != "") {
                $parent = "twitter::" . $post->in_reply_to_status_id_str;
 
-               $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                       dbesc($parent),
-                       intval($uid)
-               );
-               if (count($r)) {
-                       $postarray['thr-parent'] = $r[0]["uri"];
-                       $postarray['parent-uri'] = $r[0]["parent-uri"];
-                       $postarray['parent'] = $r[0]["parent"];
+               $fields = ['uri', 'parent-uri', 'parent'];
+               $parent_item = Item::selectFirst($fields, ['uri' => $parent, 'uid' => $uid]);
+               if (!DBA::isResult($parent_item)) {
+                       $parent_item = Item::selectFirst($fields, ['extid' => $parent, 'uid' => $uid]);
+               }
+
+               if (DBA::isResult($parent_item)) {
+                       $postarray['thr-parent'] = $parent_item['uri'];
+                       $postarray['parent-uri'] = $parent_item['parent-uri'];
+                       $postarray['parent'] = $parent_item['parent'];
                        $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
                } else {
-                       $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
-                               dbesc($parent),
-                               intval($uid)
-                       );
-                       if (count($r)) {
-                               $postarray['thr-parent'] = $r[0]['uri'];
-                               $postarray['parent-uri'] = $r[0]['parent-uri'];
-                               $postarray['parent'] = $r[0]['parent'];
-                               $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
-                       } else {
-                               $postarray['thr-parent'] = $postarray['uri'];
-                               $postarray['parent-uri'] = $postarray['uri'];
-                               $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
-                       }
+                       $postarray['thr-parent'] = $postarray['uri'];
+                       $postarray['parent-uri'] = $postarray['uri'];
+                       $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
                }
 
                // Is it me?
@@ -1426,7 +1392,7 @@ function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_exis
                        $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
                                intval($uid));
 
-                       if (count($r)) {
+                       if (DBA::isResult($r)) {
                                $contactid = $r[0]["id"];
 
                                $postarray['owner-name']   = $r[0]["name"];
@@ -1471,6 +1437,9 @@ function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_exis
        if ($post->user->protected) {
                $postarray['private'] = 1;
                $postarray['allow_cid'] = '<' . $self['id'] . '>';
+       } else {
+               $postarray['private'] = 0;
+               $postarray['allow_cid'] = '';
        }
 
        if (is_string($post->full_text)) {
@@ -1487,30 +1456,34 @@ function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_exis
        // Search for media links
        $picture = twitter_media_entities($post, $postarray);
 
-       $converted = twitter_expand_entities($a, $postarray['body'], $post, false, $picture);
+       $converted = twitter_expand_entities($a, $postarray['body'], $post, $picture);
        $postarray['body'] = $converted["body"];
        $postarray['tag'] = $converted["tags"];
-       $postarray['created'] = Temporal::convert($post->created_at);
-       $postarray['edited'] = Temporal::convert($post->created_at);
+       $postarray['created'] = DateTimeFormat::utc($post->created_at);
+       $postarray['edited'] = DateTimeFormat::utc($post->created_at);
 
        $statustext = $converted["plain"];
 
-       if (is_string($post->place->name)) {
+       if (!empty($post->place->name)) {
                $postarray["location"] = $post->place->name;
        }
-       if (is_string($post->place->full_name)) {
+       if (!empty($post->place->full_name)) {
                $postarray["location"] = $post->place->full_name;
        }
-       if (is_array($post->geo->coordinates)) {
+       if (!empty($post->geo->coordinates)) {
                $postarray["coord"] = $post->geo->coordinates[0] . " " . $post->geo->coordinates[1];
        }
-       if (is_array($post->coordinates->coordinates)) {
+       if (!empty($post->coordinates->coordinates)) {
                $postarray["coord"] = $post->coordinates->coordinates[1] . " " . $post->coordinates->coordinates[0];
        }
-       if (is_object($post->retweeted_status)) {
+       if (!empty($post->retweeted_status)) {
                $retweet = twitter_createpost($a, $uid, $post->retweeted_status, $self, false, false, $noquote);
 
-               $retweet['object'] = $postarray['object'];
+               if (empty($retweet['body'])) {
+                       return [];
+               }
+
+               $retweet['source'] = $postarray['source'];
                $retweet['private'] = $postarray['private'];
                $retweet['allow_cid'] = $postarray['allow_cid'];
                $retweet['contact-id'] = $postarray['contact-id'];
@@ -1521,9 +1494,13 @@ function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_exis
                $postarray = $retweet;
        }
 
-       if (is_object($post->quoted_status) && !$noquote) {
+       if (!empty($post->quoted_status) && !$noquote) {
                $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true);
 
+               if (empty($quoted['body'])) {
+                       return [];
+               }
+
                $postarray['body'] = $statustext;
 
                $postarray['body'] .= "\n" . share_header(
@@ -1541,97 +1518,33 @@ function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_exis
        return $postarray;
 }
 
-function twitter_checknotification(App $a, $uid, $own_id, $top_item, $postarray)
-{
-       /// TODO: this whole function doesn't seem to work. Needs complete check
-       $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
-               intval($uid)
-       );
-
-       if (!count($user)) {
-               return;
-       }
-
-       // Is it me?
-       if (link_compare($user[0]["url"], $postarray['author-link'])) {
-               return;
-       }
-
-       $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
-               intval($uid),
-               dbesc("twitter::".$own_id)
-       );
-
-       if (!count($own_user)) {
-               return;
-       }
-
-       // Is it me from twitter?
-       if (link_compare($own_user[0]["url"], $postarray['author-link'])) {
-               return;
-       }
-
-       $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
-               dbesc($postarray['parent-uri']),
-               intval($uid)
-       );
-
-       if (count($myconv)) {
-               foreach ($myconv as $conv) {
-                       // now if we find a match, it means we're in this conversation
-                       if (!link_compare($conv['author-link'], $user[0]["url"]) && !link_compare($conv['author-link'], $own_user[0]["url"])) {
-                               continue;
-                       }
-
-                       require_once 'include/enotify.php';
-
-                       $conv_parent = $conv['parent'];
-
-                       notification([
-                               'type' => NOTIFY_COMMENT,
-                               'notify_flags' => $user[0]['notify-flags'],
-                               'language' => $user[0]['language'],
-                               'to_name' => $user[0]['username'],
-                               'to_email' => $user[0]['email'],
-                               'uid' => $user[0]['uid'],
-                               'item' => $postarray,
-                               'link' => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($top_item)),
-                               'source_name' => $postarray['author-name'],
-                               'source_link' => $postarray['author-link'],
-                               'source_photo' => $postarray['author-avatar'],
-                               'verb' => ACTIVITY_POST,
-                               'otype' => 'item',
-                               'parent' => $conv_parent,
-                       ]);
-
-                       // only send one notification
-                       break;
-               }
-       }
-}
-
-function twitter_fetchparentposts(App $a, $uid, $post, $connection, $self, $own_id)
+function twitter_fetchparentposts(App $a, $uid, $post, TwitterOAuth $connection, array $self)
 {
        logger("twitter_fetchparentposts: Fetching for user " . $uid . " and post " . $post->id_str, LOGGER_DEBUG);
 
        $posts = [];
 
-       while ($post->in_reply_to_status_id_str != "") {
+       while (!empty($post->in_reply_to_status_id_str)) {
                $parameters = ["trim_user" => false, "tweet_mode" => "extended", "id" => $post->in_reply_to_status_id_str];
 
-               $post = $connection->get('statuses/show', $parameters);
+               try {
+                       $post = $connection->get('statuses/show', $parameters);
+               } catch (TwitterOAuthException $e) {
+                       logger('twitter_fetchparentposts: Error fetching for user ' . $uid . ' and post ' . $post->id_str . ': ' . $e->getMessage());
+                       break;
+               }
 
-               if (!count($post)) {
+               if (empty($post)) {
                        logger("twitter_fetchparentposts: Can't fetch post " . $parameters->id, LOGGER_DEBUG);
                        break;
                }
 
-               $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                       dbesc("twitter::".$post->id_str),
-                       intval($uid)
-               );
+               if (empty($post->id_str)) {
+                       logger("twitter_fetchparentposts: This is not a post " . json_encode($post), LOGGER_DEBUG);
+                       break;
+               }
 
-               if (count($r)) {
+               if (Item::exists(['uri' => 'twitter::' . $post->id_str, 'uid' => $uid])) {
                        break;
                }
 
@@ -1642,22 +1555,19 @@ function twitter_fetchparentposts(App $a, $uid, $post, $connection, $self, $own_
 
        $posts = array_reverse($posts);
 
-       if (count($posts)) {
+       if (!empty($posts)) {
                foreach ($posts as $post) {
                        $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
 
-                       if (trim($postarray['body']) == "") {
+                       if (empty($postarray['body'])) {
                                continue;
                        }
 
                        $item = Item::insert($postarray);
+
                        $postarray["id"] = $item;
 
                        logger('twitter_fetchparentpost: User ' . $self["nick"] . ' posted parent timeline item ' . $item);
-
-                       if ($item && !function_exists("check_item_notification")) {
-                               twitter_checknotification($a, $uid, $own_id, $item, $postarray);
-                       }
                }
        }
 }
@@ -1683,13 +1593,18 @@ function twitter_fetchhometimeline(App $a, $uid)
 
        $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
 
-       $own_contact = twitter_fetch_own_contact($a, $uid);
+       try {
+               $own_contact = twitter_fetch_own_contact($a, $uid);
+       } catch (TwitterOAuthException $e) {
+               logger('twitter_fetchhometimeline: Error fetching own contact for user ' . $uid . ': ' . $e->getMessage());
+               return;
+       }
 
        $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
                intval($own_contact),
                intval($uid));
 
-       if (count($r)) {
+       if (DBA::isResult($r)) {
                $own_id = $r[0]["nick"];
        } else {
                logger("twitter_fetchhometimeline: Own twitter contact not found for user " . $uid, LOGGER_DEBUG);
@@ -1699,7 +1614,7 @@ function twitter_fetchhometimeline(App $a, $uid)
        $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
                intval($uid));
 
-       if (count($r)) {
+       if (DBA::isResult($r)) {
                $self = $r[0];
        } else {
                logger("twitter_fetchhometimeline: Own contact not found for user " . $uid, LOGGER_DEBUG);
@@ -1708,7 +1623,7 @@ function twitter_fetchhometimeline(App $a, $uid)
 
        $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
                intval($uid));
-       if (!count($u)) {
+       if (!DBA::isResult($u)) {
                logger("twitter_fetchhometimeline: Own user not found for user " . $uid, LOGGER_DEBUG);
                return;
        }
@@ -1724,7 +1639,12 @@ function twitter_fetchhometimeline(App $a, $uid)
                $parameters["since_id"] = $lastid;
        }
 
-       $items = $connection->get('statuses/home_timeline', $parameters);
+       try {
+               $items = $connection->get('statuses/home_timeline', $parameters);
+       } catch (TwitterOAuthException $e) {
+               logger('twitter_fetchhometimeline: Error fetching home timeline: ' . $e->getMessage());
+               return;
+       }
 
        if (!is_array($items)) {
                logger("twitter_fetchhometimeline: Error fetching home timeline: " . print_r($items, true), LOGGER_DEBUG);
@@ -1757,23 +1677,28 @@ function twitter_fetchhometimeline(App $a, $uid)
                        }
 
                        if ($post->in_reply_to_status_id_str != "") {
-                               twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
+                               twitter_fetchparentposts($a, $uid, $post, $connection, $self);
                        }
 
                        $postarray = twitter_createpost($a, $uid, $post, $self, $create_user, true, false);
 
-                       if (trim($postarray['body']) == "") {
+                       if (empty($postarray['body']) || trim($postarray['body']) == "") {
                                continue;
                        }
 
-                       $item = Item::insert($postarray);
+                       $notify = false;
+
+                       if (($postarray['uri'] == $postarray['parent-uri']) && ($postarray['author-link'] == $postarray['owner-link'])) {
+                               $contact = DBA::selectFirst('contact', [], ['id' => $postarray['contact-id'], 'self' => false]);
+                               if (DBA::isResult($contact)) {
+                                       $notify = Item::isRemoteSelf($contact, $postarray);
+                               }
+                       }
+
+                       $item = Item::insert($postarray, false, $notify);
                        $postarray["id"] = $item;
 
                        logger('twitter_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
-
-                       if ($item && !function_exists("check_item_notification")) {
-                               twitter_checknotification($a, $uid, $own_id, $item, $postarray);
-                       }
                }
        }
        PConfig::set($uid, 'twitter', 'lasthometimelineid', $lastid);
@@ -1787,7 +1712,12 @@ function twitter_fetchhometimeline(App $a, $uid)
                $parameters["since_id"] = $lastid;
        }
 
-       $items = $connection->get('statuses/mentions_timeline', $parameters);
+       try {
+               $items = $connection->get('statuses/mentions_timeline', $parameters);
+       } catch (TwitterOAuthException $e) {
+               logger('twitter_fetchhometimeline: Error fetching mentions: ' . $e->getMessage());
+               return;
+       }
 
        if (!is_array($items)) {
                logger("twitter_fetchhometimeline: Error fetching mentions: " . print_r($items, true), LOGGER_DEBUG);
@@ -1809,60 +1739,18 @@ function twitter_fetchhometimeline(App $a, $uid)
                        }
 
                        if ($post->in_reply_to_status_id_str != "") {
-                               twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
+                               twitter_fetchparentposts($a, $uid, $post, $connection, $self);
                        }
 
                        $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
 
-                       if (trim($postarray['body']) == "") {
+                       if (empty($postarray['body'])) {
                                continue;
                        }
 
                        $item = Item::insert($postarray);
-                       $postarray["id"] = $item;
-
-                       if ($item && function_exists("check_item_notification")) {
-                               check_item_notification($item, $uid, NOTIFY_TAGSELF);
-                       }
-
-                       if (!isset($postarray["parent"]) || ($postarray["parent"] == 0)) {
-                               $postarray["parent"] = $item;
-                       }
 
                        logger('twitter_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
-
-                       if ($item == 0) {
-                               $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                                       dbesc($postarray['uri']),
-                                       intval($uid)
-                               );
-                               if (count($r)) {
-                                       $item = $r[0]['id'];
-                                       $parent_id = $r[0]['parent'];
-                               }
-                       } else {
-                               $parent_id = $postarray['parent'];
-                       }
-
-                       if (($item != 0) && !function_exists("check_item_notification")) {
-                               require_once 'include/enotify.php';
-                               notification([
-                                       'type'         => NOTIFY_TAGSELF,
-                                       'notify_flags' => $u[0]['notify-flags'],
-                                       'language'     => $u[0]['language'],
-                                       'to_name'      => $u[0]['username'],
-                                       'to_email'     => $u[0]['email'],
-                                       'uid'          => $u[0]['uid'],
-                                       'item'         => $postarray,
-                                       'link'         => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($item)),
-                                       'source_name'  => $postarray['author-name'],
-                                       'source_link'  => $postarray['author-link'],
-                                       'source_photo' => $postarray['author-avatar'],
-                                       'verb'         => ACTIVITY_TAG,
-                                       'otype'        => 'item',
-                                       'parent'       => $parent_id
-                               ]);
-                       }
                }
        }
 
@@ -1884,6 +1772,7 @@ function twitter_fetch_own_contact(App $a, $uid)
                $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
 
                // Fetching user data
+               // get() may throw TwitterOAuthException, but we will catch it later
                $user = $connection->get('account/verify_credentials');
 
                PConfig::set($uid, 'twitter', 'own_id', $user->id_str);
@@ -1892,8 +1781,8 @@ function twitter_fetch_own_contact(App $a, $uid)
        } else {
                $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
                        intval($uid),
-                       dbesc("twitter::" . $own_id));
-               if (count($r)) {
+                       DBA::escape("twitter::" . $own_id));
+               if (DBA::isResult($r)) {
                        $contact_id = $r[0]["id"];
                } else {
                        PConfig::delete($uid, 'twitter', 'own_id');
@@ -1926,12 +1815,12 @@ function twitter_is_retweet(App $a, $uid, $body)
 
        $link = "";
        preg_match("/link='(.*?)'/ism", $attributes, $matches);
-       if ($matches[1] != "") {
+       if (!empty($matches[1])) {
                $link = $matches[1];
        }
 
        preg_match('/link="(.*?)"/ism', $attributes, $matches);
-       if ($matches[1] != "") {
+       if (!empty($matches[1])) {
                $link = $matches[1];
        }