Seditio Source
Root |
./othercms/ips_4.3.4/applications/core/sources/Promote/Promote.php
<?php
/**
 * @brief        Promote model
 * @author        <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright    (c) Invision Power Services, Inc.
 * @license        https://www.invisioncommunity.com/legal/standards/
 * @package        Invision Community
 * @since        16 Feb 2017
 */

namespace IPS\core;

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
   
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
    exit;
}

/**
 * @brief Promote Model
 */
class _Promote extends \IPS\Patterns\ActiveRecord
{
   
/**
     * @brief    Multiton Store
     */
   
protected static $multitons = array();
   
   
/**
     * @brief    [ActiveRecord] Database Table
     */
   
public static $databaseTable = 'core_social_promote';
   
   
/**
     * @brief    [ActiveRecord] ID Database Column
     */
   
public static $databaseColumnId = 'id';
   
   
/**
     * @brief    Database Prefix
     */
   
public static $databasePrefix = 'promote_';
   
   
/**
     * @brief    Class object
     */
   
protected $object = NULL;
   
   
/**
     * @brief    Author object
     */
   
protected $author = NULL;
   
   
/**
     * @brief    Sent data
     */
   
protected $history = array();
   
   
/**
     * @brief    Promoter objects
     */
   
protected static $promoters = NULL;
   
   
/**
     * Set Default Values
     *
     * @return    void
     */
   
public function setDefaultValues()
    {
       
/* Ensure TEXT fields are never NULL */
       
$this->_data['text'] = array();
       
$this->_data['short_link'] = '';
       
$this->_data['images'] = array();
       
$this->_data['media'] = array();
       
$this->_data['share_to'] = array();
       
$this->_data['returned'] = array();
    }

   
/**
     * Set the "text" field
     *
     * @param string|array $value
     * @return void
     */
   
public function set_text( $value )
    {
       
$this->_data['text'] = ( is_array( $value ) ? json_encode( $value ) : $value );
    }
   
   
/**
     * Get the "text" field
     *
     * @return array
     */
   
public function get_text()
    {
        return
json_decode( $this->_data['text'], TRUE );
    }
   
   
/**
     * Set the "attach_ids" field
     *
     * @param string|array $value
     * @return void
     */
   
public function set_images( $value )
    {
       
$this->_data['images'] = ( is_array( $value ) ? json_encode( $value ) : $value );
    }
   
   
/**
     * Get the "attach_ids" field
     *
     * @return array
     */
   
public function get_images()
    {
        return
$this->_data['images'] ? json_decode( $this->_data['images'], TRUE ) : array();
    }
   
   
/**
     * Set the "media" field
     *
     * @param string|array $value
     * @return void
     */
   
public function set_media( $value )
    {
       
$this->_data['media'] = ( is_array( $value ) ? json_encode( $value ) : $value );
    }
   
   
/**
     * Get the "media" field
     *
     * @return array
     */
   
public function get_media()
    {
        return
$this->_data['media'] ? json_decode( $this->_data['media'], TRUE ) : array();
    }
   
   
/**
     * Set the "share_to" field
     *
     * @param string|array $value
     * @return void
     */
   
public function set_share_to( $value )
    {
       
$this->_data['share_to'] = ( is_array( $value ) ? json_encode( $value ) : $value );
    }
   
   
/**
     * Get the "share_to" field
     *
     * @return array
     */
   
public function get_share_to()
    {
        return
$this->_data['share_to'] ? json_decode( $this->_data['share_to'], TRUE ) : array();
    }
   
   
/**
     * Set the "returned" field
     *
     * @param string|array $value
     * @return void
     */
   
public function set_returned( $value )
    {
       
$this->_data['returned'] = ( is_array( $value ) ? json_encode( $value ) : $value );
    }
   
   
/**
     * Get the "returned" field
     *
     * @return array
     */
   
public function get_returned()
    {
        return
$this->_data['returned'] ? json_decode( $this->_data['returned'], TRUE ) : array();
    }
   
   
/**
     * Set the "form_data" field
     *
     * @param string|array $value
     * @return void
     */
   
public function set_form_data( $value )
    {
       
$this->_data['form_data'] = ( is_array( $value ) ? json_encode( $value ) : $value );
    }
   
   
/**
     * Get the "form_data" field
     *
     * @return array
     */
   
public function get_form_data()
    {
        return
$this->_data['form_data'] ? json_decode( $this->_data['form_data'], TRUE ) : array();
    }
   
   
/**
     * The author object
     *
     * @return \IPS\Member
     */
   
public function author()
    {
        if (
$this->author === NULL )
        {
            try
            {
               
$this->author = \IPS\Member::load( $this->added_by );
            }
            catch ( \
Exception $e )
            {
               
$this->author = new \IPS\Member;
            }
        }
       
        return
$this->author;
    }
   
   
/**
     * The content object
     *
     * @return \IPS\Content
     */
   
public function object()
    {
        if (
$this->object === NULL )
        {
           
$class = $this->class;
           
$this->object = $class::load( $this->class_id );
        }
       
        return
$this->object;
    }
   
   
/**
     * Get Our Picks title
     *
     * @return string
     */
   
public function get_ourPicksTitle()
    {
       
$settings = $this->form_data;
       
        if ( ! empty(
$settings['internal']['title'] ) )
        {
            return
$settings['internal']['title'];
        }
       
        return
$this->objectTitle;
    }
   
   
/**
     * Get the object title
     *
     * @return string
     */
   
public function get_objectTitle()
    {
        return static::
objectTitle( $this->object() );
    }
   
   
/**
     * Get the object date posted
     *
     * @return \IPS\DateTime|NULL
     */
   
public function get_objectDatePosted()
    {
       
$object = $this->object();
       
        if (
$object instanceof \IPS\Content )
        {
            if ( isset(
$object::$databaseColumnMap['date'] ) )
            {
                return \
IPS\DateTime::ts( $object->mapped('date') );
            }
           
           
/* Valid object, but there isn't any date data available */
           
return NULL;
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
           
/* Valid object, but there isn't any date data available */
           
return NULL;
        }
       
        throw new \
OutofRangeException('object_not_valid');
    }
   
   
/**
     * Get the object author
     *
     * @return \IPS\Member|NULL
     */
   
public function get_objectAuthor()
    {
       
$object = $this->object();
       
        if (
$object instanceof \IPS\Content )
        {
            return
$object->author();
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
            try
            {
                return
$object->owner();
            }
            catch( \
Exception $e )
            {
                return
NULL;
            }
        }
       
        throw new \
OutofRangeException('object_not_valid');
    }
   
   
/**
     * Get the object unread status
     *
     * @return bool|null
     */
   
public function get_objectIsUnread()
    {
       
$object = $this->object();
       
        if (
$object instanceof \IPS\Content\Item )
        {
            return
$object->unread();
        }
        else if (
$object instanceof \IPS\Content\Comment )
        {
            return
$object->item()->unread();
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
            if (
$object::$contentItemClass )
            {
               
$contentItemClass = $object::$contentItemClass;
               
                return
$contentItemClass::containerUnread( $object );
            }
           
            return
NULL;
        }
       
        throw new \
OutofRangeException('object_not_valid');
    }

   
/**
     * Get the number and indefinite article for replies/children where applicable
     *
     * @return array|null
     */
   
public function get_objectDataCount()
    {
        return
$this->objectDataCount( NULL );
    }

   
/**
     * Get the number and indefinite article for replies/children where applicable
     *
     * @param    \IPS\Lang|null    $language    Language to use (or NULL for currently logged in member's language)
     * @return    array|null
     */
   
public function objectDataCount( $language=NULL )
    {
       
$language    = $language ?: \IPS\Member::loggedIn()->language();
       
$object        = $this->object();
       
        if (
$object instanceof \IPS\Content\Item )
        {
            try
            {
               
$container = $object->container();
            }
            catch( \
Exception $e ){}

            if (
$object::supportsComments( NULL, $container ) )
            {
               
$count = $object->mapped('num_comments');

                if (
$count AND isset( $object::$firstCommentRequired ) AND $object::$firstCommentRequired === TRUE )
                {
                   
$count--;
                }
               
                return array(
'count' => $count, 'words' => $language->addToStack( 'num_replies', NULL, array( 'pluralize' => array( $count ) ) ) );
            }

            if (
$object::supportsReviews( NULL, $container ) )
            {
               
$count = $object->mapped('num_reviews');

                return array(
'count' => $count, 'words' => $language->addToStack( 'num_reviews', NULL, array( 'pluralize' => array( $count ) ) ) );
            }
           
           
/* Valid object, but there isn't any date data available */
           
return NULL;
        }
        else if (
$object instanceof \IPS\Content\Comment )
        {
            return
NULL;
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
            if(
$object->_items !== NULL )
            {
               
$count = $object->_items;

                return array(
'count' => $count, 'words' => $language->addToStack( $object->_countLanguageString, NULL, array( 'pluralize' => array( $count ) ) ) );
            }

            return
NULL;
        }
       
        throw new \
OutofRangeException('object_not_valid');
    }
   
   
/**
     * Returns "Foo posted {{indefart}} in {{container}}, {{date}}
     *
     * @return    string
     */
   
public function get_objectMetaDescription()
    {
        return
$this->objectMetaDescription( NULL );
    }
   
   
/**
     * Returns "Foo posted {{indefart}} in {{container}}, {{date}}
     *
     * @param    \IPS\Lang|NULL    $plaintextLanguage    If specified, will return plaintext (not linking the user or the container in the language specified). If NULL, returns with links based on logged in user's theme and language
     * @return    string
     */
   
public function objectMetaDescription( $plaintextLanguage=NULL )
    {
       
$object = $this->object();
       
$author = $this->objectAuthor;
               
        if (
$object instanceof \IPS\Content\Item )
        {
           
$container = $object->containerWrapper();

            if (
$container )
            {
                if ( !
$plaintextLanguage )
                {
                    return \
IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_container', NULL, array(
                       
'htmlsprintf'    => array( $author->link(), $this->objectDatePosted->html( FALSE ) ),
                       
'sprintf'        => array( $object->indefiniteArticle(), $container->url(), $container->_title ),
                    ) );
                }
                else
                {
                    return
$plaintextLanguage->addToStack( 'promote_metadescription_container_nolink', NULL, array(
                       
'sprintf'        => array( $object->indefiniteArticle( $plaintextLanguage ), $container->getTitleForLanguage( $plaintextLanguage ), $author->name, $this->objectDatePosted->relative( \IPS\DateTime::RELATIVE_FORMAT_NORMAL, $plaintextLanguage ) ),
                    ) );
                }
            }
            else
            {
                if ( !
$plaintextLanguage )
                {
                    return \
IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', NULL, array(
                       
'htmlsprintf'    => array( $author->link(), $this->objectDatePosted->html( FALSE ) ),
                       
'sprintf'        => array( $object->indefiniteArticle() )
                    ) );
                }
                else
                {
                    return
$plaintextLanguage->addToStack( 'promote_metadescription_nocontainer', NULL, array(
                       
'sprintf'        => array( $object->indefiniteArticle( $plaintextLanguage ), $author->name, $this->objectDatePosted->relative( \IPS\DateTime::RELATIVE_FORMAT_NORMAL, $plaintextLanguage ) )
                    ) );
                }
            }
        }
        else if (
$object instanceof \IPS\Content\Comment )
        {
            if ( !
$plaintextLanguage )
            {
                return \
IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', NULL, array(
                   
'htmlsprintf'    => array( $author->link(), $this->objectDatePosted->html( FALSE ) ),
                   
'sprintf'        => array( $object->indefiniteArticle() )
                ) );
            }
            else
            {
                return
$plaintextLanguage->addToStack( 'promote_metadescription_nocontainer', NULL, array(
                   
'sprintf'        => array( $object->indefiniteArticle( $plaintextLanguage ), $author->name, $this->objectDatePosted->relative( \IPS\DateTime::RELATIVE_FORMAT_NORMAL, $plaintextLanguage ) )
                ) );
            }
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
            if ( !
$plaintextLanguage )
            {
                return \
IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_node', NULL, array(
                   
'htmlsprintf'    => array( $author->link() ),
                   
'sprintf'        => array( $object->url(), $object->_title )
                ) );
            }
            else
            {
                return
$plaintextLanguage->addToStack( 'promote_metadescription_node_nolink', NULL, array(
                   
'sprintf'        => array( $object->getTitleForLanguage( $plaintextLanguage ), $author->name )
                ) );
            }
        }
       
        throw new \
OutofRangeException('object_not_valid');
    }
   
   
/**
     * Get reactions class for this object
     *
     * @return array|null
     */
   
public function get_objectReactionClass()
    {
       
$object = $this->object();
       
$class = NULL;
       
        if ( ! \
IPS\Settings::i()->reputation_enabled )
        {
            return
NULL;
        }
       
        if (
$object instanceof \IPS\Content\Item )
        {
           
/* The first post has the reactions for this item */
           
if ( $object::$firstCommentRequired )
            {
                try
                {
                   
$class = $object->comments( 1, NULL, 'date', 'asc' );
                }
                catch( \
Exception $e )
                {
                   
$class =  NULL;
                }
            }
            else
            {
               
$class = $object;
            }
        }
        else if (
$object instanceof \IPS\Content\Comment )
        {
           
$class = $object;
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
            return
NULL;
        }
       
        return (
$class and \IPS\IPS::classUsesTrait( $class, 'IPS\Content\Reactable' ) ) ? $class : NULL;
    }
   
   
/**
     * Send to networks now
     *
     * @return void
     */
   
public function send()
    {
       
/* Race condition possible, so flag as sent now */
       
$this->sent = time();
       
$this->save();
       
$returned = $this->returned;
       
$time = time();
       
$hasFailed = false;
       
       
/* Item hidden? Unhide it now */
       
if ( $this->object()->hidden() !== 0 )
        {
           
$this->object()->unhide( FALSE );
        }
       
        foreach(
$this->share_to as $service )
        {
            if (
$this->failed )
            {
               
/* It failed, but some services may have sent */
               
if ( ! $this->serviceFailed( $service ) )
                {
                   
/* This service did sent, so skip */
                   
continue;
                }
            }

           
$response = array(
               
'response_promote_id' => $this->id,
               
'response_promote_key' => $service,
               
'response_date' => time(),
               
'response_sent_date' => $time
           
);
       
            try
            {
               
$serviceObject = $this->getPromoter( $service )->setMember( \IPS\Member::load( $this->added_by ) );
               
               
/* Successful post ID returned */
               
$returned[ $service ] = $serviceObject->post( $this );
            }
            catch( \
Exception $ex )
            {
               
$hasFailed = true;
               
$this->scheduled = time() + 600;
               
$this->sent = 0;
               
               
$response['response_failed'] = 1;
            }
           
           
/* Full response stored */
           
if ( isset( $returned[ $service ] ) AND is_array( $returned[ $service ] ) )
            {
                foreach(
$returned[ $service ] as $id => $data )
                {
                   
$response['response_json'] = json_encode( $data, TRUE );
                   
$response['response_service_id'] = $id;
                   
                    \
IPS\Db::i()->insert( 'core_social_promote_content', $response );
                }
            }
            else
            {
               
$response['response_json'] = json_encode( $serviceObject->response, TRUE );
               
                \
IPS\Db::i()->insert( 'core_social_promote_content', $response );
            }
        }

       
$this->failed = ( $hasFailed ) ? $this->failed + 1 : 0;
       
$this->returned = $returned;
       
$this->save();
    }
   
   
/**
     * Save Changed Columns
     *
     * @return    void
     */
   
public function save()
    {
       
parent::save();
       
       
/* Enable the task again */
       
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 'promote' ) );
    }
   
   
/**
     * Return an array of File objects
     *
     * @return array|null
     */
   
public function imageObjects()
    {
       
$photos = array();
        if (
count( $this->images ) )
        {
            foreach(
$this->images as $image )
            {
                foreach(
$image as $ext => $url )
                {
                   
$photos[] = \IPS\File::get( $ext, $url );
                }
            }
        }
       
        if (
count( $this->media ) )
        {
            foreach(
$this->media as $media )
            {
               
$photos[] = \IPS\File::get( 'core_Promote', $media );
            }
        }
       
        return (
count( $photos ) ) ? $photos : NULL;
    }
   
   
/**
     * Look for a specific image
     *
     * @param    string    $path        Image path monthly_x_x/foo.gif
     * @param    string    $extension    Storage extension
     * @return boolean
     */
   
public function hasImage( $path, $extension='core_Attachment' )
    {
        foreach(
$this->images as $image )
        {
            foreach(
$image as $ext => $url )
            {
                if (
$ext == $extension and $path == $url )
                {
                    return
TRUE;
                }
            }
        }
       
        return
FALSE;
    }
   
   
/**
     * Returns a \IPS\DateTime object for the scheduled timestamp
     *
     * @return \IPS\DateTime
     */
   
public function scheduledDateTime()
    {
       
$timezone = new \DateTimeZone( \IPS\Settings::i()->promote_tz );
        return \
IPS\DateTime::ts( $this->scheduled )->setTimezone( $timezone );
    }
   
   
/**
     * Returns a \IPS\DateTime object for the sent timestamp
     *
     * @return \IPS\DateTime
     */
   
public function sentDateTime()
    {
       
$timezone = new \DateTimeZone( \IPS\Settings::i()->promote_tz );
        return \
IPS\DateTime::ts( $this->sent )->setTimezone( $timezone );
    }
   
   
/**
     * Shorten URL.
     *
     * @param    string        $service    Service key, (such as facebook or twitter)
     * @returns    boolean
     */
   
public function serviceFailed( $service )
    {
        if ( !
$this->failed )
        {
            return
FALSE;
        }
       
       
$returned = $this->returned;
       
        return isset(
$returned[ $service ] ) ? FALSE : TRUE;
    }
   
   
/**
     * Returns text sent to a named service
     *
     * @param    string        $service        Service key (twitter, facebook)
     * @param    boolean        $forDisplay        Is this for display in output?
     * @return  string|NULL
     */
   
public function getText( $service, $forDisplay=false )
    {
        if (
in_array( $service, $this->share_to ) )
        {
           
$text = $this->text;
           
            return isset(
$text[ $service ] ) ? ( $forDisplay ? nl2br( htmlspecialchars( $text[ $service ], ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) ) : $text[ $service ] ) : NULL;
        }
       
        return
NULL;
    }
   
   
/**
     * Sets text for a named service
     *
     * @param    string        $service        Service key (twitter, facebook)
     * @param    boolean        $text            Text to save
     * @return  string|NULL
     */
   
public function setText( $service, $text )
    {
       
$allText = $this->text;
       
$allText[ $service ] = $text;
       
$this->text = $allText;
    }
   
   
/**
     * Return the published URL for this post
     *
     * @param    string        $service    Service key, (such as facebook or twitter)
     * @returns    string|NULL
     */
   
public function getPublishedUrl( $service )
    {
       
$returned = $this->returned;
       
        if ( isset(
$returned[ $service ] ) )
        {
            try
            {
                if (
$url = static::getPromoter( $service )->getUrl( $returned[ $service ] ) )
                {
                    return
$url;
                }
               
                return
$this->object()->url();
            }
            catch ( \
InvalidArgumentException $e )
            {
                return
NULL;
            }
        }
    }

   
/**
     * Attempt to get all responses for this ID
     *
     * @param     string        $service    Service to return (twitter, facebook, etc)
     * @return    array|NULL        array( response_id => data )
     */
   
public function responses( $service )
    {
       
$responses = array();
        try
        {
            foreach( \
IPS\Db::i()->select( '*', 'core_social_promote_content', array( 'response_promote_id=? and response_promote_key=?', $this->id, $service ) ) as $row )
            {
               
$responses[ $row['response_id'] ] = json_decode( $row['response_json'], TRUE );
            }
        }
        catch( \
Exception $e )
        {
            return
NULL;
        }
       
        return
count( $responses ) ? $responses : NULL;
    }
   
   
/**
     * Fetch successful sent history for this promoted item
     *
     * @return    array|NULL        array( timestamp => array( service => response_time, ... )
     */
   
public function history()
    {
        if ( ! isset(
$this->history[ $this->id ] ) )
        {
           
$this->history[ $this->id ] = array();
           
            foreach( \
IPS\Db::i()->select( '*', 'core_social_promote_content', array( 'response_promote_id=? and response_failed=0', $this->id ) ) as $row )
            {
               
$this->history[ $this->id ][ $row['response_sent_date'] ][ $row['response_promote_key'] ][] = $row;
            }
        }
       
        return
count( $this->history[ $this->id ] ) ? $this->history[ $this->id ] : NULL;
    }
   
   
/**
     * [ActiveRecord] Delete Record
     *
     * @return    void
     */
   
public function delete()
    {
        try
        {
            \
IPS\Db::i()->delete( 'core_social_promote_content', array( 'response_promote_id=?', $this->id ) );
        }
        catch( \
Exception $e ) { }
       
        return
parent::delete();
    }
   
   
/**
     * Promote stream of internally promoted items
     *
     * @param    int|array    $limit            Number of items to fetch
     * @param    string        $sortField        Sort by field
     * @param    string        $sortDirection    Sort by direction (asc, desc)
     * @return  array
     */
   
public static function internalStream( $limit=10, $sortField='promote_sent', $sortDirection='desc')
    {
       
$items = array();
       
        foreach( \
IPS\Db::i()->select( '*', 'core_social_promote', array( 'promote_sent > 0 and promote_internal=1' ), $sortField . ' ' . $sortDirection, $limit ) as $row )
        {
           
$items[ $row['promote_id'] ] = static::constructFromData( $row );
        }
       
        return
$items;
    }
   
   
   
/**
     * Can a member promote anything?
     *
     * @param    NULL|\IPS\Member    $member        Member object or NULL for current member
     * @return    bool
     */
   
public static function canPromote( $member=NULL )
    {
       
$member = $member ? $member : \IPS\Member::loggedIn();
       
       
/* Got any services enabled? */
       
if ( static::promoters() === NULL )
        {
            return
FALSE;
        }
       
        if ( \
IPS\Settings::i()->promote_members )
        {
            if ( \
IPS\Settings::i()->promote_members and in_array( $member->member_id , explode( "\n", \IPS\Settings::i()->promote_members ) ) )
            {
                return
TRUE;
            }
        }
       
        if (
$member->group['gbw_promote'] )
        {
            return
TRUE;
        }
       
        return
FALSE;
    }
   
   
/**
     * Can View wrapper for items and nodes
     *
     * @param    object                $object        Object (node or content item)
     * @param    NULL|\IPS\Member    $member        Member object or NULL for current member
     * @return boolean
     */
   
public static function objectCanView( $object, $member=NULL )
    {
       
$member ?: \IPS\Member::loggedIn();
       
        if (
$object instanceof \IPS\Content )
        {
            return
$object->canView( $member );
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
            return
$object->can( 'view', $member );
        }
       
        throw new \
OutofRangeException('object_not_valid');
    }

   
/**
     * Return a list of groups that cannot see this item
     *
     * @param    object                $object        Object (node or content item)
     * @return NULL|Array
     */
   
public static function objectCannotViewGroups( $object )
    {
       
$groups = array();
        foreach( \
IPS\Member\Group::groups() as $group )
        {
            if (
$object instanceof \IPS\Content\Comment )
            {
                if ( !
$object->item()->can( 'view', $group ) )
                {
                   
$groups[] = $group->name;
                }
            }
            else
            {
                if ( !
$object->can( 'view', $group ) )
                {
                   
$groups[] = $group->name;
                }
            }
        }
       
        return
count( $groups ) ? $groups : NULL;
    }
   
   
/**
     * Get title wrapper for items and nodes
     *
     * @param    object    $object        Object (node or content item)
     * @return string
     */
   
public static function objectTitle( $object )
    {
        if (
$object instanceof \IPS\Content\Item )
        {
            return
$object->mapped('title');
        }
        else if (
$object instanceof \IPS\Content\Comment )
        {
            try
            {
                return \
IPS\Member::loggedIn()->language()->addToStack( 'promote_thing_in_thing', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $object::$title ), $object->item()->mapped('title') ) ) );
            }
            catch( \
Exception $e )
            {
                return
$object->item()->mapped('title');
            }
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
            return
$object->_title;
        }
       
        throw new \
OutofRangeException('object_not_valid');
    }
   
   
/**
     * Get content wrapper for items and nodes
     *
     * @param    object    $object        Object (node or content item)
     * @return string
     */
   
public static function objectContent( $object )
    {
       
$result = NULL;

        if (
$object instanceof \IPS\Content\Item )
        {
            if ( isset(
$object::$databaseColumnMap['content'] ) )
            {
               
$result = $object->truncated();
            }
            else if (
$object::$firstCommentRequired )
            {
               
$firstComment = $object->comments( 1, NULL, 'date', 'asc' );
               
               
$result = $firstComment->truncated();
            }
        }
        else if (
$object instanceof \IPS\Content\Comment )
        {
           
$result = $object->truncated();
        }
        else if (
$object instanceof \IPS\Node\Model )
        {
           
$result = $object->description;
        }
       
       
/* If result was not set, throw exception now */
       
if( $result === NULL )
        {
            throw new \
OutofRangeException('object_not_valid');
        }

       
/* If we treat enter key as newline instead of paragraph, we need to clean up a bit further */
       
if( !\IPS\Settings::i()->editor_paragraph_padding )
        {
           
$result = str_replace( "\n", '', $result );
        }

       
/* Clean up excess newlines */
       
$result = trim( preg_replace( "#(<br>){1,}#", "\n",  preg_replace( '#(<br>)\s+#', "\n", $result ) ) );

       
/* If this is a node, strip HTML tags for security reasons */
       
if ( $object instanceof \IPS\Node\Model )
        {
           
$result = strip_tags( $result );
        }

        return
$result;
    }
   
   
/**
     * Load promote row for this class and id
     */
   
protected static $classAndIdLookup = array();
   
   
/**
     * Construct ActiveRecord from database row
     *
     * @param    array    $data                            Row from database table
     * @param    bool    $updateMultitonStoreIfExists    Replace current object in multiton store if it already exists there?
     * @return    static
     */
   
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
    {
       
$object = parent::constructFromData( $data, $updateMultitonStoreIfExists );
        static::
$classAndIdLookup[ $object->class ][ $object->class_id ] = $object->id;
        return
$object;
    }

   
/**
     * Load promote row for this class and id
     *
     * @param    string        $class                 Class name
     * @param    integer        $id                    Item ID
     * @param    boolean        $sent                Only look for sent items
     * @param    boolean        $futureScheduled    Only look for future scheduled items
     * @return  \IPS\core\Promote item
     */
   
public static function loadByClassAndId( $class, $id, $sent=FALSE, $futureScheduled=FALSE )
    {        
        if ( !isset( static::
$classAndIdLookup[ $class ][ $id ] ) )
        {
            try
            {
               
$object = static::constructFromData( \IPS\Db::i()->select( '*', 'core_social_promote', array( 'promote_class=? and promote_class_id=?', $class, $id ) )->first() );
            }
            catch( \
UnderflowException $e )
            {
                static::
$classAndIdLookup[ $class ][ $id ] = 0;
                return;
            }
        }
        else
        {
            if ( static::
$classAndIdLookup[ $class ][ $id ] )
            {
               
$object = static::load( static::$classAndIdLookup[ $class ][ $id ] );
            }
            else
            {
                return;
            }
        }
       
        if (
$futureScheduled and $object->scheduled < time() )
        {
            return;
        }
       
        if (
$sent and $object->sent > time() )
        {
            return;
        }
       
        return
$object;        
    }
   
   
/**
     * Shorten URL.
     * We only have bit.ly as a shortener at the moment.
     *
     * @param    string        $longUrl    The original URL
     * @returns    string        NULL if no shortnener available or it fails
     * @throws    \RuntimeException if shortener fails
     * @throws    \UnderflowException if no shortener available
     */
   
public static function shortenUrl( $longUrl )
    {
        if ( ! \
IPS\Settings::i()->bitly_enabled or ! \IPS\Settings::i()->bitly_token )
        {
            throw new \
UnderflowException;
        }
       
       
/* Have a bash at it like */
       
try
        {
           
$response = \IPS\Http\Url::external( "https://api-ssl.bitly.com/v3/shorten" )->setQueryString( array( 'access_token' => \IPS\Settings::i()->bitly_token, 'longUrl' => $longUrl ) )->request()->get()->decodeJson();

            if (
$response['status_code'] !== 200 )
            {
                throw new \
RuntimeException;
            }
           
            return
$response['data']['url'];
        }
        catch ( \
IPS\Http\Request\Exception $e )
        {
            throw new \
RuntimeException;
        }
    }
   
   
/**
     * Get the next auto schedule timestamp
     *
     * @return null|\IPS\DateTime object
     */
   
public static function getNextAutoSchedule()
    {
        if ( ! \
IPS\Settings::i()->promote_scheduled )
        {
            return
NULL;
        }
       
       
$latest = \IPS\Db::i()->select( 'MAX(promote_scheduled)', 'core_social_promote', array( 'promote_schedule_auto=1 and promote_sent=0' ) )->first();
       
$timezone = new \DateTimeZone( \IPS\Settings::i()->promote_tz );
       
$current = \IPS\DateTime::create()->setTimezone( $timezone );
       
$times = explode( ',', \IPS\Settings::i()->promote_scheduled );
       
$time = NULL;
       
        if (
$latest )
        {
           
$current = \IPS\DateTime::ts( $latest )->setTimezone( $timezone );
        }
       
       
/* Fetch the next scheduled time */
       
$test = $current;
        foreach(
$times as $entry )
        {
            list(
$h, $m ) = explode( ':', $entry );
           
$test = \IPS\DateTime::create()->setTimezone( $timezone )->setTime( $h, $m );
           
            if (
$current->getTimeStamp() < $test->getTimeStamp() )
            {
               
$time = $test;
                break;
            }
        }
       
       
/* Still here? Then pick the earliest time for the next day */
       
if ( $time === NULL )
        {
           
$firstTime = array_shift( $times );
            list(
$h, $m ) = explode( ':', $firstTime );
           
           
$time = $current->add( new \DateInterval( 'P1D' ) )->setTime( $h, $m );
        }
       
        return
$time;
    }

   
/**
     * Return a single promote object
     *
     * @param $key        string    Promote Service
     * @return \IPS\Login
     */

   
public static function getPromoter( $key )
    {
       
/* Try and get this from the datastore first */
       
$promoters = static::promoters();
   
        if (
$promoters !== NULL )
        {
            foreach(
$promoters as $promoterKey => $object )
            {
                if (
mb_strtolower( $promoterKey ) == mb_strtolower( $key ) )
                {
                    return
$object;
                }
            }
        }
       
       
/* Fall back */
       
return \IPS\Content\Promote\PromoteAbstract::constructFromData( \IPS\Db::i()->select( '*', 'core_social_promote_sharers', array( 'sharer_key=?', $key ) )->first() );
    }
   
   
/**
     * Get Promoter objects
     *
     * @return    array
     */
   
public static function promoters()
    {
       
/* Fetch the appropriate promoters */
       
if ( static::$promoters === NULL )
        {
           
            if ( isset( \
IPS\Data\Store::i()->promoters ) )
            {
               
$rows = \IPS\Data\Store::i()->promoters;
            }
            else
            {
               
$rows = iterator_to_array( \IPS\Db::i()->select( '*', 'core_social_promote_sharers', 'sharer_enabled=1' ) );
                \
IPS\Data\Store::i()->promoters = $rows;                    
            }
               
            foreach (
$rows as $row )
            {
                try
                {
                    static::
$promoters[ $row['sharer_key'] ] = \IPS\Content\Promote\PromoteAbstract::constructFromData( $row );
                }
                catch ( \
RuntimeException $e ) { }
            }
        }

        return static::
$promoters;
    }
   
   
/**
     * Return the promotable services this user has access to
     *
     * @return NULL|array of promote classes
     */
   
public static function promoteServices()
    {
       
$services = array();
       
$promoters = static::promoters();
       
        if (
$promoters === NULL )
        {
            return
NULL;
        }
       
        foreach(
$promoters as $key => $object )
        {
            if (
$object->setMember( \IPS\Member::loggedIn() )->canPromote() )
            {
               
$services[] = $object;
            }
        }
       
        return
count( $services ) ? $services : NULL;
    }
   
   
/**
     * Process any queued items
     *
     * @return void
     */
   
public static function processQueue()
    {
       
$processed = 0;
        foreach( \
IPS\Db::i()->select( '*', 'core_social_promote', array( 'promote_sent=0 and promote_failed < 4 and ( promote_scheduled < ' . time() . ' and promote_scheduled > 0 )' ), 'promote_scheduled asc', array( 0, 5 ) ) as $row )
        {
           
$processed++;
           
$promote = static::constructFromData( $row );
           
$promote->send();
        }
       
        if ( !
$processed )
        {
           
/* Disable the task for now, but only if there is nothing to send later */
           
try
            {
               
$future = \IPS\Db::i()->select( 'COUNT(*)', 'core_social_promote', array( "promote_sent=?", 0 ) )->first();
            }
            catch( \
Exception $e )
            {
               
$future = 0;
            }
           
            if ( !
$future )
            {
                \
IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 0 ), array( '`key`=?', 'promote' ) );
            }
        }
    }
   
   
/**
     * Reschedule queued items
     *
     * @return void
     */
   
public static function rescheduleQueue()
    {
       
/* Reset times */
       
if ( \IPS\Settings::i()->promote_scheduled )
        {
            \
IPS\Db::i()->update( 'core_social_promote', array( 'promote_scheduled' => 0 ), array( 'promote_schedule_auto=1 and promote_sent=0' ) );
           
            foreach( \
IPS\Db::i()->select( '*', 'core_social_promote', array( 'promote_scheduled=0 and promote_schedule_auto=1 and promote_sent=0' ), 'promote_added asc' ) as $row )
            {
                \
IPS\Db::i()->update( 'core_social_promote', array( 'promote_scheduled' => static::getNextAutoSchedule()->getTimeStamp() ), array( 'promote_id=?', $row['promote_id'] ) );
            }
        }
    }
}