' . PLUGIN_EVENT_TWITTER_TWEETER_SIDEBARTITLE . '';
$this->display_twitter_client(true);
return true;
case 'frontend_footer':
$this->display_frontend_footer();
return true;
case 'frontend_saveComment':
$this->hook_saveComment($eventData, $addData);
return true;
case 'backend_display':
if (!serendipity_db_bool($this->get_config('announce_articles'))) {
return true;
}
if (isset($serendipity['POST']['properties']['microblogging_tagList'])) {
$tagList = $serendipity['POST']['properties']['microblogging_tagList'];
} else {
$tagList = '';
}
if (isset($serendipity['POST']['properties']['microblogging_dontannounce'])) {
$checked_dontannounce = "checked='checked'";
} else {
$checked_dontannounce = '';
}
if (serendipity_db_bool($this->get_config('announce_articles_default_no'))) {
$checked_dontannounce = "checked='checked'";
}
if ($serendipity['version'][0] == '1') {
?>
get_config('highest_id_global','0')<$highest_id_single) {
$this->set_config('highest_id_global', TwitterPluginDbAccess::find_highest_twitterid());
}
// patch old config
if ('default' == $this->get_config('tweetback_moderate')) {
$this->set_config('tweetback_moderate','approved');
}
if ('snipr' == $this->get_config('anounce_url_service')) {
$this->set_config('7ax');
}
$this->check_tweetbacks_global(true);
}
function install() {
TwitterPluginDbAccess::install($this);
}
function addToCSS(&$eventData) {
$eventData .= '
/* plugin twitter */
#twitter_update_list {
list-style: none;
padding-left: 0;
}
a.twitter_update_time {
display: block;
padding-bottom: 5px;
}
';
}
function add_backend_header_parts() {
$cssfilename = TwitterPluginFileAccess::get_file_from_template('tweeter/serendipity_event_twitter_tweeter.css', $this->get_config('plugin_rel_url'));
echo '' . "\n";
$jsfilename = $this->get_config('plugin_rel_url') . '/tweeter/serendipity_event_twitter_tweeter.js';
echo '' . "\n";
}
function display_frontend_footer() {
global $serendipity;
if (!serendipity_db_bool($this->get_config('do_tweetbacks',false))) return false;
$pluginurl = $serendipity['baseURL'] . $serendipity['indexFile'] . '?/' . TwitterPluginFileAccess::get_permaplugin_path();
// add pixel doing tweetback check to each visible entry footer
$tweetbackpng_url = $pluginurl . '/gtweetback.png';
echo '';
}
/**
* Entry deleted event fetched. Delete all data, not needed anymore.
*/
function entry_deleted($entryId) {
TwitterPluginDbAccess::entry_deleted($entryId);
}
function check_tweetbacks_save_comment($article_id, $entry, $comment_type, $strip_tags = false) {
$commentInfo = array();
$commentInfo['title'] = $entry[TWITTER_SEARCHRESULT_REALNAME] . " via Twitter";
$commentInfo['name'] = $entry[TWITTER_SEARCHRESULT_REALNAME];
$commentInfo['url'] = $this->comment_url($entry);
$commentInfo['email'] = $entry[TWITTER_SEARCHRESULT_EMAIL];
$comment = $entry[TWITTER_SEARCHRESULT_TWEET];
if ($strip_tags) {
$comment = strip_tags($comment);
}
if (LANG_CHARSET!='UTF-8' && function_exists("mb_convert_encoding")) {
$comment = mb_convert_encoding($comment, LANG_CHARSET);
}
$commentInfo['comment'] = $comment;
$commentInfo['time'] = strtotime($entry[TWITTER_SEARCHRESULT_PUBDATE]);
$commentInfo['source'] = 'tweetback';
$this->log("Tweetback save: title=[" . $commentInfo['title'] ."], comment=[" . $commentInfo['comment'] . "] articleid=[$article_id]");
// patch old config
if ('default' == $this->get_config('tweetback_moderate')) {
$this->set_config('tweetback_moderate','approved');
}
$comment_moderation = $this->get_config('tweetback_moderate','approved');
if ('save'==$comment_moderation) {
// save comment starts spam plugin. This might intervent the saving, but we don't want that here.
// If we have more than 1 tweetback, at least the min posting freq for one IP will hit.
return serendipity_saveComment($article_id, $commentInfo, $comment_type, 'tweetback');
}
else {
$ca = array();
$this->hook_saveComment($ca, $commentInfo);
return serendipity_insertComment($article_id, $commentInfo, $comment_type, 'tweetback', $ca);
}
}
function hook_saveComment(&$ca, &$commentInfo) {
// is this our comment to be saved?
if (empty($commentInfo) || $commentInfo['source']!='tweetback') return;
$comment_moderation = $this->get_config('tweetback_moderate','approved');
if ('save'!=$comment_moderation) {
$commentInfo['status'] = $comment_moderation;
if ('pending' == $comment_moderation) {
$ca['moderate_comments'] = true;
}
else if ('approved' == $comment_moderation) {
$ca['moderate_comments'] = false;
}
else if ('default' == $comment_moderation) { // this is an old configuration
$ca['moderate_comments'] = false;
}
}
$this->log("hook_saveComment end");
}
function check_tweetbacks_global($complete = false) {
global $serendipity;
$this->log("Check global");
if (!serendipity_db_bool($this->get_config('do_tweetbacks'))) return false;
$lastcheck = $this->get_config('last_check_global');
$check_freq = $this->get_config('tweetback_check_freq',30);
if (!is_numeric($check_freq) || $check_freq < 5) {
$check_freq = 5;
}
$check_freq = $check_freq * 60; // we need seconds
if (!$complete && !empty($lastcheck) && (time() - $lastcheck) < $check_freq){
// Search already done.
return $lastcheck + $check_freq;
}
// TODO: This file sema is blocking! wtf?!
if (!($fp=TwitterPluginFileAccess::get_lock(0))) {
$this->log("Sema is blocking.");
return time() + $check_freq; // someone else ist processing the global check
}
$baseUrl = $serendipity['baseURL'];
$permalinkRegex = '@(' . $baseUrl . '.*'. serendipity_makePermalinkRegex($serendipity['permalinkStructure'], 'entry') . ')/?@i';
if ($complete) {
$old_since_id ='0';
}
else {
$old_since_id = $this->get_config('highest_id_global');
}
$this->log("old_since_id [$old_since_id]");
$comment_type = $this->get_config('tweetback_type','TRACKBACK');
$ignore_names = explode(',', $this->get_config('ignore_tweetbacks_by_name', $this->get_config('twittername')));
$search_since_id = "0";
if (!empty($old_since_id)) {
$search_since_id = $old_since_id;
}
// Start searching
$idx = $this->get_config('twitter_generic_acc', '1');
if ($idx == '1') {
$idx = '';
}
$connection = $this->twitteroa_connect($idx);
$twitterapi = new TwitterOAuthApi($connection);
$twittersearch = $this->generate_domain_url(false) . "&since_id=".$search_since_id;
$entries = $twitterapi->search($twittersearch);
if (is_array($entries) && !empty($entries) ) {
// reverse the entries to get oldest first!
$entries = array_reverse( $entries, true );
$redirCheck = new RedirectCheck();
$validated_entries = array();
foreach ($entries as $entry) {
$writer = $entry[TWITTER_SEARCHRESULT_LOGIN];
// First check if the tweets autor should be ignored:
$ignore = false;
foreach( $ignore_names as $ignore_name) {
$ignore = strtoupper(trim($ignore_name)) == strtoupper($writer);
if ($ignore) break;
}
if ($ignore) continue;
// Check wether this tweet is blog article related:
$tweetmatches = false;
// Check all expanded urls:
if (!empty($entry[TWITTER_SEARCHRESULT_URL_ARRAY])) {
foreach ($entry[TWITTER_SEARCHRESULT_URL_ARRAY] as $url) {
$url = $redirCheck->get_final_url($url);
$tweetmatches = preg_match($permalinkRegex, $url, $matches);
if ($tweetmatches) break; // Found it!
}
}
// If no URL matches, try the tweet itself:
if (!$tweetmatches) {
$tweet = $entry[TWITTER_SEARCHRESULT_TWEET];
!$tweetmatches = preg_match($permalinkRegex, $tweet, $matches);
}
// If we found a match, add it to the validated entries
if ($tweetmatches) {
$this->log("Tweet matches!");
$match = array('id' => $matches[2], 'entry' => $entry);
$validated_entries[] = $match;
}
}
} //is array $entries end
// Save all comments and evaluate highest ids:
$highest_ids = array();
if (is_array($validated_entries) && !empty($validated_entries) ) {
foreach ($validated_entries as $valid) {
$comment_saved = false;
$article_id = $valid['id'];
$entry = $valid['entry'];
$test_highest_id = null;
if (!empty($highest_ids[$article_id])) {
$test_highest_id = $highest_ids[$article_id]['high_id'];
}
if (empty($test_highest_id)) {
// load old highest id of the article
$last_info = TwitterPluginDbAccess::load_tweetback_info($article_id, $this);
$test_highest_id = empty($last_info)?null:$last_info['lasttweetid'];
$highest_ids[$article_id] = array('high_id' => $test_highest_id, 'last_info' => $last_info);
}
if (empty($highest_ids[$article_id]['last_info']) || empty($highest_ids[$article_id]['last_info']['lasttweetid']) || "{$entry[TWITTER_SEARCHRESULT_ID]}">$highest_ids[$article_id]['last_info']['lasttweetid']) {
if ($complete) { // This is called from admin interface
echo "
Found new tweetback for article $article_id: tweetid: {$entry[TWITTER_SEARCHRESULT_ID]}
";
}
$this->check_tweetbacks_save_comment($article_id, $entry, $comment_type, true);
$comment_saved = true;
}
// Remember highest id, if saved and highest id is higher than old one.
if ($comment_saved && (empty($test_highest_id) || "{$entry[TWITTER_SEARCHRESULT_ID]}" > "$test_highest_id")) {
$highest_ids[$article_id]['high_id'] = "{$entry[TWITTER_SEARCHRESULT_ID]}";
}
}
} // is array $validated_entries end
$global_highest_id = $this->get_config('highest_id_global');
if (empty($global_highest_id)) $global_highest_id = '0';
if (is_array($highest_ids) && !empty($highest_ids) ) {
// Save highest ids:
foreach( $highest_ids as $article_id => $highest_id_array ) {
// remember globaly
if (!empty($highest_id_array['high_id']) && $highest_id_array['high_id'] > $global_highest_id) {
$global_highest_id = $highest_id_array['high_id'];
}
// save per article
if (empty($highest_id_array['last_info']) || $highest_id_array['high_id']>$highest_id_array['last_info']['lasttweetid']) {
TwitterPluginDbAccess::save_highest_id($article_id, $highest_id_array['high_id'], $highest_id_array['last_info']);
}
}
} // is array $highest_ids end
$lastcheck = time();
$this->set_config('last_check_global', $lastcheck);
$this->set_config('highest_id_global', $global_highest_id);
TwitterPluginFileAccess::free_lock(0);
return $lastcheck + $check_freq;
}
function search($article_url, $old_since_id) {
if ($this->get_config('twitter_api', 'api10') == 'api10') {
$entries = Twitter::search_multiple($article_url, $old_since_id);
}
else {
$idx = $this->get_config('twitter_generic_acc', '1');
if ($idx=='1') $idx = '';
$connection = $this->twitteroa_connect($idx);
$entries = TwitterOAuthApi::search_multiple($connection, $article_url, $old_since_id);
}
return array_reverse ( $entries, true );
}
function comment_url($entry) {
$use_url_kind = $this->get_config('tweetback_url','status');
switch ($use_url_kind) {
case 'profile':
$comment_url = $entry[TWITTER_SEARCHRESULT_URL_AUTOR];
break;
case 'weburl':
$api = new Twitter();
$user = $api->userinfo($entry[TWITTER_SEARCHRESULT_LOGIN]);
if (!empty($user) && !empty($user->url)) {
$comment_url = $user->url;
}
else {
$comment_url = $entry[TWITTER_SEARCHRESULT_URL_TWEET];
}
break;
default: // status
$comment_url = $entry[TWITTER_SEARCHRESULT_URL_TWEET];
}
return $comment_url;
}
function create_update_from_entry($entry, $announce_format, $checkUrlLen = false) {
global $serendipity;
$entryurl = $this->generate_article_url($entry);
$short_url = $this->default_shorturl($entryurl);
// Check for real URL length after twitter shortened it:
if ($checkUrlLen) {
$this->twitter_check_config();
if (substr($short_url,0,5) == 'https') {
$url_len = $this->get_config('twitter_config_https_len');
}
else {
$url_len = $this->get_config('twitter_config_http_len');
}
}
if (empty($url_len)) $url_len = strlen($short_url); // Fallback, if we were not able to detect it
$url_placeholder = "#";
for ($i=1; $i<$url_len-2;$i++) {
$url_placeholder .= $i;
}
$url_placeholder .= "#";
$author = !empty($entry['author'])?$entry['author']:$serendipity['serendipityRealname'];
$announce_format = str_replace(
array('#author#','#autor#','#link#'),
array($author,$author,$url_placeholder),
$announce_format);
$title = $entry['title'];
$tags_marker = '#tags#';
$announce_format_notags = trim(str_replace($tags_marker,'',$announce_format));
// Check for tags:
$tagsnotused = array();
if (serendipity_db_bool($this->get_config('announce_with_tags',false)) && !empty($serendipity['POST']['properties']) && !(empty($serendipity['POST']['properties']['freetag_tagList']) && empty($serendipity['POST']['properties']['microblogging_tagList'])) && strlen($announce_format_notags)<140) {
$taglist = '';
if (!empty($serendipity['POST']['properties']['microblogging_tagList'])) {
$taglist = $serendipity['POST']['properties']['microblogging_tagList'];
}
else if (!empty($serendipity['POST']['properties']['freetag_tagList'])) {
$taglist = $serendipity['POST']['properties']['freetag_tagList'];
}
if (!empty($taglist)) {
$tags = explode(',',$taglist);
foreach ($tags as $tag) {
$tag = trim($tag);
$tag = str_replace(" ","_",$tag); // make tags more twitter alike
$test = str_replace('#title#',$title,$announce_format_notags);
$len = strlen($title);
if (strlen($test) < 140) { // still capacity
$title = preg_replace('@(^|\s)(' . $tag . '($|\s))@i'," #$2",$title);
if (strlen($title) == $len) $tagsnotused[] = $tag;
}
else {
$tagsnotused[] = $tag;
}
}
}
}
$update = str_replace('#title#',$title,$announce_format);
// Fill up with not used tags
if (strstr($announce_format,$tags_marker)) {
$added = false;
foreach ($tagsnotused as $tag) {
$test = str_replace($tags_marker,'#' . $tag . ' ' . $tags_marker,$update);
if (strlen($test) <142 + strlen($tags_marker)) {
$update = $test;
$added = true;
}
}
// remove tags marker now
$update = trim(str_replace(array(' ' . $tags_marker,$tags_marker),'',$update));
}
// Now fill in shorturl, if needed
$update = str_replace($url_placeholder, $short_url, $update);
// Change encoding of update to UTF-8
if (LANG_CHARSET!='UTF-8' && function_exists("mb_convert_encoding")) {
$update = mb_convert_encoding($update, 'UTF-8', LANG_CHARSET);
}
return $update;
}
function twitter_check_config() {
$last_config_check = $this->get_config("twitter_last_config_check", 0);
$now = time();
$daybefore = $now - (24 * 60 * 60); // One day in seconds
if (empty($last_config_check) || $last_config_check<$daybefore) {
$api = new Twitter();
$config = $api->get_twitter_config();
if (!empty($config)) {
$max_http = $config->short_url_length;
$max_https = $config->short_url_length_https;
$this->set_config('twitter_last_config_check', $now);
$this->set_config('twitter_config_http_len', $max_http);
$this->set_config('twitter_config_https_len', $max_https);
}
}
}
function twitter_published_entry( $entry ) {
global $serendipity;
if (!serendipity_db_bool($this->get_config('announce_articles'))) return false;
// Do not announce future entries!
if (empty($entry['timestamp']) || time()<$entry['timestamp']) return false;
$announce_account_list = explode('^',$this->get_config('announce_via_accounts'));
if (count($announce_account_list)==0) return true;
$announce_format = trim($this->get_config('announce_format', $this->get_default_announceformat()));
$updates = array();
echo "
";
foreach ($announce_account_list as $announce_account) {
$suffix = empty($announce_account)?'':(int)$announce_account+1;
$account_name = $this->get_config('twittername'.$suffix,'');
$account_pwd = $this->get_config('twitterpwd'.$suffix,'');
$account_type = $this->get_config('id_service'.$suffix,'twitter');
// For twitter we need a special update generation
if (empty($updates[$account_type])) {
$update = $this->create_update_from_entry($entry, $announce_format, 'twitter'==$account_type);
$updates[$account_type] = $update;
}
else {
$update = $updates[$account_type];
}
// If the GeoTag Plugin is installed, it will hand over some geodata
$geo_lat = $serendipity['POST']['properties']['geo_lat'];
$geo_long = $serendipity['POST']['properties']['geo_long'];
$this->twitter_published_entry_to_account($suffix, $account_name, $account_pwd, $account_type, $update, $geo_lat, $geo_long);
}
echo "
';
}
}
// Display the history
include dirname(__FILE__) . '/tweeter/tweeter_history.inc.php';
$return = true;
}
}
// We generate a secret only known by the blog admit.
// We use the directory of this plugin md5'd
static function pluginSecret() {
return md5(dirname(__FILE__));
}
static function getTwitterOauths() {
$idcount = serendipity_event_twitter::get_config_event('id_count');
if (empty($idcount)) {
return array();
}
$twitter_oauths = array();
for ($idx=1; $idx<=$idcount; $idx++) {
$suffix = $idx==1?'':$idx;
$service = serendipity_event_twitter::get_config_event('id_service' . $suffix);
if ($service == "twitter") {
$oautKey = serendipity_event_twitter::get_config_event('twitteroa_consumer_secret' . $suffix);
if (!empty($oautKey)) {
$twitter_oauths[$idx] = serendipity_event_twitter::get_config_event('twittername' . $suffix);
}
}
}
return $twitter_oauths;
}
static function get_config_event($name, $defaultvalue = null) {
global $serendipity;
$db_event_search = "serendipity_event_twitter:%/" . $name;
$r = serendipity_db_query("SELECT value FROM {$serendipity['dbPrefix']}config WHERE name like '" . $db_event_search . "' LIMIT 1", true);
if (is_array($r)) {
return $r[0];
} else {
return $defaultvalue;
}
}
}