Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+191 -109
View File
@@ -33,13 +33,14 @@ class _Facebook extends PromoteAbstract
public $defaultSettings = array(
'token' => NULL,
'owner' => NULL,
'page_name' => NULL,
'page_name' => NULL, //
'permissions' => NULL,
'members' => NULL,
'page' => NULL,
'page' => NULL, //
'tags' => NULL,
'image' => NULL,
'last_sync' => 0
'last_sync' => 0,
'shareable' => array()
);
/**
@@ -62,7 +63,7 @@ class _Facebook extends PromoteAbstract
if ( ! $this->settings['image'] or $this->settings['last_sync'] < time() - 86400 )
{
/* Fetch again */
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/picture?type=large" )->request()->get();
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/picture?type=large&appsecret_proof=" . static::appSecretProof( $this->settings['token'] ) )->request()->get();
$extension = str_replace( 'image/', '', $response->httpHeaders['Content-Type'] );
$newFile = \IPS\File::create( 'core_Promote', 'facebook_' . $this->settings['page'] . '.' . $extension, (string) $response, NULL, FALSE, NULL, FALSE );
@@ -76,11 +77,23 @@ class _Facebook extends PromoteAbstract
/**
* Get name
*
* @param string|NULL $serviceId Specific page/group ID
* @return string
*/
public function getName()
public function getName( $serviceId=NULL )
{
return $this->settings['page_name'];
if ( $serviceId )
{
$realId = mb_substr( $serviceId, 2 );
if ( mb_substr( $serviceId, 0, 2 ) == 'p_' )
{
return isset( $this->settings['shareable']['pages'][ $realId ]['name'] ) ? $this->settings['shareable']['pages'][ $realId ]['name'] : \IPS\Member::loggedIn()->language()->addToStack( 'promote_service_name_missing' );
}
else
{
return isset( $this->settings['shareable']['groups'][ $realId ]['name'] ) ? $this->settings['shareable']['groups'][ $realId ]['name'] : \IPS\Member::loggedIn()->language()->addToStack( 'promote_service_name_missing' );
}
}
}
/**
@@ -90,65 +103,38 @@ class _Facebook extends PromoteAbstract
*/
public function canPostToPage()
{
try
{
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}/permissions" )->setQueryString( 'access_token', $this->member->fb_token )->request()->get()->decodeJson();
if ( isset( $response['error'] ) )
{
return $response['error']['message'];
}
if ( !empty( $response['data'] ) )
{
$got = 0;
foreach( $response['data'] as $permission )
{
if ( in_array( $permission['permission'], array( 'manage_pages', 'publish_pages' ) ) and $permission['status'] == 'granted' )
{
$got++;
}
}
$facebook = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth2\Facebook');
$authorizedScopes = $facebook->authorizedScopes( $this->member );
return $got === 2;
}
}
catch ( \IPS\Http\Request\Exception $e )
if( $authorizedScopes === NULL )
{
\IPS\Log::log( $e, 'facebook' );
return FALSE;
}
return NULL;
foreach( array( 'user_managed_groups', 'manage_pages', 'publish_pages', 'publish_actions' ) as $perm )
{
if ( ! in_array( $perm, $authorizedScopes ) )
{
return FALSE;
}
}
return TRUE;
}
/**
* Get pages this user manages
* Create the appSecretProof key
*
* @return array
* @param string $token The Token
* @return string
*/
public function getPages()
protected static function appSecretProof( $token )
{
$pages = array();
try
{
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}/accounts" )->setQueryString( 'access_token', $this->member->fb_token )->request()->get()->decodeJson();
if ( !empty( $response['data'] ) )
{
foreach( $response['data'] as $page )
{
$pages[ $page['id'] ] = array( $page['name'], $page['access_token'] );
}
}
}
catch ( \IPS\Http\Request\Exception $e )
{
\IPS\Log::log( $e, 'facebook' );
}
return $pages;
$facebook = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth2\Facebook');
return hash_hmac( 'sha256', $token, $facebook->settings['client_secret'] );
}
/**
* Get form elements for this share service
*
@@ -160,33 +146,63 @@ class _Facebook extends PromoteAbstract
*/
public function form( $text, $link=null, $content=null )
{
$shareable = array();
$_text = '';
if ( $this->promote and $this->promote->id )
{
$_text = $text;
if ( isset( $this->promote->form_data['facebook'] ) AND $settings = $this->promote->form_data['facebook'] )
{
$shareable = $settings['shareable'];
}
}
else
{
$_text = $text;
if ( $content )
{
$_text .= "\n" . $content;
}
if ( $link )
{
$_text .= "\n" . $link;
}
if ( count( $this->settings['tags'] ) )
{
$_text .= "\n" . '#' . implode( ' #', $this->settings['tags'] );
$_text .= '#' . implode( ' #', $this->settings['tags'] );
}
}
return array( new \IPS\Helpers\Form\TextArea( 'promote_social_content_facebook', $_text, FALSE, array( 'maxLength' => 2000, 'rows' => 10 ) ) );
$names = array();
$options = array();
if ( isset( $this->settings['shareable'] ) )
{
foreach( $this->settings['shareable']['pages'] as $id => $data )
{
$options[ 'p_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_page_prefix', NULL, array( 'sprintf' => array( $data['name'] ) ) );
$names[] = $data['name'];
}
foreach( $this->settings['shareable']['groups'] as $id => $data )
{
$options[ 'g_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_group_prefix', NULL, array( 'sprintf' => array( $data['name'] ) ) );
$names[] = $data['name'];
}
}
return array(
new \IPS\Helpers\Form\TextArea( 'promote_social_content_facebook', $_text, FALSE, array( 'maxLength' => 2000, 'rows' => 10 ) ),
new \IPS\Helpers\Form\CheckboxSet( 'promote_facebook_shareable', $shareable, FALSE, array( 'options' => $options ) )
);
}
/**
* Allow for any extra processing
*
* @param array $values Values from the form isn't it though
* @return mixed
*/
public function processPromoteForm( $values )
{
if ( isset( $values['promote_facebook_shareable'] ) )
{
return array( 'shareable' => $values['promote_facebook_shareable'] );
}
return NULL;
}
/**
@@ -198,30 +214,71 @@ class _Facebook extends PromoteAbstract
public function post( $promote )
{
$photos = $promote->imageObjects();
/* Get the last 20 items to see if we have a duplicate */
$items = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/feed" )->setQueryString( array( 'limit' => 20, 'access_token' => $this->settings['token'] ) )->request()->get()->decodeJson();
if ( isset( $items['data'] ) )
$settings = $promote->form_data['facebook'];
$shareable = $this->settings['shareable'];
$responses = array();
foreach( $settings['shareable'] as $id )
{
foreach( $items['data'] as $item )
$feedId = mb_substr( $id, 2 );
if ( mb_substr( $id, 0, 2 ) == 'p_' )
{
$time = new \IPS\DateTime( $item['created_time'] );
$now = new \IPS\DateTime();
/* Only look back past the last 30 mins */
if ( intval( $now->diff( $time )->format('%i') ) > 30 )
$responses[ $id ][] = $this->_post( $promote, $photos, $feedId, $shareable['pages'][ $feedId ]['token'] );
}
else
{
$responses[ $id ][] = $this->_post( $promote, $photos, $feedId, $this->settings['token'] );
}
}
return $responses;
}
/**
* Actually post the post, but not post post as that would be too late
*
* @param \IPS\core\Promote $promote Promote Object
* @param array $photos The photos
* @param int $feedId The feed ID to post to. Honestly, it's not rocket science.
* @param string $token I'll give you one guess. Does anyone actually read this?
* @return stuff
*/
protected function _post( $promote, $photos, $feedId, $token=NULL )
{
/* Get the last 20 items to see if we have a duplicate */
try
{
$items = \IPS\Http\Url::external( "https://graph.facebook.com/" . $feedId . "/feed" )->setQueryString( $this->_addToken( array(
'limit' => 20
), $token ) )->request()->get()->decodeJson();
if ( isset( $items['data'] ) )
{
foreach( $items['data'] as $item )
{
continue;
}
if ( preg_replace( '#\s{1,}#', " ", $promote->text['facebook'] ) == preg_replace( '#\s{1,}#', " ", $item['message'] ) )
{
/* Duplicate */
return $item['id'];
$time = new \IPS\DateTime( ( isset( $item['created_time'] ) ? $item['created_time'] : $item['updated_time' ] ) );
$now = new \IPS\DateTime();
/* Only look back past the last 30 mins */
if ( intval( $now->diff( $time )->format('%i') ) > 30 )
{
continue;
}
if ( preg_replace( '#\s{1,}#', " ", $promote->text['facebook'] ) == preg_replace( '#\s{1,}#', " ", $item['message'] ) )
{
/* Duplicate */
return $item['id'];
}
}
}
}
catch( \Exception $e )
{
\IPS\Log::log( $e, 'facebook' );
}
if ( ! $photos or count( $photos ) === 1 )
{
@@ -231,32 +288,30 @@ class _Facebook extends PromoteAbstract
if ( $photos )
{
$thePhoto = array_pop( $photos );
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/photos" )->request()->post( array(
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $feedId . "/photos" )->request()->post( $this->_addToken( array(
'message' => $promote->text['facebook'],
'url' => (string) $this->returnUrlWithProtocol( $thePhoto->url ),
'access_token' => $this->settings['token'],
'link' => $promote->short_link
) )->decodeJson();
), $token ) )->decodeJson();
}
else
{
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/feed" )->request()->post( array(
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $feedId . "/feed" )->request()->post( $this->_addToken( array(
'message' => $promote->text['facebook'],
'access_token' => $this->settings['token'],
'link' => $promote->short_link
) )->decodeJson();
), $token ) )->decodeJson();
}
}
catch( \Exception $e )
{
\IPS\Log::log( $e, 'facebook' );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
}
if ( isset( $this->response['id'] ) )
if ( isset( $response['id'] ) )
{
return $this->response['id'];
return $response['id'];
}
else
{
@@ -268,48 +323,75 @@ class _Facebook extends PromoteAbstract
/* We have multiple photos so we first need to create an album, and then upload into that */
try
{
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/albums" )->request()->post( array(
'name' => $promote->objectTitle,
'access_token' => $this->settings['token'],
) )->decodeJson();
/* The item may be a comment, which uses a language string for objectTitle - make sure that is parsed before sending */
$title = $promote->objectTitle;
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $title );
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $feedId . "/albums" )->request()->post( $this->_addToken( array(
'name' => $title,
), $token ) )->decodeJson();
if ( ! isset( $this->response['id'] ) )
if ( ! isset( $response['id'] ) )
{
throw new \InvalidArgumentException('Could not create album');
}
$newAlbumId = $this->response['id'];
$newAlbumId = $response['id'];
foreach( $photos as $photo )
{
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $newAlbumId . "/photos" )->request()->post( array(
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $newAlbumId . "/photos" )->request()->post( $this->_addToken( array(
'message' => $promote->text['facebook'],
'url' => (string) $this->returnUrlWithProtocol( $photo->url ),
'access_token' => $this->settings['token'],
) )->decodeJson();
), $token ) )->decodeJson();
}
}
catch( \Exception $e )
{
\IPS\Log::log( $e, 'facebook' );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->get('facebook_publish_exception') );
}
return $this->response['id'];
if ( isset( $response['id'] ) )
{
return $response['id'];
}
else
{
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
}
}
}
/**
* Adds 'access_token' to query string if token defined (it is for pages, but not for groups
*
* @param array $values The values of the array
* @param string|NULL $token The token obvs
* @return array
*/
protected function _addToken( $values, $token )
{
return ( $token ) ? array_merge( $values, array( 'access_token' => $token, 'appsecret_proof' => static::appSecretProof( $token ) ) ) : $values;
}
/**
* Return the published URL
*
* @param array $data Data returned from a successful POST
* @return \IPS\Http\Url
* @throws InvalidArgumentException
* @throws \InvalidArgumentException
*/
public function getUrl( $data )
{
if ( is_array( $data ) and count( $data ) )
{
/* Get a random URL from the many it was shared to */
$array = $data[ array_rand( $data ) ];
$data = array_pop( $array );
}
if ( $data and preg_match( '#^[0-9_]*$#', $data ) )
{
return \IPS\Http\Url::external( 'https://facebook.com/' . $data );
+31 -3
View File
@@ -45,9 +45,10 @@ class _Internal extends PromoteAbstract
/**
* Get name
*
* @param string|NULL $serviceId Specific page/group ID
* @return string
*/
public function getName()
public function getName( $serviceId=NULL )
{
return \IPS\Settings::i()->board_name;
}
@@ -63,10 +64,37 @@ class _Internal extends PromoteAbstract
*/
public function form( $text, $link=null, $content=null )
{
return array( new \IPS\Helpers\Form\TextArea( 'promote_social_content_internal', $content ?: $text, FALSE, array( 'maxLength' => 3000, 'rows' => 6 ) ) );
$title = NULL;
if ( $this->promote and $this->promote->id )
{
if ( isset( $this->promote->form_data['internal'] ) AND $settings = $this->promote->form_data['internal'] )
{
$title = $settings['title'];
}
}
return array(
new \IPS\Helpers\Form\Text( 'promote_social_title_internal', $title ),
new \IPS\Helpers\Form\TextArea( 'promote_social_content_internal', $content ?: $text, FALSE, array( 'maxLength' => 3000, 'rows' => 6 ) )
);
}
/**
* Allow for any extra processing
*
* @param array $values Values from the form isn't it though
* @return mixed
*/
public function processPromoteForm( $values )
{
if ( isset( $values['promote_social_title_internal'] ) )
{
return array( 'title' => $values['promote_social_title_internal'] );
}
return NULL;
}
/**
* Post to internal
*
+15 -3
View File
@@ -77,7 +77,7 @@ abstract class _PromoteAbstract extends \IPS\Patterns\ActiveRecord
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
{
/* Initiate an object */
$classname = '\\IPS\Content\\Promote\\' . \ucfirst( $data['sharer_key'] );
$classname = '\\IPS\Content\\Promote\\' . mb_ucfirst( $data['sharer_key'] );
if ( !class_exists( $classname ) )
{
@@ -162,9 +162,10 @@ abstract class _PromoteAbstract extends \IPS\Patterns\ActiveRecord
/**
* Get name
*
* @param string|NULL $serviceId Specific page/group ID
* @return string
*/
abstract public function getName();
abstract public function getName( $serviceId=NULL );
/**
* Return the published URL
@@ -174,7 +175,18 @@ abstract class _PromoteAbstract extends \IPS\Patterns\ActiveRecord
* @throws InvalidArgumentException
*/
abstract function getUrl( $data );
/**
* Allow for any extra processing
*
* @param array $values Values from the form isn't it though
* @return mixed
*/
public function processPromoteForm( $values )
{
return NULL;
}
/**
* Bulk save settings
*
+21 -20
View File
@@ -58,7 +58,7 @@ class _Twitter extends PromoteAbstract
{
if ( static::$twitter === NULL )
{
static::$twitter = \IPS\Login::getHandler('Twitter');
static::$twitter = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth1\Twitter');
}
return static::$twitter;
@@ -90,9 +90,10 @@ class _Twitter extends PromoteAbstract
/**
* Get name
*
* @param string|NULL $serviceId Specific page/group ID
* @return string
*/
public function getName()
public function getName( $serviceId=NULL )
{
return $this->settings['name'];
}
@@ -104,15 +105,8 @@ class _Twitter extends PromoteAbstract
*/
public function canPostToPage()
{
try
{
$this->twitter()->sendRequest( 'get', 'https://api.twitter.com/1.1/account/verify_credentials.json', array(), $this->settings['token'], $this->settings['secret'] )->decodeJson();
return TRUE;
}
catch ( \IPS\Http\Request\Exception $e ) { }
return FALSE;
$twitter = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth1\Twitter');
return $twitter->hasWritePermissions( $this->settings['token'], $this->settings['secret'] );
}
@@ -134,7 +128,7 @@ class _Twitter extends PromoteAbstract
$textToActuallyUse .= ' ' . $link;
}
if ( mb_strlen( $textToActuallyUse ) < 140 )
if ( mb_strlen( $textToActuallyUse ) < 280 )
{
/* Got any tags to add? */
if ( count( $this->settings['tags'] ) )
@@ -142,7 +136,7 @@ class _Twitter extends PromoteAbstract
if ( $this->settings['tags_method'] === 'trim' )
{
$urlLength = mb_strlen( $link ) + 2; // spaces either side
$left = 140 - $urlLength;
$left = 280 - $urlLength;
$tagLength = 0;
$tagString = '';
$useText = $text;
@@ -168,7 +162,7 @@ class _Twitter extends PromoteAbstract
{
$considerAddingThis = ' #' . $tag;
if ( mb_strlen( $textToActuallyUse . $considerAddingThis ) < 140 )
if ( mb_strlen( $textToActuallyUse . $considerAddingThis ) < 280 )
{
$textToActuallyUse .= $considerAddingThis;
}
@@ -185,7 +179,7 @@ class _Twitter extends PromoteAbstract
$return[] = \IPS\Theme::i()->getTemplate( 'promote' )->promoteDialogTwitterDuplicate();
}
$return[] = new \IPS\Helpers\Form\TextArea( 'promote_social_content_twitter', $textToActuallyUse, FALSE, array( 'maxLength' => 300, 'rows' => 3 ) );
$return[] = new \IPS\Helpers\Form\TextArea( 'promote_social_content_twitter', $textToActuallyUse, FALSE, array( 'maxLength' => 600, 'rows' => 3 ) );
return $return;
}
@@ -211,8 +205,8 @@ class _Twitter extends PromoteAbstract
/* Twitter can only have a max of 4 images */
if ( $done < 4 )
{
$this->response = $this->twitter()->sendRequest( 'post', 'https://upload.twitter.com/1.1/media/upload.json', array( 'media' => $photo->contents() ), $this->settings['token'], $this->settings['secret'] )->decodeJson();
$this->response = $this->twitter()->sendMedia( $photo->contents(), $this->settings['token'], $this->settings['secret'] );
if ( isset( $this->response['media_id_string'] ) )
{
$mediaIds[] = $this->response['media_id_string'];
@@ -241,7 +235,7 @@ class _Twitter extends PromoteAbstract
$send['media_ids'] = implode( ',', $mediaIds );
}
$this->response = $this->twitter()->sendRequest( 'post', "https://api.twitter.com/1.1/statuses/update.json", $send, $this->settings['token'], $this->settings['secret'] )->decodeJson();
$this->response = $this->twitter()->sendStatus( $send, $this->settings['token'], $this->settings['secret'] );
if ( isset( $this->response['id_str'] ) )
{
@@ -249,6 +243,13 @@ class _Twitter extends PromoteAbstract
}
else
{
/* Check for specific errors */
if ( isset( $this->response['error'] ) )
{
\IPS\Log::log( $this->response['error'], 'twitter' );
throw new \InvalidArgumentException( $this->response['error'] );
}
/* Check for non critical errors we don't need to flag as complete failures */
if ( isset( $this->response['errors'] ) )
{
@@ -272,14 +273,14 @@ class _Twitter extends PromoteAbstract
}
}
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_exception') );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->get('twitter_publish_exception') );
}
}
catch ( \Exception $e )
{
\IPS\Log::log( $e->getMessage(), 'twitter' );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_exception') );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->get('twitter_publish_exception') );
}
}