Seditio Source
Root |
./othercms/ips_4.3.4/system/Poll/Poll.php
<?php
/**
 * @brief        Poll 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        10 Jan 2014
 */

namespace IPS;

/* 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;
}

/**
 * Poll Model
 */
class _Poll extends \IPS\Patterns\ActiveRecord implements \SplSubject
{
   
/**
     * @brief    Database Table
     */
   
public static $databaseTable = 'core_polls';
   
   
/**
     * @brief    Database ID Column
     */
   
public static $databaseColumnId = 'pid';
   
   
/**
     * @brief    Multiton Store
     */
   
protected static $multitons;
   
   
/**
     * @brief    Display template
     */
   
public $displayTemplate;
   
   
/**
     * @brief    URL to use instead of \IPS\Request::i()->url()
     */
   
public $url;
   
   
/**
     * Set Default Values
     *
     * @return    void
     */
   
public function setDefaultValues()
    {
       
$this->start_date = new \IPS\DateTime;
       
$this->choices = array();
    }
   
   
/**
     * Set start date
     *
     * @param    \IPS\DateTime    $value    Value
     * @return    void
     */
   
public function set_start_date( \IPS\DateTime $value )
    {
       
$this->_data['start_date'] = $value->getTimestamp();
    }
   
   
/**
     * Get start date
     *
     * @return    \IPS\DateTime
     */
   
public function get_start_date()
    {
        return \
IPS\DateTime::ts( $this->_data['start_date'] );
    }

   
/**
     * Poll load - We do this so that we can close the poll in realtime when it's viewed.
     *
     * @see        \IPS\Db::build
     * @param    int|string    $id                    ID
     * @param    string        $idField            The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
     * @param    mixed        $extraWhereClause    Additional where clause(s) (see \IPS\Db::build for details) - if used will cause multiton store to be skipped and a query always ran
     * @return    static
     * @throws    \InvalidArgumentException
     * @throws    \OutOfRangeException
     */
   
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
    {
       
$poll = parent::load( $id, $idField, $extraWhereClause );

        if(
$poll->poll_close_date instanceof \IPS\DateTime )
        {
            if( !
$poll->poll_closed and $poll->poll_close_date < \IPS\DateTime::create() )
            {
               
$poll->poll_closed = 1;
               
$poll->save();
            }
        }

        return
$poll;
    }
   
   
/**
     * Set choices
     *
     * @param    array    $value    Value
     * @return    void
     */
   
public function set_choices( array $value )
    {
       
$this->_data['choices'] = json_encode( $value );
    }

   
/**
     * @brief    Poll close date \IPS\DateTime object
     */
   
protected $_pollCloseDateObject = null;

   
/**
     * Get Poll Close Date
     *
     * @return    \IPS\DateTime
     */
   
public function get_poll_close_date()
    {
        if(
$this->_pollCloseDateObject instanceof \IPS\DateTime )
        {
            return
$this->_pollCloseDateObject;
        }
        elseif(
$this->_data['poll_close_date'] == -1 )
        {
            return
$this->_data['poll_close_date'];
        }

        return
$this->_pollCloseDateObject = \IPS\DateTime::ts( $this->_data['poll_close_date'] );
    }

   
/**
     * Set Poll Close Date
     *
     * @param    \IPS\DateTime|int    $date
     * @return    void
     */
   
public function set_poll_close_date( $date )
    {
        if(
$date instanceof \IPS\DateTime )
        {
           
$this->changed['poll_close_date'] = $this->_data['poll_close_date'] = $date->getTimestamp();
           
$this->_pollCloseDateObject = $date;
            return;
        }

       
$this->_pollCloseDateObject = null;
       
$this->_data['poll_close_date'] = $date;
    }
   
   
/**
     * Get choices
     *
     * @return    array
     */
   
public function get_choices()
    {
        return
json_decode( $this->_data['choices'], TRUE );
    }
   
   
/**
     * Get author
     *
     * @return    \IPS\Member
     */
   
public function author()
    {
        return \
IPS\Member::load( $this->starter_id );
    }

   
/**
     * Set Choices
     *
     * @param    array    $data            Values from form
     * @param    bool    $allowPollOnly    Allow poll-only?
     * @return    void
     */
   
public function setDataFromForm( $data, $allowPollOnly )
    {
        if (
$data['title'] )
        {
           
$this->poll_question = $data['title'];
        }
        else
        {
           
/* If no title specified, just use the one from the first title */
           
$questions = $data['questions'];
           
$firstQuestion = array_shift( $questions );
           
$this->poll_question = $firstQuestion['title'];
        }
       
       
$this->poll_only = ( \IPS\Settings::i()->ipb_poll_only and $allowPollOnly and isset( $data['poll_only'] ) );
       
$this->poll_view_voters = ( \IPS\Settings::i()->poll_allow_public and isset( $data['public'] ) );
       
       
$this->votes = 0;
       
$choices = array();
       
$existing = $this->choices;
        foreach (
$data['questions'] as $k => $questionData )
        {
            if (
$questionData['title'] )
            {
               
$choices[ $k ] = array(
                   
'question'    => $questionData['title'],
                   
'multi'        => intval( isset( $questionData['multichoice'] ) ),
                   
'choice'    => array(),
                   
'votes'        => array(),
                );
               
                foreach (
$questionData['answers'] as $answerId => $answerData )
                {
                   
$answerData['value'] = strip_tags( \IPS\Text\Parser::parseStatic( $answerData['value'], true, null, null, true, true, true, function( $config ) {
                           
$config->set( 'HTML.AllowedElements', 'a,img' );
                    } ),
'<a><img>' );

                   
/* AllowedElements strips <___base_url___> */
                   
$answerData['value'] = preg_replace( '#(<img\s+?src=[\'"])___base_url___/#', '\1<___base_url___>/', $answerData['value'] );

                   
$count = isset( $existing[ $k ]['votes'][ $answerId ] ) ? $existing[ $k ]['votes'][ $answerId ] : 0;
                    if (
trim( $answerData['value'] ) !== "" )
                    {
                       
$choices[ $k ]['choice'][ $answerId ] = $answerData['value'];

                        if ( isset(
$answerData['count'] ) and \IPS\Member::loggedIn()->modPermission('can_edit_poll_votes') )
                        {
                           
$count = ( $answerData['count'] > 0 ? intval( $answerData['count'] ) : 0 );
                        }
                    }

                   
$choices[ $k ]['votes'][ $answerId ] = $count;
                }
            }
        }
       
$this->choices = $choices;

       
/* Poll Close Date */
       
if( isset( $data['poll_close_date'] ) && isset( $data['has_close_date'] ) )
        {
            try
            {
               
$timezone = $this->options['timezone'] ?: ( \IPS\Member::loggedIn()->timezone ? new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) : NULL );
            }
            catch ( \
Exception $e )
            {
               
$timezone = NULL;
            }

           
$time = ' 24:00';
            if( isset(
$data['poll_close_time'] ) and !empty( $data['poll_close_time'] ) )
            {
               
$time = ' ' . $data['poll_close_time'];
            }

           
$closeDate = new \IPS\DateTime( \IPS\Helpers\Form\Date::_convertDateFormat( $data['poll_close_date'] ) . $time, $timezone );
           
$this->poll_close_date = $closeDate;

           
/* Close date is in the past, make sure the poll is closed */
           
if( $closeDate < \IPS\DateTime::create() )
            {
               
$this->poll_closed = 1;
            }
        }
        else
        {
           
/* Reset the close date, in the event the poll was edited and the close date removed */
           
$this->poll_close_date = -1;
        }

       
/* Set the number of voters */
       
$this->recountVotes();
    }

   
/**
     * Member can close poll?
     *
     * @param    \IPS\Member|NULL    $member    Member or NULL for currently logged in member
     * @return    void
     */
   
public function canClose( \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();

        if( !
$member->member_id )
        {
            return
FALSE;
        }

        if(
$member->modPermission('can_close_polls') )
        {
            return
TRUE;
        }

        if( !(
$member->group['g_close_polls'] and $member->member_id == $this->starter_id ) )
        {
            return
FALSE;
        }

        return
TRUE;
    }
   
   
/**
     * Member can vote?
     *
     * @param    \IPS\Member|NULL    $member    Member or NULL for currently logged in member
     * @return    void
     */
   
public function canVote( \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();
       
        if ( !
$member->member_id )
        {
            return
FALSE;
        }
       
        if ( !
$member->group['g_vote_polls'] )
        {
            return
FALSE;
        }
       
        if ( !\
IPS\Settings::i()->allow_creator_vote and $member == $this->author() )
        {
            return
FALSE;
        }
       
        if ( !\
IPS\Settings::i()->poll_allow_vdelete and $this->getVote( $member ) )
        {
            return
FALSE;
        }
       
        if (
$this->poll_closed )
        {
            return
FALSE;
        }
       
        return
TRUE;
    }
   
   
/**
     * Member can see voters?
     *
     * @param    \IPS\Member|NULL    $member    Member or NULL for currently logged in member
     * @return    void
     */
   
public function canSeeVoters( \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();
        return
$member->modPermission('can_see_poll_voters') or ( \IPS\Settings::i()->poll_allow_public and $this->poll_view_voters );
    }

   
/**
     * Add Vote
     *
     * @param    \IPS\Poll\Vote    $vote    Vote
     * @return    void
     */
   
public function addVote( \IPS\Poll\Vote $vote, \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();
       
       
/* Delete existing vote */
       
if ( $existingVote = $this->getVote( $member ) )
        {
           
$existingVote->delete();
        }

       
/* Add new vote */
       
$vote->poll = $this;
       
$vote->save();
       
$this->notify();

       
// If $vote is not associated with the member_id in the vote cache on vote submit the poll will not show until
        // next page refresh. This forces the page to reload and the poll to show.
       
$this->_voteCache[ $member->member_id ] = $vote;

       
/* Log */
       
if ( $vote->member_choices !== NULL )
        {
           
$this->votes = 0;

           
$pollChoices = $this->choices;
            foreach (
$vote->member_choices as $key => $value )
            {
                if (
is_array( $value ) )
                {
                    foreach (
$value as $k => $v )
                    {
                       
$pollChoices[ $key ]['votes'][ $v ]++;
                    }
                }
                else
                {
                    if(
$value )
                    {
                       
$pollChoices[ $key ]['votes'][$value]++;
                    }
                }
            }
           
$this->choices = $pollChoices;
           
$this->recountVotes();
           
$this->save();
        }
    }

   
/**
     * Recount the total votes for the poll.
     * A vote is a single "submit" regardless of how many questions there are.
     *
     * @note    This requires that $this->choices have the votes stored in it
     * @note    This method does not call save()...be sure you do that yourself
     * @return    void
     */
   
public function recountVotes()
    {
       
/* Reset the cached count */
       
$this->votes = \IPS\Db::i()->select( 'COUNT(*)', 'core_voters', array( 'poll=?', $this->pid ) )->first();
    }
   
   
/**
     * Get Votes
     *
     * @param    int|NULL    $question    If you only want to retreive votes where users voted a particular answer for a particular question, provide the question ID
     * @param    int|NULL    $option        If you only want to retreive votes where users voted a particular answer for a particular question, provide the option ID
     * @return    \IPS\Patterns\ActiveRecordIterator
     */
   
public function getVotes( $question=NULL, $option=NULL )
    {
       
$iterator = \IPS\Db::i()->select( '*', 'core_voters', array( 'poll=?', $this->pid ) );
       
$iterator = new \IPS\Patterns\ActiveRecordIterator( $iterator, 'IPS\Poll\Vote' );
       
        if (
$question !== NULL )
        {
           
$iterator = new \IPS\Poll\Iterator( $iterator, $question, $option );
        }
       
        return
$iterator;
    }
   
   
/**
     * @brief    Vote Cache
     */
   
protected $_voteCache = array();
   
   
/**
     * Get Vote
     *
     * @param    \IPS\Member    $member    Member
     * @return    \IPS\Poll\Vote|NULL
     */
   
public function getVote( \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();
        try
        {
            if ( !isset(
$this->_voteCache[ $member->member_id ] ) )
            {
               
$this->_voteCache[ $member->member_id ] = \IPS\Poll\Vote::load( $member->member_id, 'member_id', array( 'poll=?', $this->pid ) );
            }
           
            if (
$this->_voteCache[ $member->member_id ] === FALSE )
            {
                return
NULL;
            }
           
            return
$this->_voteCache[ $member->member_id ];
        }
        catch ( \
OutOfRangeException $e )
        {
           
$this->_voteCache[ $member->member_id ] = FALSE;
            return
NULL;
        }
    }
   
   
/**
     * Show Poll
     *
     * @return    string
     */
   
public function __toString()
    {
        try
        {
           
/* Pre 4.x data can be bad */
           
if ( !is_array( $this->choices ) || !count( $this->choices ) )
            {
                return
'';
            }

            foreach(
$this->choices as $id => $question )
            {
                if ( ! isset(
$question['votes'] ) or ! isset( $question['choice'] ) )
                {
                    return
'';
                }
            }
           
            if ( !
$this->displayTemplate )
            {
               
$this->displayTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' ), 'poll' );
            }
   
           
$output    = call_user_func( $this->displayTemplate, $this, ( $this->url ?: \IPS\Request::i()->url() ) );

            if( \
IPS\Request::i()->isAjax() && \IPS\Request::i()->fetchPoll )
            {
               
/* If a vote was submitted but we're returning HTML, that means there was an error (probably a choice not selected for
                    a question) so we return a 500 error code to make the form submit properly rather than showing "Your vote has been saved" */
               
\IPS\Output::i()->sendOutput( $output, ( $this->buildForm() and $this->formSaved === FALSE and ! isset( \IPS\Request::i()->viewResults ) ) ? 500 : 200, 'text/html' );
            }

            return
$output;
        }
        catch( \
Exception $e )
        {
            \
IPS\IPS::exceptionHandler( $e );
        }
        catch ( \
Throwable $e )
        {
            \
IPS\IPS::exceptionHandler( $e );
        }
    }
   
   
/**
     * Can view results
     *
     * @return boolean
     */
   
public function canViewResults()
    {
        if ( \
IPS\Settings::i()->allow_result_view )
        {
            return
TRUE;
        }
        else if ( isset( \
IPS\Request::i()->nullVote ) and \IPS\Member::loggedIn()->member_id )
        {
            if ( !
$this->getVote() )
            {
               
$this->addVote( \IPS\Poll\Vote::fromForm( NULL ) );
               
                return
TRUE;
            }
        }
       
        return
FALSE;
    }

   
/**
     * @brief    Flag if the form has been saved so we don't resave votes
     */
   
protected $formSaved    = FALSE;
   
   
/**
     * Build Form
     *
     * @return    \IPS\Helpers\Form
     */
   
public function buildForm()
    {
        if ( !
$this->canVote() )
        {
            return
'';
        }
       
       
$form = new \IPS\Helpers\Form('poll', 'save_vote');
        foreach (
$this->choices as $k => $data )
        {
           
$class = ( isset( $data['multi'] ) AND $data['multi'] ) ? 'IPS\Helpers\Form\CheckboxSet' : 'IPS\Helpers\Form\Radio';
           
$input = new $class( $k, ( isset( $data['multi'] ) AND $data['multi'] ) ? array() : NULL, TRUE, array( 'options' => $data['choice'], 'noDefault' => TRUE ) );

           
$input->label = $data['question'];
           
$form->add( $input );
        }
       
        if (
$values = $form->values() AND !$this->formSaved )
        {
           
$this->formSaved    = TRUE;
           
$this->addVote( \IPS\Poll\Vote::fromForm( $values ) );
            return
'';
        }
       
        return
$form;
       
    }
   
   
/* !SplSubject */
   
    /**
     * @brief    Observers
     */
   
protected $observers = array();
   
   
/**
     * Attach Observer
     *
     * @param    \SplObserver    $observer
     * @return    void
     */
   
public function attach( \SplObserver $observer )
    {
       
$this->observers[] = $observer;
    }
   
   
/**
     * Attach Observer
     *
     * @param    \SplObserver    $observer
     * @return    void
     */
   
public function detach( \SplObserver $observer )
    {
        foreach (
$this->observers as $k => $v )
        {
            if (
$v === $observer )
            {
                unset(
$this->observers[ $k ] );
            }
        }
    }
   
   
/**
     * Notify
     *
     * @return    void
     */
   
public function notify()
    {
        foreach (
$this->observers as $k => $v )
        {
           
$v->update( $this );
        }
    }
   
   
/**
     * Get output for API
     *
     * @param    \IPS\Member|NULL    $authorizedMember    The member making the API request or NULL for API Key / client_credentials
     * @return    array
     * @apiresponse    int                        id            ID number
     * @apiresponse    string                    title        Title
     * @apiresponse    int                        votes        Number of votes
     * @apiresponse    [\IPS\Poll\Question]    questions    The questions
     */
   
public function apiOutput( \IPS\Member $authorizedMember = NULL )
    {
       
$questions = array();
        foreach (
$this->choices as $choice )
        {
           
$questions[] = ( new \IPS\Poll\Question( $choice ) )->apiOutput( $authorizedMember );
        }
       
        return array(
           
'id'        => $this->pid,
           
'title'        => $this->poll_question,
           
'votes'        => $this->votes,
           
'questions'    => $questions
       
);
    }
}