Version 4.3.0
This commit is contained in:
1 parent
96997ddd8e
commit
fe1acf984a
1349 files changed
+116159
-67711
No files matched your search
@@ -183,8 +183,7 @@ class _Advertisement extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( !\IPS\Db::i()->select( 'COUNT(*)', 'core_advertisements', 'ad_active=1' )->first() )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => 0 ), array( 'conf_key=?', 'ads_exist' ) );
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
\IPS\Settings::i()->changeValues( array( 'ads_exist' => 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,8 +269,7 @@ class _Advertisement extends \IPS\Patterns\ActiveRecord
|
||||
/* Make sure we still have active ads */
|
||||
if ( !\IPS\Db::i()->select( 'COUNT(*)', 'core_advertisements', 'ad_active=1' )->first() )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => 0 ), array( 'conf_key=?', 'ads_exist' ) );
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
\IPS\Settings::i()->changeValues( array( 'ads_exist' => 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,16 @@ class _Announcement extends \IPS\Content\Item
|
||||
*/
|
||||
public static $icon = 'bullhorn';
|
||||
|
||||
/**
|
||||
* Get page location array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_page_location()
|
||||
{
|
||||
return explode( ',', $this->_data['page_location'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SEO name
|
||||
*
|
||||
@@ -89,6 +99,71 @@ class _Announcement extends \IPS\Content\Item
|
||||
return $this->_data['seo_title'] ?: \IPS\Http\Url\Friendly::seoTitle( $this->title );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load announcements by page location
|
||||
*
|
||||
* @param string $location Page location: top, content or sidebar
|
||||
* @return array
|
||||
*/
|
||||
public static function loadAllByLocation( $location )
|
||||
{
|
||||
$announcements = static::getStore();
|
||||
|
||||
$return = array();
|
||||
foreach( $announcements as $announce )
|
||||
{
|
||||
$announcement = static::constructFromData( $announce );
|
||||
|
||||
/* Check page location, active status and permissions */
|
||||
if( !in_array( $location, $announcement->page_location ) OR !$announcement->active OR ( $announcement->end !== 0 AND $announcement->end <= time() ) OR !$announcement->canView() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Is this a global announcement? */
|
||||
if( $announcement->app == '*' )
|
||||
{
|
||||
$return[] = $announcement;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* App and container specific announcements */
|
||||
foreach ( \IPS\Dispatcher::i()->application->extensions( 'core', 'Announcements' ) as $key => $extension )
|
||||
{
|
||||
$id = $extension::$idField;
|
||||
|
||||
if ( isset( \IPS\Request::i()->$id ) )
|
||||
{
|
||||
if ( \IPS\Dispatcher::i()->dispatcherController instanceof \IPS\Content\Controller )
|
||||
{
|
||||
foreach( \IPS\Dispatcher::i()->application->extensions( 'core', 'ContentRouter' ) AS $contentRouter )
|
||||
{
|
||||
foreach( $contentRouter->classes AS $class )
|
||||
{
|
||||
try
|
||||
{
|
||||
if( $announcement->location == $key AND in_array( $class::load( \IPS\Request::i()->$id )->mapped('container'), explode( ',', $announcement->ids ) ) )
|
||||
{
|
||||
$return[] = $announcement;
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e ){}
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* App specific, doesn't matter which container we're in */
|
||||
elseif( $announcement->location == $key )
|
||||
{
|
||||
$return[] = $announcement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Form
|
||||
*
|
||||
@@ -104,7 +179,8 @@ class _Announcement extends \IPS\Content\Item
|
||||
$form->add( new \IPS\Helpers\Form\Date( 'announce_start', ( $announcement ) ? $announcement->start : new \IPS\DateTime ) );
|
||||
$form->add( new \IPS\Helpers\Form\Date( 'announce_end', ( $announcement ) ? $announcement->end : 0, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'indefinitely' ) ) );
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'announce_content', ( $announcement ) ? $announcement->content : NULL, TRUE, array( 'app' => 'core', 'key' => 'Announcement', 'autoSaveKey' => ( $announcement ? 'editAnnouncement__' . $announcement->id : 'createAnnouncement' ), 'attachIds' => $announcement ? array( $announcement->id, NULL, 'announcement' ) : NULL ) ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'announce_page_location', ( $announcement ) ? $announcement->page_location : array(), TRUE, array( 'options' => array( 'top' => 'page_top', 'content' => 'content_top', 'sidebar' => 'sidebar' ) ) ) );
|
||||
|
||||
/* Apps */
|
||||
$apps = array();
|
||||
|
||||
@@ -112,6 +188,11 @@ class _Announcement extends \IPS\Content\Item
|
||||
{
|
||||
if ( $key != 'core' )
|
||||
{
|
||||
/* Don't list apps without front modules */
|
||||
if( !count( $data->modules( 'front' ) ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$apps[ $key ] = $data->_title;
|
||||
}
|
||||
}
|
||||
@@ -148,10 +229,28 @@ class _Announcement extends \IPS\Content\Item
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'announce_permissions', $announcement ? ( $announcement->permissions == '*' ? '*' : explode( ',', $announcement->permissions ) ) : '*', NULL, array( 'multiple' => TRUE, 'options' => $groups, 'unlimited' => '*', 'unlimitedLang' => 'everyone' ) ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Custom( 'announce_color', $announcement ? $announcement->color : 'information', NULL, array( 'getHtml' => function( $element )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'front' )->colorSelection( $element->name, $element->value );
|
||||
} ), NULL, NULL, NULL, 'announce_color' ) );
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data store
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getStore()
|
||||
{
|
||||
if ( !isset( \IPS\Data\Store::i()->announcements ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->announcements = iterator_to_array( \IPS\Db::i()->select( '*', static::$databaseTable, NULL, "announce_id ASC" )->setKeyField( 'announce_id' ) );
|
||||
}
|
||||
|
||||
return \IPS\Data\Store::i()->announcements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from form
|
||||
@@ -179,7 +278,8 @@ class _Announcement extends \IPS\Content\Item
|
||||
$obj->end = $values['announce_end'] ? $values['announce_end']->getTimestamp() : 0;
|
||||
$obj->app = $values['announce_app'] ? $values['announce_app'] : "*";
|
||||
$obj->permissions = is_array( $values['announce_permissions'] ) ? implode( ',', $values['announce_permissions'] ) : '*';
|
||||
|
||||
$obj->page_location = $values['announce_page_location'];
|
||||
$obj->color = $values['announce_color'];
|
||||
|
||||
if( in_array( $obj->app, array_keys(\IPS\Application::applications() ) ) )
|
||||
{
|
||||
@@ -337,8 +437,6 @@ class _Announcement extends \IPS\Content\Item
|
||||
$this->active = 1;
|
||||
$this->save();
|
||||
|
||||
\IPS\Widget::deleteCaches( 'announcements', 'core' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,8 +447,6 @@ class _Announcement extends \IPS\Content\Item
|
||||
$this->active = 0;
|
||||
$this->save();
|
||||
|
||||
\IPS\Widget::deleteCaches( 'announcements', 'core' );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -440,7 +536,18 @@ class _Announcement extends \IPS\Content\Item
|
||||
public function save()
|
||||
{
|
||||
parent::save();
|
||||
static::rebuildAnnouncementsExist();
|
||||
unset( \IPS\Data\Store::i()->announcements );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set page location array
|
||||
*
|
||||
* @param array Page locations: top, content and sidebar
|
||||
* @return void
|
||||
*/
|
||||
protected function set_page_location( $value )
|
||||
{
|
||||
$this->_data['page_location'] = implode( ',', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,30 +558,6 @@ class _Announcement extends \IPS\Content\Item
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
static::rebuildAnnouncementsExist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild \IPS\Settings::i()->announcements_exist which is sued to flag if any announcements
|
||||
* exist to save a query if we know there aren't any
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function rebuildAnnouncementsExist()
|
||||
{
|
||||
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_announcements', 'announce_active=1' )->first() )
|
||||
{
|
||||
$value = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
if ( $value != \IPS\Settings::i()->announcements_exist )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $value ), array( 'conf_key=?', 'announcements_exist' ) );
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
}
|
||||
unset( \IPS\Data\Store::i()->announcements );
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class _Bulkmailer extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
/* Work out how many to do */
|
||||
/* Work out how many to do */
|
||||
$classToUse = \IPS\Email::classToUse( \IPS\Email::TYPE_BULK );
|
||||
$limit = array( $this->offset, $classToUse::MAX_EMAILS_PER_GO );
|
||||
|
||||
@@ -123,7 +123,7 @@ class _Bulkmailer extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* Send it */
|
||||
$email = \IPS\Email::buildFromContent( $this->_data['subject'], $content, NULL, \IPS\Email::TYPE_BULK )
|
||||
$email = \IPS\Email::buildFromContent( $this->_data['subject'], \IPS\Email::staticParseTextForEmail( $content, \IPS\Lang::load( \IPS\Lang::defaultLanguage() ) ), NULL, \IPS\Email::TYPE_BULK )
|
||||
->setUnsubscribe( 'core', 'unsubscribeBulk' );
|
||||
$sent = 0;
|
||||
foreach ( $recipients as $languageId => $_recipients )
|
||||
@@ -187,9 +187,9 @@ class _Bulkmailer extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* Compile query */
|
||||
$select = ( $option !== static::GET_COUNT_ONLY ) ? 'core_members.member_id AS my_member_id, core_members.*' : 'COUNT(*)';
|
||||
$select = ( $option !== static::GET_COUNT_ONLY ) ? 'core_members.member_id AS my_member_id, core_members.*' : 'COUNT(DISTINCT member_id)';
|
||||
$limit = is_array( $option ) ? $option : NULL;
|
||||
$query = \IPS\Db::i()->select( $select, 'core_members', $where, ( $option !== static::GET_COUNT_ONLY ) ? 'core_members.member_id' : NULL, $limit );
|
||||
$query = \IPS\Db::i()->select( $select, 'core_members', $where, ( $option !== static::GET_COUNT_ONLY ) ? 'core_members.member_id' : NULL, $limit, NULL, NULL, \IPS\Db::SELECT_DISTINCT );
|
||||
|
||||
/* Run callbacks */
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'MemberFilter', FALSE, 'core' ) as $key => $extension )
|
||||
|
||||
@@ -175,7 +175,7 @@ class _DeletionLog extends \IPS\Patterns\ActiveRecord
|
||||
$this->content_id = $content->$idField;
|
||||
$this->content_title = $item->mapped('title');
|
||||
$this->content_seo_title = \IPS\Http\Url\Friendly::seoTitle( $item->mapped('title') );
|
||||
$this->content_permissions = $item->searchIndexPermissions();
|
||||
$this->content_permissions = $item->deleteLogPermissions();
|
||||
|
||||
/* Member Data */
|
||||
$this->deleted_by = $member;
|
||||
@@ -260,9 +260,28 @@ class _DeletionLog extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
if ( $action === NULL )
|
||||
{
|
||||
$class = $this->content_class;
|
||||
$item = $class::load( $this->content_id );
|
||||
return $item->url()->setQueryString( 'showDeleted', 1 );
|
||||
try
|
||||
{
|
||||
$class = $this->content_class;
|
||||
if ( class_exists( $this->content_class ) )
|
||||
{
|
||||
$item = $class::load( $this->content_id );
|
||||
return $item->url()->setQueryString( 'showDeleted', 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Content class doesn't exist anymore */
|
||||
$this->delete();
|
||||
return \IPS\Http\Url::internal( "app=core&module=modcp&controller=modcp&tab=deleted", 'front', 'modcp_deleted' )->setQueryString( array( 'id' => $this->id, 'action' => $action ) );
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
/* Orphaned item */
|
||||
$this->delete();
|
||||
|
||||
return \IPS\Http\Url::internal( "app=core&module=modcp&controller=modcp&tab=deleted", 'front', 'modcp_deleted' )->setQueryString( array( 'id' => $this->id, 'action' => $action ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -53,7 +53,11 @@ class _Table extends \IPS\Helpers\Table\Table
|
||||
/* Where Clause */
|
||||
$where = array();
|
||||
$where[] = array( "(dellog_content_permissions=? OR " . \IPS\Db::i()->findInSet( 'dellog_content_permissions', \IPS\Member::loggedIn()->groups ) . " OR " . \IPS\Db::i()->findInSet( 'dellog_content_permissions', array( 'm' . \IPS\Member::loggedIn()->member_id ) ) . ")", '*' );
|
||||
|
||||
|
||||
/* Return only content from enabled apps */
|
||||
$where[] = \IPS\Db::i()->in( 'dellog_content_class', array_values( \IPS\Content::routedClasses( TRUE, TRUE ) ) );
|
||||
|
||||
|
||||
if ( $advancedSearchValues )
|
||||
{
|
||||
if ( isset( $advancedSearchValues['dellog_content_class'] ) AND $advancedSearchValues['dellog_content_class'] != 'all' )
|
||||
|
||||
@@ -87,9 +87,9 @@ class _Digest
|
||||
$added = FALSE;
|
||||
|
||||
/* Following an item or node */
|
||||
$class = 'IPS\\' . $app . '\\' . ucfirst( $key );
|
||||
|
||||
if ( class_exists( $class ) )
|
||||
$class = 'IPS\\' . $app . '\\' . mb_ucfirst( $key );
|
||||
|
||||
if ( class_exists( $class ) AND \IPS\Application::appIsEnabled( $app ) )
|
||||
{
|
||||
$parents = class_parents( $class );
|
||||
|
||||
@@ -140,7 +140,7 @@ class _Digest
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach( $item->comments( 5, NULL, 'date', 'asc', NULL, FALSE, \IPS\DateTime::ts( $follow['follow_notify_sent'] ?: $follow['follow_added'] ) ) as $comment )
|
||||
foreach( $item->comments( 5, NULL, 'date', 'asc', NULL, FALSE, \IPS\DateTime::ts( $follow['follow_notify_sent'] ?: $follow['follow_added'] ), NULL, FALSE, FALSE, FALSE ) as $comment )
|
||||
{
|
||||
|
||||
try
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Facebook Pixel Class
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 16 May 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\Facebook;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Facebook Pixel class
|
||||
*/
|
||||
class _Pixel extends \IPS\Patterns\Singleton
|
||||
{
|
||||
/**
|
||||
* @brief Singleton Instances
|
||||
*/
|
||||
protected static $instance = NULL;
|
||||
|
||||
/**
|
||||
* @brief Data Store
|
||||
*/
|
||||
protected $data = NULL;
|
||||
|
||||
/**
|
||||
* @brief Events
|
||||
*/
|
||||
protected static $events = NULL;
|
||||
|
||||
/**
|
||||
* Output for JS inclusion
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
if ( $this->data === NULL )
|
||||
{
|
||||
return "fbq('track', 'PageView');";
|
||||
}
|
||||
|
||||
$return = array();
|
||||
|
||||
if ( ! isset( $this->data['PageView'] ) )
|
||||
{
|
||||
$this->data['PageView'] = true;
|
||||
}
|
||||
|
||||
foreach( $this->data as $name => $params )
|
||||
{
|
||||
$inlineParams = '';
|
||||
|
||||
if ( is_array( $params ) and count( $params ) )
|
||||
{
|
||||
$inlineParams = json_encode( $params );
|
||||
}
|
||||
|
||||
if ( $inlineParams )
|
||||
{
|
||||
$return[] = "fbq('track', '{$name}', " . $inlineParams . " );";
|
||||
}
|
||||
else
|
||||
{
|
||||
$return[] = "fbq('track', '{$name}');";
|
||||
}
|
||||
}
|
||||
|
||||
return count( $return ) ? implode( "\n", $return ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output for noscript inclusion
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function noscript()
|
||||
{
|
||||
if ( $this->data === NULL )
|
||||
{
|
||||
return '<img alt="" height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=' . \IPS\Settings::i()->fb_pixel_id . '&ev=PageView&noscript=1"/>';
|
||||
}
|
||||
|
||||
$return = array();
|
||||
|
||||
if ( ! isset( $this->data['PageView'] ) )
|
||||
{
|
||||
$this->data['PageView'] = true;
|
||||
}
|
||||
|
||||
foreach( $this->data as $name => $params )
|
||||
{
|
||||
$url = 'https://www.facebook.com/tr?id=' . \IPS\Settings::i()->fb_pixel_id . '&ev=' . $name;
|
||||
|
||||
if ( is_array( $params ) and count( $params ) )
|
||||
{
|
||||
$url .= '&' . http_build_query( array( 'cd' => $params ) );
|
||||
}
|
||||
|
||||
$return[] = '<img alt="" height="1" width="1" style="display:none" src="' . $url . '"/>';
|
||||
|
||||
}
|
||||
|
||||
return count( $return ) ? implode( "\n", $return ) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Follow 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 13 Nov 2017
|
||||
* @todo Adjust follow code over time to use this class instead
|
||||
*/
|
||||
|
||||
namespace IPS\core\Followed;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow Model
|
||||
*/
|
||||
class _Follow extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_follow';
|
||||
|
||||
/**
|
||||
* @brief Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'follow_';
|
||||
|
||||
/**
|
||||
* @brief Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] ID Database Column
|
||||
*/
|
||||
public static $databaseColumnId = 'id';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Map
|
||||
*/
|
||||
protected static $multitonMap = array();
|
||||
|
||||
/**
|
||||
* Save
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if( $this->_new )
|
||||
{
|
||||
$this->visible = 1;
|
||||
$this->notify_sent = 0;
|
||||
$this->added = time();
|
||||
$this->index_id = NULL;
|
||||
}
|
||||
|
||||
$this->id = md5( $this->app . ';' . $this->area . ';' . $this->rel_id . ';' . $this->member_id );
|
||||
|
||||
parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 followKey Unique key that represents the follow
|
||||
* @apiresponse string followApp The application of the content that was followed
|
||||
* @apiresponse string followArea The area of the content that was followed
|
||||
* @apiresponse int followId The ID of the content that was followed
|
||||
* @apiresponse bool followAnon Flag to indicate if the member is following anonymously
|
||||
* @apiresponse bool followNotify Flag to indicate if notifications should be sent
|
||||
* @apiresponse string|null followType Notification preference for this follow, or null if notifications are not being sent
|
||||
* @apiresponse datetime|null followSent Date and time the last notification was sent, or NULL if none has been sent
|
||||
* @apiresponse string followName Textual representation of the content that was followed (title, name, etc.)
|
||||
* @apiresponse string followUrl URL to the content that was followed
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL )
|
||||
{
|
||||
if( $this->area == 'member' AND $this->app == 'core' )
|
||||
{
|
||||
$followed = \IPS\Member::load( $this->rel_id );
|
||||
$name = $followed->name;
|
||||
$url = $followed->url();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( \IPS\Application::load( $this->app )->extensions( 'core', 'ContentRouter' ) as $key => $router )
|
||||
{
|
||||
foreach( $router->classes as $class )
|
||||
{
|
||||
$followArea = mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) );
|
||||
|
||||
if( $followArea == $this->area AND $class::$application == $this->app )
|
||||
{
|
||||
try
|
||||
{
|
||||
$followed = $class::load( $this->rel_id );
|
||||
$name = $followed->mapped('title');
|
||||
$url = $followed->url();
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
/* If the item doesn't exist we may as well clean up core_follow */
|
||||
parent::delete();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$containers = array();
|
||||
|
||||
if( isset( $class::$containerNodeClass ) )
|
||||
{
|
||||
$containers[ $class::$containerNodeClass ] = $class::$containerNodeClass;
|
||||
}
|
||||
|
||||
if( isset( $class::$containerFollowClasses ) )
|
||||
{
|
||||
foreach( $class::$containerFollowClasses as $followClass )
|
||||
{
|
||||
$containers[ $followClass ] = $followClass;
|
||||
}
|
||||
}
|
||||
|
||||
foreach( $containers as $container )
|
||||
{
|
||||
$containerArea = mb_strtolower( mb_substr( $container, mb_strrpos( $container, '\\' ) + 1 ) );
|
||||
|
||||
if( $containerArea == $this->area AND $class::$application == $this->app )
|
||||
{
|
||||
try
|
||||
{
|
||||
$followed = $container::load( $this->rel_id );
|
||||
$name = $followed->_title;
|
||||
$url = $followed->url();
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
/* If the item doesn't exist we may as well clean up core_follow */
|
||||
parent::delete();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'followKey' => $this->id,
|
||||
'followApp' => $this->app,
|
||||
'followArea' => $this->area,
|
||||
'followId' => $this->rel_id,
|
||||
'followAnon' => (bool) $this->is_anon,
|
||||
'followNotify' => (bool) $this->notify_do,
|
||||
'followType' => $this->notify_do ? $this->notify_freq : NULL,
|
||||
'followSent' => $this->notify_sent ? \IPS\DateTime::ts( $this->notify_sent )->rfc3339() : NULL,
|
||||
'followName' => $name,
|
||||
'followUrl' => (string) $url,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ class _Table extends \IPS\Helpers\Table\Db
|
||||
if ( in_array( 'IPS\Content\Permissions', class_implements( $className ) ) )
|
||||
{
|
||||
$containerClass = $className::$containerNodeClass;
|
||||
$this->where[0] .= ' AND (' . \IPS\Db::i()->findInSet( 'core_permission_index.perm_' . $containerClass::$permissionMap['view'], \IPS\Member::loggedIn()->groups ) . ' OR core_permission_index.perm_' . $containerClass::$permissionMap['view'] . "='*' )";
|
||||
$this->where[0] .= ' AND (' . \IPS\Db::i()->findInSet( 'core_permission_index.perm_' . $containerClass::$permissionMap['view'], \IPS\Member::loggedIn()->permissionArray() ) . ' OR core_permission_index.perm_' . $containerClass::$permissionMap['view'] . "='*' )";
|
||||
}
|
||||
|
||||
$this->joins[] = array( 'from' => 'core_permission_index', 'where' => array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::$databaseColumnId, $containerClass::$permApp, $containerClass::$permType ) );
|
||||
@@ -138,13 +138,15 @@ class _Table extends \IPS\Helpers\Table\Db
|
||||
/* Permission check? */
|
||||
if ( in_array( 'IPS\Node\Permissions', class_implements( $className ) ) )
|
||||
{
|
||||
$this->where[0] .= ' AND (' . \IPS\Db::i()->findInSet( 'core_permission_index.perm_' . $className::$permissionMap['view'], \IPS\Member::loggedIn()->groups ) . ' OR ' . 'core_permission_index.perm_' . $className::$permissionMap['view'] . "='*' )";
|
||||
$this->where[0] .= ' AND (' . \IPS\Db::i()->findInSet( 'core_permission_index.perm_' . $className::$permissionMap['view'], \IPS\Member::loggedIn()->permissionArray() ) . ' OR ' . 'core_permission_index.perm_' . $className::$permissionMap['view'] . "='*' )";
|
||||
}
|
||||
|
||||
/* Additional where */
|
||||
if ( method_exists( $className, 'followWhere' ) )
|
||||
{
|
||||
$extraWhere = $className::followWhere();
|
||||
$joins = array();
|
||||
$extraWhere = $className::followWhere( $joins );
|
||||
$this->joins = array_merge( $this->joins, $joins );
|
||||
|
||||
if( isset( $extraWhere['item'] ) )
|
||||
{
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief ACP Member Profile: Block
|
||||
* @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 20 Nov 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\MemberACPProfile;
|
||||
|
||||
/* 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 ACP Member Profile: Block
|
||||
*/
|
||||
abstract class _Block
|
||||
{
|
||||
/**
|
||||
* @brief Member
|
||||
*/
|
||||
protected $member;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Member $member )
|
||||
{
|
||||
$this->member = $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function title()
|
||||
{
|
||||
$class = get_called_class();
|
||||
$exploded = explode( '\\', $class );
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'memberACPProfileTitle_' . $exploded[1] . '_' . $exploded[5] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function output();
|
||||
|
||||
/**
|
||||
* Edit Window
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
\IPS\Output::i()->error( 'node_error', '2C114/T', 404, '' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief ACP Member Profile: Block
|
||||
* @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 20 Nov 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\MemberACPProfile;
|
||||
|
||||
/* 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 ACP Member Profile: Block
|
||||
*/
|
||||
abstract class _LazyLoadingBlock extends Block
|
||||
{
|
||||
/**
|
||||
* Get Output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate('memberprofile')->lazyLoad( $this->member, get_called_class() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Real Output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function lazyOutput();
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief ACP Member Profile: Main Tab
|
||||
* @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 20 Nov 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\MemberACPProfile;
|
||||
|
||||
/* 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 ACP Member Profile: Main Tab
|
||||
*/
|
||||
abstract class _MainTab
|
||||
{
|
||||
/**
|
||||
* @brief Member
|
||||
*/
|
||||
protected $member;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Member $member )
|
||||
{
|
||||
$this->member = $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function title()
|
||||
{
|
||||
$class = get_called_class();
|
||||
$exploded = explode( '\\', $class );
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'memberACPProfileTitle_' . $exploded[1] . '_' . $exploded[5] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get left-column blocks
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function leftColumnBlocks()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main-column blocks
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function mainColumnBlocks()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
$leftColumnBlocks = array();
|
||||
foreach ( $this->leftColumnBlocks() as $class )
|
||||
{
|
||||
$leftColumnBlocks[] = new $class( $this->member );
|
||||
}
|
||||
|
||||
$mainColumnBlocks = array();
|
||||
foreach ( $this->mainColumnBlocks() as $class )
|
||||
{
|
||||
$mainColumnBlocks[] = new $class( $this->member );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate('memberprofile')->tabTemplate( $leftColumnBlocks, $mainColumnBlocks );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Member Restrictions Extension Base
|
||||
* @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 30 Nov 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\MemberACPProfile;
|
||||
|
||||
/* 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 Member Restrictions Extension Base
|
||||
*/
|
||||
class _Restriction
|
||||
{
|
||||
/**
|
||||
* @brief Member
|
||||
*/
|
||||
protected $member;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Member $member )
|
||||
{
|
||||
$this->member = $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this extension available?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enabled()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify Edit Restrictions form
|
||||
*
|
||||
* @param \IPS\Helpers\Form $form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( \IPS\Helpers\Form $form )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Form
|
||||
*
|
||||
* @param array $values Values from form
|
||||
* @return void
|
||||
*/
|
||||
public function save( $values )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* What restrictions are active on the account?
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function activeRestrictions()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief ACP Member Profile: Tabbed Block
|
||||
* @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 22 Nov 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\MemberACPProfile;
|
||||
|
||||
/* 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 ACP Member Profile: Tabbed Block
|
||||
*/
|
||||
abstract class _TabbedBlock extends Block
|
||||
{
|
||||
/**
|
||||
* Get Output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
$tabs = $this->tabs();
|
||||
if ( !count( $tabs ) )
|
||||
{
|
||||
return '';
|
||||
}
|
||||
$tabKeys = array_keys( $tabs );
|
||||
|
||||
$exploded = explode( '\\', get_called_class() );
|
||||
$tabParam = $exploded[1] . '_' . $exploded[5];
|
||||
$activeTabKey = ( isset( \IPS\Request::i()->block[$tabParam] ) and array_key_exists( \IPS\Request::i()->block[$tabParam], $tabs ) ) ? \IPS\Request::i()->block[$tabParam] : array_shift( $tabKeys );
|
||||
|
||||
return \IPS\Theme::i()->getTemplate('memberprofile')->tabbedBlock( $this->member, $tabParam, $this->blockTitle(), $tabs, $activeTabKey, $this->tabOutput( $activeTabKey ), $this->showEditLink() ? $this->editLink() : NULL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Edit Link?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function showEditLink()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Link
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function editLink()
|
||||
{
|
||||
return \IPS\Http\Url::internal("app=core&module=members&controller=members&do=editBlock")->setQueryString( array(
|
||||
'block' => get_called_class(),
|
||||
'id' => $this->member->member_id
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Block Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function blockTitle()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Tab Names
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function tabs();
|
||||
|
||||
/**
|
||||
* Get output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function tabOutput( $tab );
|
||||
}
|
||||
@@ -221,7 +221,7 @@ class _Conversation extends \IPS\Content\Item
|
||||
{
|
||||
if ( !$m instanceof \IPS\Member OR !static::memberCanReceiveNewMessage( $m, \IPS\Member::loggedIn() ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('meesnger_err_bad_recipient', FALSE, array( 'sprintf' => array( $m ) ) ) );
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('meesnger_err_bad_recipient', FALSE, array( 'sprintf' => array( ( $m instanceof \IPS\Member ) ? $m->name : $m ) ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,7 +244,7 @@ class _Conversation extends \IPS\Content\Item
|
||||
else
|
||||
{
|
||||
/* Ensure that the autosave doesn't populate the editor witha previous PM which may be sent by accident */
|
||||
$v->options['autoSaveKey'] = 'newMessageTo-' . uniqid();
|
||||
$v->options['autoSaveKey'] = 'newMessageTo-' . mt_rand();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ class _Conversation extends \IPS\Content\Item
|
||||
*
|
||||
* @param \IPS\Member $member The member to check
|
||||
* @param \IPS\Member $sender The member sending the new message
|
||||
* @return array
|
||||
* @return bool
|
||||
*/
|
||||
public static function memberCanReceiveNewMessage( \IPS\Member $member, \IPS\Member $sender )
|
||||
{
|
||||
@@ -505,9 +505,10 @@ class _Conversation extends \IPS\Content\Item
|
||||
/**
|
||||
* Get the most recent unread conversation and dismiss the popup
|
||||
*
|
||||
* @return \IPS\Conversation|NULL
|
||||
* @param bool $dismiss Whether or not to dismiss the popup for future page loads
|
||||
* @return \IPS\core\Messenger\Conversation|NULL
|
||||
*/
|
||||
public static function latestUnreadConversation()
|
||||
public static function latestUnreadConversation( $dismiss = TRUE )
|
||||
{
|
||||
$return = NULL;
|
||||
$latestConversationMap = \IPS\Db::i()->select( 'map_topic_id', 'core_message_topic_user_map', array( 'map_user_id=? AND map_user_active=1 AND map_has_unread=1 AND map_ignore_notification=0', \IPS\Member::loggedIn()->member_id ), 'map_last_topic_reply DESC' );
|
||||
@@ -519,11 +520,39 @@ class _Conversation extends \IPS\Content\Item
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
catch ( \UnderflowException $e ) { }
|
||||
|
||||
\IPS\Member::loggedIn()->msg_show_notification = FALSE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
if( $dismiss === TRUE )
|
||||
{
|
||||
\IPS\Member::loggedIn()->msg_show_notification = FALSE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent unread message and dismiss the popup
|
||||
*
|
||||
* @note This is here and abstracted to account for database read/write separation where the conversation may be available, but not the message itself
|
||||
* @return \IPS\core\Messenger\Message|NULL
|
||||
*/
|
||||
public static function latestUnreadMessage()
|
||||
{
|
||||
/* Get the latest conversation, but don't dismiss the notification yet */
|
||||
if( $conversation = static::latestUnreadConversation( FALSE ) )
|
||||
{
|
||||
/* Get the latest comment, which is what we will actually use in the template */
|
||||
if( $latestComment = $conversation->comments( 1, 0, 'date', 'desc' ) )
|
||||
{
|
||||
/* Ok we have what we need, NOW dismiss the notification */
|
||||
\IPS\Member::loggedIn()->msg_show_notification = FALSE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
|
||||
return $latestComment;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recount the member's message counts
|
||||
|
||||
@@ -20,8 +20,10 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
/**
|
||||
* Personal Conversation Message
|
||||
*/
|
||||
class _Message extends \IPS\Content\Comment implements \IPS\Content\ReportCenter
|
||||
class _Message extends \IPS\Content\Comment
|
||||
{
|
||||
use \IPS\Content\Reportable;
|
||||
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Online Users Table Helper
|
||||
* @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 8th September 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\Online;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promote Table Helper
|
||||
*/
|
||||
class _Table extends \IPS\Helpers\Table\Table
|
||||
{
|
||||
/**
|
||||
* @brief Rows
|
||||
*/
|
||||
protected static $rows = null;
|
||||
|
||||
/**
|
||||
* @brief WHERE clause
|
||||
*/
|
||||
protected $where = array();
|
||||
|
||||
/**
|
||||
* @brief WHERE clause
|
||||
*/
|
||||
public $count = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Http\Url Base URL
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Http\Url $url=NULL )
|
||||
{
|
||||
/* Init */
|
||||
parent::__construct( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rows
|
||||
*
|
||||
* @param array $advancedSearchValues Values from the advanced search form
|
||||
* @return array
|
||||
*/
|
||||
public function getRows( $advancedSearchValues=NULL )
|
||||
{
|
||||
if ( static::$rows === NULL )
|
||||
{
|
||||
/* What are we sorting by? */
|
||||
$sortDirection = ( mb_strtolower( $this->sortDirection ) == 'asc' ? 'asc' : 'desc' );
|
||||
$flags = \IPS\Session\Store::ONLINE_MEMBERS | \IPS\Session\Store::ONLINE_GUESTS;
|
||||
$memberGroup = NULL;
|
||||
|
||||
if ( $this->filter == 'filter_loggedin' )
|
||||
{
|
||||
$flags = \IPS\Session\Store::ONLINE_MEMBERS;
|
||||
}
|
||||
elseif ( mb_stristr( $this->filter, 'group_' ) )
|
||||
{
|
||||
$memberGroup = intval( $this->filters[ $this->filter ] );
|
||||
}
|
||||
|
||||
$this->count = \IPS\Session\Store::i()->getOnlineUsers( $flags | \IPS\Session\Store::ONLINE_COUNT_ONLY, $sortDirection, NULL, $memberGroup, \IPS\Member::loggedIn()->isAdmin() );
|
||||
|
||||
/* If we're not filtering - update our most online count */
|
||||
if ( $this->filter === NULL )
|
||||
{
|
||||
$mostOnline = json_decode( \IPS\Settings::i()->most_online, TRUE );
|
||||
if ( $this->count > $mostOnline['count'] )
|
||||
{
|
||||
\IPS\Settings::i()->changeValues( array( 'most_online' => json_encode( array(
|
||||
'count' => $this->count,
|
||||
'time' => time()
|
||||
) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
$this->pages = ceil( $this->count / $this->limit );
|
||||
|
||||
/* Get results */
|
||||
$rows = \IPS\Session\Store::i()->getOnlineUsers( $flags, $sortDirection, array( ( $this->limit * ( $this->page - 1 ) ), $this->limit ), $memberGroup, \IPS\Member::loggedIn()->isAdmin() );
|
||||
|
||||
/* Loop the data */
|
||||
foreach ( $rows as $rowId => $row )
|
||||
{
|
||||
/* Add in any 'custom' fields */
|
||||
$_row = $row;
|
||||
if ( $this->include !== NULL )
|
||||
{
|
||||
$row = array();
|
||||
foreach ( $this->include as $k )
|
||||
{
|
||||
$row[ $k ] = isset( $_row[ $k ] ) ? $_row[ $k ] : NULL;
|
||||
}
|
||||
|
||||
if( !empty( $advancedSearchValues ) AND !isset( \IPS\Request::i()->noColumn ) )
|
||||
{
|
||||
foreach ( $advancedSearchValues as $k => $v )
|
||||
{
|
||||
$row[ $k ] = isset( $_row[ $k ] ) ? $_row[ $k ] : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $row as $k => $v )
|
||||
{
|
||||
/* Parse if necessary (NB: deliberately do this before removing the row in case we need to do some processing, but don't want the column to actually show) */
|
||||
if( isset( $this->parsers[ $k ] ) )
|
||||
{
|
||||
$v = call_user_func( $this->parsers[ $k ], $v, $_row );
|
||||
}
|
||||
else
|
||||
{
|
||||
$v = htmlspecialchars( $v, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE );
|
||||
}
|
||||
|
||||
/* Are we including this one? */
|
||||
if( ( ( $this->include !== NULL and !in_array( $k, $this->include ) ) or ( $this->exclude !== NULL and in_array( $k, $this->exclude ) ) ) and !array_key_exists( $k, $advancedSearchValues ) )
|
||||
{
|
||||
unset( $row[ $k ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Add to array */
|
||||
$row[ $k ] = $v;
|
||||
}
|
||||
|
||||
static::$rows[ $rowId ] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return static::$rows;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the table headers
|
||||
*
|
||||
* @param array|NULL $advancedSearchValues Advanced search values
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders( $advancedSearchValues )
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ class _Profanity extends \IPS\Patterns\ActiveRecord implements \JsonSerializable
|
||||
|
||||
/* Default sort options */
|
||||
$table->sortBy = $table->sortBy ?: 'type';
|
||||
$table->sortDirection = $table->sortDirection ?: 'desc';
|
||||
$table->sortDirection = $table->sortDirection ?: 'asc';
|
||||
|
||||
/* Filters */
|
||||
$table->filters = array(
|
||||
@@ -388,11 +388,11 @@ class _Profanity extends \IPS\Patterns\ActiveRecord implements \JsonSerializable
|
||||
|
||||
/* Gather up all URL's */
|
||||
$selector = new \DOMXPath($dom);
|
||||
$tags = $selector->query('//img | //a');
|
||||
$tags = $selector->query('//img | //a | //iframe');
|
||||
$good = NULL;
|
||||
foreach( $tags AS $tag )
|
||||
{
|
||||
if ( ( $tag->hasAttribute( 'href' ) and !$tag->hasAttribute( 'data-mentionid' ) ) OR $tag->hasAttribute( 'src' ) )
|
||||
if ( ( $tag->hasAttribute( 'href' ) and !$tag->hasAttribute( 'data-mentionid' ) ) OR ( $tag->hasAttribute( 'src' ) AND !$tag->hasAttribute( 'data-emoticon' ) ) )
|
||||
{
|
||||
if ( \IPS\Settings::i()->ipb_url_filter_option == 'none' )
|
||||
{
|
||||
@@ -419,15 +419,14 @@ class _Profanity extends \IPS\Patterns\ActiveRecord implements \JsonSerializable
|
||||
else if ( \IPS\Settings::i()->ipb_url_filter_option == 'white' )
|
||||
{
|
||||
/* @note http:// is hard-coded here as we're simply validating that the URL is on the same domain, so the protocol doesn't matter for the base_url replacement */
|
||||
if ( is_null( $good ) AND !preg_match( '/' . $url . '/i', str_replace( '<___base_url___>', 'http://' . parse_url( \IPS\Settings::i()->base_url, PHP_URL_HOST ), $urlToCheck ), $matches ) )
|
||||
if ( !preg_match( '/' . $url . '/i', str_replace( '<___base_url___>', 'http://' . parse_url( \IPS\Settings::i()->base_url, PHP_URL_HOST ), $urlToCheck ), $matches ) )
|
||||
{
|
||||
$good = FALSE;
|
||||
break 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
$good = TRUE;
|
||||
break 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,11 +47,12 @@ class _Field
|
||||
/**
|
||||
* 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 string name Field name
|
||||
* @apiresponse string value Value
|
||||
*/
|
||||
public function apiOutput()
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL )
|
||||
{
|
||||
return array( 'name' => $this->name, 'value' => $this->value );
|
||||
}
|
||||
|
||||
@@ -47,14 +47,15 @@ class _FieldGroup
|
||||
/**
|
||||
* 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 string name Group name
|
||||
* @apiresponse [\IPS\core\ProfileFields\Api\Field] fields Fields
|
||||
*/
|
||||
public function apiOutput()
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL )
|
||||
{
|
||||
return array( 'name' => $this->name, 'fields' => array_map( function( $val ) {
|
||||
return $val->apiOutput();
|
||||
return array( 'name' => $this->name, 'fields' => array_map( function( $val ) use ( $authorizedMember ) {
|
||||
return $val->apiOutput( $authorizedMember );
|
||||
}, $this->values ) );
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,11 @@ class _Field extends \IPS\CustomField
|
||||
'module' => 'membersettings',
|
||||
'prefix' => 'profilefields_',
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'profile_field';
|
||||
|
||||
/**
|
||||
* @brief [CustomField] Editor Options
|
||||
@@ -176,6 +181,7 @@ class _Field extends \IPS\CustomField
|
||||
$values['field_' . $row['pf_id'] ] = NULL;
|
||||
}
|
||||
|
||||
/* @todo Remove reliance on \IPS\Request here and instead pass an \IPS\Member object to the method */
|
||||
static::$editorOptions['autoSaveKey'] = md5( get_called_class() . '-' . $row['pf_id'] ) . ( isset( \IPS\Request::i()->id ) ? '-' . \IPS\Request::i()->id : '' );
|
||||
|
||||
if( $row['pf_type'] == 'Editor' )
|
||||
@@ -243,8 +249,12 @@ class _Field extends \IPS\CustomField
|
||||
if ( $location == static::CONTENT and $row['pf_format'] != '' and isset( $values[ 'field_' . $row['pf_id'] ] ) and $row['pf_type'] != 'Poll' )
|
||||
{
|
||||
$row = static::constructFromData( $row );
|
||||
$value = $row->type == 'Url' ? (string) $values[ 'field_' . $row->id ] : $row->displayValue( $values[ 'field_' . $row->id ] );
|
||||
$return[ $row->group_id ][ $row->id ] = str_replace( array( '{title}', '{content}' ), array( \IPS\Member::loggedIn()->language()->addToStack( static::$langKey . '_' . $row->id ), $value ), $row->format );
|
||||
|
||||
if( $row->displayValue( $values[ 'field_' . $row->id ] ) !== "" )
|
||||
{
|
||||
$value = $row->type == 'Url' ? (string) $values[ 'field_' . $row->id ] : $row->displayValue( $values[ 'field_' . $row->id ] );
|
||||
$return[ $row->group_id ][ $row->id ] = str_replace( array( '{title}', '{content}' ), array( \IPS\Member::loggedIn()->language()->addToStack( static::$langKey . '_' . $row->id ), $value ), $row->format );
|
||||
}
|
||||
}
|
||||
else if ( $location !== static::CONTENT )
|
||||
{
|
||||
@@ -270,7 +280,7 @@ class _Field extends \IPS\CustomField
|
||||
$form->addheader( 'pfield_permissions' );
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'pf_admin_only', $this->id ? !$this->admin_only : TRUE, FALSE, array( 'togglesOn' => array( 'pf_show_on_reg', 'pf_member_edit', 'pf_member_hide' ) ) ) );
|
||||
|
||||
if ( \IPS\Settings::i()->quick_register )
|
||||
if ( \IPS\Login::registrationType() != 'full' )
|
||||
{
|
||||
/* Quick register is enabled, so do not allow required to be set */
|
||||
if ( isset( $form->elements['']['pf_not_null'] ) )
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Facebook Profile Sync
|
||||
* @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 13 Jun 2013
|
||||
*/
|
||||
|
||||
namespace IPS\core\ProfileSync;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Facebook Profile Sync
|
||||
*/
|
||||
class _Facebook extends ProfileSyncAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Login handler key
|
||||
*/
|
||||
public static $loginKey = 'Facebook';
|
||||
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'facebook-square';
|
||||
|
||||
/**
|
||||
* Is connected?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected()
|
||||
{
|
||||
return ( $this->member->fb_uid and $this->member->fb_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get photo
|
||||
*
|
||||
* @return \IPS\Http\Url|\IPS\File|NULL
|
||||
*/
|
||||
public function photo()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}/picture?type=large&redirect=false" )->request()->get()->decodeJson();
|
||||
if ( isset( $response['data']['is_silhouette'] ) AND $response['data']['is_silhouette'] === false )
|
||||
{
|
||||
$file = \IPS\Http\Url::external( $response['data']['url'] )->import( 'core_Profile' );
|
||||
$photo = \IPS\Image::create( $file->contents() );
|
||||
|
||||
if ( ( $photo->width > $photo->height ) OR ( $photo->height > $photo->width ) )
|
||||
{
|
||||
if ( $photo->width > $photo->height )
|
||||
{
|
||||
$difference = $photo->width - $photo->height;
|
||||
$photo->cropToPoints( ceil( $difference / 2 ), 0, ceil( $photo->width - ( $difference / 2 ) ), $photo->height );
|
||||
$file->replace( (string) $photo );
|
||||
}
|
||||
else if ( $photo->height > $photo->width )
|
||||
{
|
||||
$difference = $photo->height - $photo->width;
|
||||
$photo->cropToPoints( 0, ceil( $difference / 2 ), $photo->width, ceil( $photo->height - ( $difference / 2 ) ) );
|
||||
$file->replace( (string) $photo );
|
||||
}
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cover photo
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
*/
|
||||
public function cover()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}" )->setQueryString( array( 'access_token' => $this->member->fb_token, 'fields' => 'cover' ) )->request()->get()->decodeJson();
|
||||
if ( isset( $response['cover'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $response['cover']['source'] )->import( 'core_Profile' );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}" )->setQueryString( 'access_token', $this->member->fb_token )->request()->get()->decodeJson();
|
||||
if ( isset( $response['name'] ) )
|
||||
{
|
||||
return $response['name'];
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Member can import statuses?
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function canImportStatus( \IPS\Member $member )
|
||||
{
|
||||
if ( parent::canImportStatus( $member ) )
|
||||
{
|
||||
$loginHandlers = \IPS\Login::handlers( TRUE );
|
||||
if ( isset( $loginHandlers['Facebook'] ) AND isset( $loginHandlers['Facebook']->settings['allow_status_import'] ) )
|
||||
{
|
||||
return $loginHandlers['Facebook']->settings['allow_status_import'];
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status
|
||||
*
|
||||
* @return \IPS\core\Statuses\Status|null
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}/feed" )->setQueryString( 'access_token', $this->member->fb_token )->request()->get()->decodeJson();
|
||||
|
||||
if ( !empty( $response['data'] ) )
|
||||
{
|
||||
foreach( $response['data'] as $statusData )
|
||||
{
|
||||
if( isset( $statusData['message'] ) and ! isset( $statusData['story'] ) )
|
||||
{
|
||||
$status = \IPS\core\Statuses\Status::createItem( $this->member, $this->member->ip_address, new \IPS\DateTime( $statusData['created_time'] ) );
|
||||
$status->content = $this->_parseStatusText( nl2br( $statusData['message'], FALSE ) );
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _disassociate()
|
||||
{
|
||||
$this->member->fb_uid = 0;
|
||||
$this->member->fb_token = NULL;
|
||||
$this->member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where clause to determine whether a member account is linked to this service
|
||||
*
|
||||
* @param bool $invert Returns clause for members not linked
|
||||
* @return array|FALSE
|
||||
*/
|
||||
public static function memberFilterLinkedWhere( $invert=FALSE )
|
||||
{
|
||||
return $invert ? array( 'fb_uid=?', 0 ) : array( 'fb_uid!=?', 0 );
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Google Profile Sync
|
||||
* @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 13 Jun 2013
|
||||
*/
|
||||
|
||||
namespace IPS\core\ProfileSync;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Profile Sync
|
||||
*/
|
||||
class _Google extends ProfileSyncAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Login handler key
|
||||
*/
|
||||
public static $loginKey = 'Google';
|
||||
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'google-plus';
|
||||
|
||||
/**
|
||||
* @brief Authorization token
|
||||
*/
|
||||
protected $authToken = NULL;
|
||||
|
||||
/**
|
||||
* @brief User data
|
||||
*/
|
||||
protected $user = NULL;
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function user()
|
||||
{
|
||||
if ( $this->user === NULL and $this->member->google_token )
|
||||
{
|
||||
try
|
||||
{
|
||||
$loginHandler = \IPS\Login\LoginAbstract::load('google');
|
||||
|
||||
$response = \IPS\Http\Url::external( 'https://accounts.google.com/o/oauth2/token' )->request()->post( array(
|
||||
'client_id' => $loginHandler->settings['client_id'],
|
||||
'client_secret' => $loginHandler->settings['client_secret'],
|
||||
'refresh_token' => $this->member->google_token,
|
||||
'grant_type' => 'refresh_token'
|
||||
) )->decodeJson();
|
||||
|
||||
if ( isset( $response['access_token'] ) )
|
||||
{
|
||||
$this->authToken = $response['access_token'];
|
||||
$this->user = $this->get( 'people/' . $this->member->google_id );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
$this->member->google_token = NULL;
|
||||
$this->member->save();
|
||||
|
||||
\IPS\Log::log( $e, 'google' );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is connected?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected()
|
||||
{
|
||||
return (bool) ( $this->member->google_id and $this->member->google_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get photo
|
||||
*
|
||||
* @return \IPS\Http\Url|\IPS\File|NULL
|
||||
*/
|
||||
public function photo()
|
||||
{
|
||||
try
|
||||
{
|
||||
$user = $this->user();
|
||||
if ( isset( $user['image'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( preg_replace( '/\?sz=\d+$/', '', $user['image']['url'] ) );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'google' );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cover photo
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
*/
|
||||
public function cover()
|
||||
{
|
||||
try
|
||||
{
|
||||
$user = $this->user();
|
||||
if ( isset( $user['cover'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $user['cover']['coverPhoto']['url'] )->import( 'core_Profile' );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'google' );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
if ( isset( $user['displayName'] ) )
|
||||
{
|
||||
return $user['displayName'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status
|
||||
*
|
||||
* @return \IPS\core\Statuses\Status|null
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->user();
|
||||
$statuses = $this->get( "people/me/activities/public" );
|
||||
if ( isset( $statuses['items'] ) )
|
||||
{
|
||||
foreach ( $statuses['items'] as $status )
|
||||
{
|
||||
if ( $status['object']['content'] )
|
||||
{
|
||||
$statusObj = \IPS\core\Statuses\Status::createItem( $this->member, $this->member->ip_address, new \IPS\DateTime( $status['published'] ) );
|
||||
$statusObj->content = $this->_parseStatusText( nl2br( $status['object']['content'], FALSE ) );
|
||||
return $statusObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'google' );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _disassociate()
|
||||
{
|
||||
$this->member->google_id = 0;
|
||||
$this->member->google_token = NULL;
|
||||
$this->member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API data
|
||||
*
|
||||
* @param string $uri The API URI
|
||||
* @return array
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
*/
|
||||
protected function get( $uri )
|
||||
{
|
||||
return \IPS\Http\Url::external( 'https://www.googleapis.com/plus/v1/' . $uri )->request()->setHeaders( array( 'Authorization' => "Bearer {$this->authToken}" ) )->get()->decodeJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where clause to determine whether a member account is linked to this service
|
||||
*
|
||||
* @param bool $invert Returns clause for members not linked
|
||||
* @return array|FALSE
|
||||
*/
|
||||
public static function memberFilterLinkedWhere( $invert=FALSE )
|
||||
{
|
||||
return $invert ? array( '( google_id IS NULL or google_id = 0 )' ) : array( 'google_id > 0' );
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief LinkedIn Profile Sync
|
||||
* @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 13 Jun 2013
|
||||
*/
|
||||
|
||||
namespace IPS\core\ProfileSync;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* LinkedIn Profile Sync
|
||||
*/
|
||||
class _Linkedin extends ProfileSyncAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Login handler key
|
||||
*/
|
||||
public static $loginKey = 'Linkedin';
|
||||
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'linkedin';
|
||||
|
||||
/**
|
||||
* @brief User data
|
||||
*/
|
||||
protected $user = NULL;
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function user()
|
||||
{
|
||||
if ( $this->user === NULL and $this->member->linkedin_token )
|
||||
{
|
||||
$this->user = array();
|
||||
try
|
||||
{
|
||||
foreach ( \IPS\Http\Url::external( "https://api.linkedin.com/v1/people/{$this->member->linkedin_id}:(formatted-name,picture-url,current-status)?oauth2_access_token={$this->member->linkedin_token}" )->request()->get()->decodeXml() as $k => $v )
|
||||
{
|
||||
$this->user[ $k ] = (string) $v;
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
$this->member->linkedin_token = NULL;
|
||||
$this->member->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is connected?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected()
|
||||
{
|
||||
return $this->member->linkedin_id and $this->member->linkedin_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get photo
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
*/
|
||||
public function photo()
|
||||
{
|
||||
$user = $this->user();
|
||||
if ( isset( $user['picture-url'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $user['picture-url'] );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
$user = $this->user();
|
||||
if ( isset( $user['formatted-name'] ) )
|
||||
{
|
||||
return $user['formatted-name'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _disassociate()
|
||||
{
|
||||
$this->member->linkedin_id = 0;
|
||||
$this->member->linkedin_token = NULL;
|
||||
$this->member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where clause to determine whether a member account is linked to this service
|
||||
*
|
||||
* @param bool $invert Returns clause for members not linked
|
||||
* @return array|FALSE
|
||||
*/
|
||||
public static function memberFilterLinkedWhere( $invert=FALSE )
|
||||
{
|
||||
return $invert ? array( '( linkedin_id IS NULL or linkedin_id = 0 )' ) : array( 'linkedin_id > 0' );
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Microsoft Profile Sync
|
||||
* @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 13 Jun 2013
|
||||
*/
|
||||
|
||||
namespace IPS\core\ProfileSync;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Microsoft Profile Sync
|
||||
*/
|
||||
class _Microsoft extends ProfileSyncAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Login handler key
|
||||
*/
|
||||
public static $loginKey = 'Live';
|
||||
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'windows';
|
||||
|
||||
/**
|
||||
* @brief Authorization token
|
||||
*/
|
||||
protected $authToken = NULL;
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function authToken()
|
||||
{
|
||||
if ( $this->authToken === NULL and $this->member->live_token )
|
||||
{
|
||||
try
|
||||
{
|
||||
$loginHandler = \IPS\Login\LoginAbstract::load('live');
|
||||
|
||||
$response = \IPS\Http\Url::external( 'https://login.live.com/oauth20_token.srf' )->request()->post( array(
|
||||
'client_id' => $loginHandler->settings['client_id'],
|
||||
'client_secret' => $loginHandler->settings['client_secret'],
|
||||
'refresh_token' => $this->member->live_token,
|
||||
'grant_type' => 'refresh_token'
|
||||
) )->decodeJson();
|
||||
|
||||
if ( isset( $response['access_token'] ) and isset( $response['refresh_token'] ) )
|
||||
{
|
||||
$this->authToken = $response['access_token'];
|
||||
$this->member->live_token = $response['refresh_token'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->member->live_token = NULL;
|
||||
}
|
||||
|
||||
$this->member->save();
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
$this->member->live_token = NULL;
|
||||
$this->member->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is connected?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected()
|
||||
{
|
||||
return (bool) ( $this->member->live_id and $this->member->live_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get photo
|
||||
*
|
||||
* @return \IPS\Http\Url|null
|
||||
*/
|
||||
public function photo()
|
||||
{
|
||||
return \IPS\Http\Url::external( "https://apis.live.net/v5.0/{$this->member->live_id}/picture?type=large" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
try
|
||||
{
|
||||
$user = \IPS\Http\Url::external( "https://apis.live.net/v5.0/{$this->member->live_id}?access_token={$this->authToken()}" )->request()->get()->decodeJson();
|
||||
if ( isset( $user['name'] ) )
|
||||
{
|
||||
return $user['name'];
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _disassociate()
|
||||
{
|
||||
$this->member->live_id = 0;
|
||||
$this->member->live_token = NULL;
|
||||
$this->member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where clause to determine whether a member account is linked to this service
|
||||
*
|
||||
* @param bool $invert Returns clause for members not linked
|
||||
* @return array|FALSE
|
||||
*/
|
||||
public static function memberFilterLinkedWhere( $invert=FALSE )
|
||||
{
|
||||
return $invert ? array( '( live_id IS NULL OR live_id = 0 )' ) : array( 'live_id > 0' );
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Profile Sync Abstract Class
|
||||
* @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 13 Jun 2013
|
||||
*/
|
||||
|
||||
namespace IPS\core\ProfileSync;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile Sync Interface
|
||||
*/
|
||||
abstract class _ProfileSyncAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Login handlers
|
||||
*/
|
||||
protected static $loginHandlers = NULL;
|
||||
|
||||
/**
|
||||
* Get services
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function services()
|
||||
{
|
||||
$services = array();
|
||||
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . '/applications/core/sources/ProfileSync' ) as $file )
|
||||
{
|
||||
if ( !$file->isDot() and mb_substr( $file, -4 ) === '.php' and mb_substr( $file, 0, 2 ) != '._' )
|
||||
{
|
||||
if ( class_exists( 'IPS\core\ProfileSync\\' . mb_substr( $file, 0, -4 ) ) )
|
||||
{
|
||||
$class = 'IPS\core\ProfileSync\\' . mb_substr( $file, 0, -4 );
|
||||
|
||||
if ( $class::enabled() )
|
||||
{
|
||||
$services[ mb_substr( $file, 0, -4 ) ] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enabled?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function enabled()
|
||||
{
|
||||
if ( isset( static::$loginKey ) )
|
||||
{
|
||||
if ( static::$loginHandlers === NULL )
|
||||
{
|
||||
static::$loginHandlers = iterator_to_array( \IPS\Db::i()->select( '*', 'core_login_handlers' )->setKeyField( 'login_key' ) );
|
||||
}
|
||||
|
||||
if ( isset( static::$loginHandlers[ static::$loginKey ] ) )
|
||||
{
|
||||
return (bool) static::$loginHandlers[ static::$loginKey ]['login_enabled'];
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Member
|
||||
*/
|
||||
protected $member;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* \IPS\Member $member The member
|
||||
*/
|
||||
public function __construct( \IPS\Member $member )
|
||||
{
|
||||
$this->member = $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is connected?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function connected();
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function name();
|
||||
|
||||
/**
|
||||
* Member can import statuses?
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function canImportStatus( \IPS\Member $member )
|
||||
{
|
||||
return \IPS\Settings::i()->profile_comments and method_exists( $this, 'status' ) and $member->canAccessModule( \IPS\Application\Module::get( 'core', 'members', 'front' ) ) and $member->pp_setting_count_comments and !$member->members_bitoptions['bw_no_status_update'] and !$member->group['gbw_no_status_import'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function settings()
|
||||
{
|
||||
$settings = $this->member->profilesync ? json_decode( $this->member->profilesync, TRUE ) : array();
|
||||
return ( isset( $settings[ static::$loginKey ] ) ? $settings[ static::$loginKey ] : array(
|
||||
'photo' => FALSE,
|
||||
'cover' => FALSE,
|
||||
'status' => ''
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings as string
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function settingsDesc()
|
||||
{
|
||||
$settings = $this->settings();
|
||||
$return = array();
|
||||
if ( $settings['photo'] or $settings['cover'] )
|
||||
{
|
||||
$_return = array();
|
||||
if ( $settings['photo'] )
|
||||
{
|
||||
$_return[] = \IPS\Member::loggedIn()->language()->addToStack('sync_settings_photo');
|
||||
}
|
||||
if ( $settings['cover'] )
|
||||
{
|
||||
$_return[] = \IPS\Member::loggedIn()->language()->addToStack('sync_settings_cover');
|
||||
}
|
||||
|
||||
if ( count( $_return ) > 1 )
|
||||
{
|
||||
$return[] = \IPS\Member::loggedIn()->language()->formatList( $_return, \IPS\Member::loggedIn()->language()->get('sync_settings_syncing') );
|
||||
}
|
||||
else
|
||||
{
|
||||
$return[] = $_return[0];
|
||||
}
|
||||
}
|
||||
if ( $settings['status'] and \IPS\Settings::i()->profile_comments )
|
||||
{
|
||||
$langKey = 'sync_settings_status_' . $settings['status'];
|
||||
$return[] = \IPS\Member::loggedIn()->language()->addToStack( $langKey );
|
||||
}
|
||||
|
||||
return empty( $return ) ? \IPS\Member::loggedIn()->language()->addToStack('profilesync_not_syncing') : \IPS\Member::loggedIn()->language()->formatList( $return );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Settings
|
||||
*
|
||||
* @param array $values Values from form
|
||||
* @return void
|
||||
*/
|
||||
public function save( $values )
|
||||
{
|
||||
/* Get existing settings */
|
||||
$settings = $this->member->profilesync ? json_decode( $this->member->profilesync, TRUE ) : array(
|
||||
static::$loginKey => array(
|
||||
'photo' => FALSE,
|
||||
'cover' => FALSE,
|
||||
'status' => ''
|
||||
)
|
||||
);
|
||||
|
||||
/* Set the new values for this service */
|
||||
$settings[ static::$loginKey ] = array(
|
||||
'photo' => isset( $values['profilesync_photo'] ) ? $values['profilesync_photo'] : ( isset( $settings[ static::$loginKey ]['photo'] ) ? $settings[ static::$loginKey ]['photo'] : FALSE ),
|
||||
'cover' => isset( $values['profilesync_cover'] ) ? $values['profilesync_cover'] : ( isset( $settings[ static::$loginKey ]['cover'] ) ? $settings[ static::$loginKey ]['cover'] : FALSE ),
|
||||
'status' => isset( $values['profilesync_status'] ) ? ( $values['profilesync_status'] ? 'import' : '' ) : ( isset( $settings[ static::$loginKey ]['status'] ) ? $settings[ static::$loginKey ]['status'] : FALSE ),
|
||||
);
|
||||
|
||||
/* We cannot have more than one service syncing photos/cover photos - otherwise they conflict */
|
||||
foreach ( array( 'photo', 'cover' ) as $type )
|
||||
{
|
||||
if ( $settings[ static::$loginKey ][ $type ] )
|
||||
{
|
||||
foreach ( $settings as $service => $serviceSettings )
|
||||
{
|
||||
if ( $service != static::$loginKey and $serviceSettings[ $type ] )
|
||||
{
|
||||
$settings[ $service ][ $type ] = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Finalise */
|
||||
$this->member->profilesync = json_encode( $settings );
|
||||
|
||||
/* If we're enabling status import, reset last sync time so we import the latest status */
|
||||
if ( isset( $values['profilesync_status'] ) AND $values['profilesync_status'] == 'import' )
|
||||
{
|
||||
$this->member->profilesync_lastsync = 0;
|
||||
}
|
||||
|
||||
$this->member->save();
|
||||
$this->sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sync()
|
||||
{
|
||||
/* Don't sync for banned users */
|
||||
if( $this->member->isBanned() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = $this->settings();
|
||||
if ( $settings['photo'] )
|
||||
{
|
||||
$class = get_called_class();
|
||||
try
|
||||
{
|
||||
if ( $photo = $this->photo() )
|
||||
{
|
||||
if ( $photo instanceof \IPS\File )
|
||||
{
|
||||
$this->member->pp_main_photo = (string) $photo;
|
||||
$this->member->pp_thumb_photo = NULL;
|
||||
$this->member->pp_photo_type = 'custom';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->member->pp_main_photo = (string) $photo;
|
||||
$this->member->pp_thumb_photo = NULL;
|
||||
$this->member->pp_photo_type = 'sync-' . mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \RuntimeException $e ) {}
|
||||
}
|
||||
if ( $settings['cover'] and $this->member->group['gbw_allow_upload_bgimage'] )
|
||||
{
|
||||
/* Download and import the synced cover photo */
|
||||
try
|
||||
{
|
||||
$coverPhoto = (string) $this->cover();
|
||||
}
|
||||
catch ( \RuntimeException $e ) {}
|
||||
|
||||
/* Did we get a cover photo returned? */
|
||||
if ( !empty( $coverPhoto ) )
|
||||
{
|
||||
/* Delete any existing */
|
||||
if ( $this->member->pp_cover_photo )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( 'core_Profile', $this->member->pp_cover_photo )->delete();
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
/* Save */
|
||||
$this->member->pp_cover_photo = $coverPhoto;
|
||||
}
|
||||
}
|
||||
/* We check against canImportStatus() to ensure we check all appropriate restrictions, including group and member restrictions */
|
||||
if ( $settings['status'] and $settings['status'] == 'import' and $this->canImportStatus( $this->member ) )
|
||||
{
|
||||
if ( $status = $this->status() )
|
||||
{
|
||||
if ( $status->date > $this->member->profilesync_lastsync )
|
||||
{
|
||||
try
|
||||
{
|
||||
$status->member_id = $this->member->member_id;
|
||||
$status->imported = TRUE;
|
||||
$status->save();
|
||||
|
||||
\IPS\Content\Search\Index::i()->index( $status );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
// May fail if status contains invalid data (like utf8mb4 characters if the database doesn't support that, etc)
|
||||
// In this case, we will just skip over the status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->member->profilesync_lastsync = time();
|
||||
$this->member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disassociate()
|
||||
{
|
||||
$settings = $this->member->profilesync ? json_decode( $this->member->profilesync, TRUE ) : array();
|
||||
$class = get_called_class();
|
||||
unset( $settings[ $class::$loginKey ] );
|
||||
$this->member->profilesync = empty( $settings ) ? NULL : json_encode( $settings );
|
||||
|
||||
$this->_disassociate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse status text - ensures valid and safe HTML, filters profanity, etc.
|
||||
*
|
||||
* @param string $value
|
||||
* @return void
|
||||
*/
|
||||
protected function _parseStatusText( $value )
|
||||
{
|
||||
/* Make sure utf8mb4 characters won't cause us issues */
|
||||
$value = \IPS\Text\Parser::utf8mb4SafeDecode( $value );
|
||||
|
||||
/* Parse */
|
||||
$value = \IPS\Text\Parser::parseStatic( $value, FALSE, NULL, $this->member, 'core_Members' );
|
||||
|
||||
/* Return */
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where clause to determine whether a member account is linked to this service
|
||||
*
|
||||
* @param bool $invert Returns clause for members not linked
|
||||
* @return array|FALSE
|
||||
*/
|
||||
public static function memberFilterLinkedWhere( $invert=FALSE )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Twitter Profile Sync
|
||||
* @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 13 Jun 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\ProfileSync;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Profile Sync
|
||||
*/
|
||||
class _Twitter extends ProfileSyncAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Login handler key
|
||||
*/
|
||||
public static $loginKey = 'Twitter';
|
||||
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'twitter';
|
||||
|
||||
/**
|
||||
* @brief User data
|
||||
*/
|
||||
protected $user = NULL;
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function user()
|
||||
{
|
||||
if ( ( $this->user === NULL ) and $this->member->twitter_token )
|
||||
{
|
||||
try
|
||||
{
|
||||
$loginHandler = \IPS\Login\LoginAbstract::load( 'twitter' );
|
||||
|
||||
$this->user = $loginHandler->sendRequest(
|
||||
'get', "https://api.twitter.com/1.1/account/verify_credentials.json", array(),
|
||||
$this->member->twitter_token, $this->member->twitter_secret
|
||||
)->decodeJson();
|
||||
|
||||
if ( isset( $this->user['errors'] ) )
|
||||
{
|
||||
throw new \Exception;
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
$this->member->twitter_token = '';
|
||||
$this->member->twitter_secret = '';
|
||||
$this->member->save();
|
||||
|
||||
\IPS\Log::log( isset( $this->user['errors'] ) ? var_export( $this->user['errors'], true ) : $e, 'twitter' );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is connected?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected()
|
||||
{
|
||||
return ( $this->member->twitter_id and $this->member->twitter_token and $this->member->twitter_secret );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get photo
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
public function photo()
|
||||
{
|
||||
return $this->resourceUrl( $this->user(), 'profile_image_url' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cover photo
|
||||
*
|
||||
* @return \IPS\File|NULL
|
||||
*/
|
||||
public function cover()
|
||||
{
|
||||
if ( $coverUrl = $this->resourceUrl( $this->user(), 'profile_background_image_url' ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
return $coverUrl->import( 'core_Profile' );
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'twitter' );
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
$user = $this->user();
|
||||
if ( isset( $user['screen_name'] ) )
|
||||
{
|
||||
return $user['screen_name'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status
|
||||
*
|
||||
* @return \IPS\core\Statuses\Status|null
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
if ( !empty( $user['status'] ) )
|
||||
{
|
||||
$status = \IPS\core\Statuses\Status::createItem(
|
||||
$this->member, $this->member->ip_address, new \IPS\DateTime( $user['status']['created_at'] )
|
||||
);
|
||||
|
||||
$status->content = $this->_parseStatusText( $user['status']['text'] );
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _disassociate()
|
||||
{
|
||||
$this->member->twitter_id = 0;
|
||||
$this->member->twitter_token = '';
|
||||
$this->member->twitter_secret = '';
|
||||
$this->member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an HTTPS or HTTP resource URL (HTTPS if available and we are using it locally, otherwise HTTP)
|
||||
*
|
||||
* @param array $user
|
||||
* @param string $fieldName
|
||||
* @return \IPS\Http\Url|NULL
|
||||
*/
|
||||
protected function resourceUrl( $user, $fieldName )
|
||||
{
|
||||
$scheme = \IPS\Http\Url::internal( '' )->data['scheme'];
|
||||
|
||||
if ( ( $scheme === 'https' ) or \IPS\Settings::i()->logins_over_https )
|
||||
{
|
||||
if ( !empty( $user[ $fieldName . '_https' ] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $user[ $fieldName . '_https' ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !empty( $user[ $fieldName ] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $user[ $fieldName ] );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where clause to determine whether a member account is linked to this service
|
||||
*
|
||||
* @param bool $invert Returns clause for members not linked
|
||||
* @return array|FALSE
|
||||
*/
|
||||
public static function memberFilterLinkedWhere( $invert=FALSE )
|
||||
{
|
||||
return $invert ? array( '( twitter_id IS NULL or twitter_id = 0 )' ) : array( 'twitter_id > 0' );
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -183,6 +183,27 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
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
|
||||
*
|
||||
@@ -221,6 +242,23 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
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
|
||||
*
|
||||
@@ -330,17 +368,30 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( $object instanceof \IPS\Content\Item )
|
||||
{
|
||||
if ( isset( $object::$databaseColumnMap['num_comments'] ) and isset( $object::$commentClass ) )
|
||||
try
|
||||
{
|
||||
$container = $object->container();
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
|
||||
if ( $object::supportsComments( NULL, $container ) )
|
||||
{
|
||||
$count = $object->mapped('num_comments');
|
||||
if ( $count AND isset( $object::$firstCommentRequired ) )
|
||||
|
||||
if ( $count AND isset( $object::$firstCommentRequired ) AND $object::$firstCommentRequired === TRUE )
|
||||
{
|
||||
$count--;
|
||||
}
|
||||
|
||||
$commentClass = $object::$commentClass;
|
||||
return array( 'count' => $count, 'words' => \IPS\Member::loggedIn()->language()->addToStack( 'num_replies', NULL, array( 'pluralize' => array( $count ) ) ) );
|
||||
}
|
||||
|
||||
if ( $object::supportsReviews( NULL, $container ) )
|
||||
{
|
||||
$count = $object->mapped('num_reviews');
|
||||
|
||||
return array( 'count' => $count, 'words' => \IPS\Member::loggedIn()->language()->addToStack( 'num_reviews', NULL, array( 'pluralize' => array( $count ) ) ) );
|
||||
}
|
||||
|
||||
/* Valid object, but there isn't any date data available */
|
||||
return NULL;
|
||||
@@ -351,7 +402,13 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
else if ( $object instanceof \IPS\Node\Model )
|
||||
{
|
||||
/* Valid object, but there isn't any date data available */
|
||||
if( $object->_items !== NULL )
|
||||
{
|
||||
$count = $object->_items;
|
||||
|
||||
return array( 'count' => $count, 'words' => \IPS\Member::loggedIn()->language()->addToStack( $object->_countLanguageString, NULL, array( 'pluralize' => array( $count ) ) ) );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -361,38 +418,143 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Returns "Foo posted {{indefart}} in {{container}}, {{date}}
|
||||
*
|
||||
* @return array|null
|
||||
* @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 )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_container', NULL, array( 'htmlsprintf' => array( $author->link(), $object->indefiniteArticle(), $container->url(), $container->_title, $this->objectDatePosted->html( FALSE ) ) ) );
|
||||
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->_title, $author->name, $this->objectDatePosted->relative( \IPS\DateTime::RELATIVE_FORMAT_NORMAL, $plaintextLanguage ) ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', NULL, array( 'htmlsprintf' => array( $author->link(), $object->indefiniteArticle(), $this->objectDatePosted->html( FALSE ) ) ) );
|
||||
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 )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', NULL, array( 'htmlsprintf' => array( $author->link(), $object->indefiniteArticle(), $this->objectDatePosted->html( FALSE ) ) ) );
|
||||
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 )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_node', NULL, array( 'htmlsprintf' => array( $author->link(), $object->url(), $object->_title ) ) );
|
||||
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->_title, $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
|
||||
*
|
||||
@@ -405,6 +567,13 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
$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 )
|
||||
{
|
||||
@@ -431,12 +600,10 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
/* Successful post ID returned */
|
||||
$returned[ $service ] = $serviceObject->post( $this );
|
||||
$this->failed = 0;
|
||||
}
|
||||
catch( \Exception $ex )
|
||||
{
|
||||
/* It failed, increment counter and set the next attempt for 10 minutes time */
|
||||
$this->failed++;
|
||||
$hasFailed = true;
|
||||
$this->scheduled = time() + 600;
|
||||
$this->sent = 0;
|
||||
|
||||
@@ -444,11 +611,25 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* Full response stored */
|
||||
$response['response_json'] = json_encode( $serviceObject->response, TRUE );
|
||||
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 );
|
||||
\IPS\Db::i()->insert( 'core_social_promote_content', $response );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->failed = ( $hasFailed ) ? $this->failed + 1 : 0;
|
||||
$this->returned = $returned;
|
||||
$this->save();
|
||||
}
|
||||
@@ -572,7 +753,7 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$text = $this->text;
|
||||
|
||||
return isset( $text[ $service ] ) ? ( $forDisplay ? nl2br( $text[ $service ] ) : $text[ $service ] ) : NULL;
|
||||
return isset( $text[ $service ] ) ? ( $forDisplay ? nl2br( htmlspecialchars( $text[ $service ], ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) ) : $text[ $service ] ) : NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -657,7 +838,7 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
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['response_date'];
|
||||
$this->history[ $this->id ][ $row['response_sent_date'] ][ $row['response_promote_key'] ][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +864,6 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Promote stream of internally promoted items
|
||||
*
|
||||
* @param array $services Array of services by key (internal, facebook, etc)
|
||||
* @param int|array $limit Number of items to fetch
|
||||
* @param string $sortField Sort by field
|
||||
* @param string $sortDirection Sort by direction (asc, desc)
|
||||
@@ -756,7 +936,7 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
throw new \OutofRangeException('object_not_valid');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a list of groups that cannot see this item
|
||||
*
|
||||
@@ -826,31 +1006,52 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public static function objectContent( $object )
|
||||
{
|
||||
$result = NULL;
|
||||
|
||||
if ( $object instanceof \IPS\Content\Item )
|
||||
{
|
||||
if ( isset( $object::$databaseColumnMap['content'] ) )
|
||||
{
|
||||
return trim( str_replace( "<br>", "\n", $object->truncated() ) );
|
||||
$result = $object->truncated();
|
||||
}
|
||||
else if ( $object::$firstCommentRequired )
|
||||
{
|
||||
$firstComment = $object->comments( 1, NULL, 'date', 'asc' );
|
||||
|
||||
return trim( preg_replace( "#(<br>){1,}#", "\n", preg_replace( '#(<br>)\s+#', "\n", $firstComment->truncated() ) ) );
|
||||
$result = $firstComment->truncated();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
else if ( $object instanceof \IPS\Content\Comment )
|
||||
{
|
||||
return trim( preg_replace( "#(<br>){1,}#", "\n", preg_replace( '#(<br>)\s+#', "\n", $object->truncated() ) ) );
|
||||
$result = $object->truncated();
|
||||
}
|
||||
else if ( $object instanceof \IPS\Node\Model )
|
||||
{
|
||||
return trim( strip_tags( preg_replace( "#(<br>){1,}#", "\n", preg_replace( '#(<br>)\s+#', "\n", $object->description ) ) ) );
|
||||
$result = $object->description;
|
||||
}
|
||||
|
||||
throw new \OutofRangeException('object_not_valid');
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -926,8 +1127,8 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
*
|
||||
* @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
|
||||
* @throws \RuntimeException if shortener fails
|
||||
* @throws \UnderflowException if no shortener available
|
||||
*/
|
||||
public static function shortenUrl( $longUrl )
|
||||
{
|
||||
@@ -957,7 +1158,7 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Get the next auto schedule timestamp
|
||||
*
|
||||
* @return null|DateTime object
|
||||
* @return null|\IPS\DateTime object
|
||||
*/
|
||||
public static function getNextAutoSchedule()
|
||||
{
|
||||
@@ -1002,11 +1203,11 @@ class _Promote extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
return $time;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a single promote object
|
||||
*
|
||||
* @throws UnderflowException
|
||||
* @param $key string Promote Service
|
||||
* @return \IPS\Login
|
||||
*/
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class _Table extends \IPS\Helpers\Table\Table
|
||||
*/
|
||||
public function setMember( \IPS\Member $member )
|
||||
{
|
||||
$this->where[] = array( 'member=?', $member->member_id );
|
||||
$this->where[] = array( 'promote_added_by=?', $member->member_id );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,10 +109,23 @@ class _Comment extends \IPS\Content\Comment
|
||||
public function url( $action='find' )
|
||||
{
|
||||
$url = parent::url( $action );
|
||||
$idColumn = static::$databaseColumnId;
|
||||
|
||||
if ( isset( $url->queryString['do'] ) )
|
||||
{
|
||||
return $url->setQueryString( array( 'do' => NULL, 'action' => $url->queryString['do'] ) );
|
||||
return $url->stripQueryString( 'do' )->setQueryString( array( 'action' => $url->queryString['do'], 'comment' => $this->$idColumn ) );
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can promote this comment/item?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return boolean
|
||||
*/
|
||||
public function canPromoteToSocialMedia( $member=NULL )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,12 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
|
||||
{
|
||||
/**
|
||||
* @brief Const No type selected
|
||||
*/
|
||||
const TYPE_MESSAGE = 0;
|
||||
|
||||
|
||||
/* !\IPS\Patterns\ActiveRecord */
|
||||
|
||||
/**
|
||||
@@ -103,7 +109,25 @@ class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load by class and content_id
|
||||
*
|
||||
* @return \IPS\core\Reports\Report
|
||||
* @throws OutofRangeException
|
||||
*/
|
||||
public static function loadByClassAndId( $class, $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
return static::constructFromData( \IPS\Db::i()->select( '*', 'core_rc_index', array( 'class=? and content_id=?', $class, $id ) )->first() );
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
throw new \OutofRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mapped value
|
||||
*
|
||||
@@ -199,11 +223,11 @@ class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
|
||||
foreach( \IPS\Db::i()->select(
|
||||
"{$databaseTable}.{$class::$databasePrefix}{$class::$databaseColumnId} as commentId, {$databaseTable}.{$class::$databasePrefix}{$class::$databaseColumnMap['item']} AS itemId, {$itemClass::$databaseTable}.{$itemClass::$databasePrefix}{$itemTitleField} AS title",
|
||||
$databaseTable,
|
||||
\IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnId, array_keys( $objects ) )
|
||||
)->join(
|
||||
$itemDatabaseTable,
|
||||
"{$itemDatabaseTable}.{$itemClass::$databasePrefix}{$itemClass::$databaseColumnId}={$databaseTable}.{$class::$databasePrefix}{$class::$databaseColumnMap['item']}"
|
||||
)->setKeyField( 'commentId' ) as $k => $data
|
||||
\IPS\Db::i()->in( $databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId, array_keys( $objects ) )
|
||||
)->join(
|
||||
$itemDatabaseTable,
|
||||
"{$itemDatabaseTable}.{$itemClass::$databasePrefix}{$itemClass::$databaseColumnId}={$databaseTable}.{$class::$databasePrefix}{$class::$databaseColumnMap['item']}"
|
||||
)->setKeyField( 'commentId' ) as $k => $data
|
||||
)
|
||||
{
|
||||
$objects[ $k ]->_data = array_merge( $objects[ $k ]->_data, $data );
|
||||
@@ -214,8 +238,8 @@ class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
|
||||
foreach( \IPS\Db::i()->select(
|
||||
"{$class::$databasePrefix}{$class::$databaseColumnId} as itemId, {$class::$databasePrefix}{$class::$databaseColumnMap['title']} AS title",
|
||||
$class::$databaseTable,
|
||||
\IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnId, array_keys( $objects ) )
|
||||
)->setKeyField( 'itemId' ) as $k => $data
|
||||
\IPS\Db::i()->in( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId, array_keys( $objects ) )
|
||||
)->setKeyField( 'itemId' ) as $k => $data
|
||||
)
|
||||
{
|
||||
$objects[ $k ]->_data = array_merge( $objects[ $k ]->_data, $data );
|
||||
@@ -322,6 +346,16 @@ class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
|
||||
{
|
||||
$this->status = mb_substr( $action, -1 );
|
||||
$this->save();
|
||||
|
||||
/* Post a comment on the report */
|
||||
$content = \IPS\Member::loggedIn()->language()->addToStack( 'update_report_status_content', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'report_status_' . $this->status ) ) ) );
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $content );
|
||||
|
||||
$comment = \IPS\core\Reports\Comment::create( $this, $content, TRUE, NULL, NULL, \IPS\Member::loggedIn(), new \IPS\DateTime );
|
||||
$comment->save();
|
||||
|
||||
/* And add to the moderator log */
|
||||
\IPS\Session::i()->modLog( 'modlog__action_update_report_status', array( $this->url()->__toString() => FALSE ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -380,9 +414,16 @@ class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function reports()
|
||||
public function reports( $filterByType=NULL)
|
||||
{
|
||||
return iterator_to_array( \IPS\Db::i()->select( '*', 'core_rc_reports', array( 'rid=?', $this->id ), 'date_reported' ) );
|
||||
$where = array( array( 'rid=?', $this->id ) );
|
||||
|
||||
if ( $filterByType )
|
||||
{
|
||||
$where[] = array( 'report_type=?', $filterByType );
|
||||
}
|
||||
|
||||
return iterator_to_array( \IPS\Db::i()->select( '*', 'core_rc_reports', $where, 'date_reported' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,8 +456,131 @@ class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
|
||||
parent::delete();
|
||||
|
||||
\IPS\Db::i()->delete( 'core_rc_reports', array( 'rid=?', $this->id ) );
|
||||
\IPS\Db::i()->delete( 'core_automatic_moderation_pending', array( 'pending_object_class=? and pending_object_id=?', $this->class, $this->content_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock auto moderation to prevent auto moderation from changing the status again
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function lockAutoModeration()
|
||||
{
|
||||
$this->auto_moderation_exempt = 1;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock auto moderation to prevent auto moderation from changing the status again
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function isAutoModerationLocked()
|
||||
{
|
||||
return (boolean) $this->auto_moderation_exempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run any automatic moderation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function runAutomaticModeration()
|
||||
{
|
||||
if ( ! \IPS\Settings::i()->automoderation_enabled )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* If it is auto moderation locked, skip it */
|
||||
if ( $this->isAutoModerationLocked() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$className = $this->class;
|
||||
try
|
||||
{
|
||||
$reportedContent = $className::load( $this->content_id );
|
||||
}
|
||||
catch ( \OutOfRangeException $ex )
|
||||
{
|
||||
/* No content, no moderation, no cry */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Fetch a count of report flags so far */
|
||||
$typeCounts = $this->getReportTypeCounts();
|
||||
$ruleToUse = NULL;
|
||||
|
||||
/* Loop over all group promotion rules and get the last one that matches us */
|
||||
foreach( \IPS\core\Reports\Rules::roots() as $rule )
|
||||
{
|
||||
if( $rule->enabled and $rule->matches( $reportedContent->author(), $typeCounts ) )
|
||||
{
|
||||
$ruleToUse = $rule->id;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there's no rule, return now */
|
||||
if( $ruleToUse === NULL )
|
||||
{
|
||||
/* It is possible a few reports have been removed so the threshold is no longer met, delete any pending rows if this is the case */
|
||||
\IPS\Db::i()->delete( 'core_automatic_moderation_pending', array( 'pending_object_class=? and pending_object_id=?', $className, $this->content_id ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Log the bad boy for actioning later. A small delay allows users to retract their warning */
|
||||
\IPS\Db::i()->replace( 'core_automatic_moderation_pending', array(
|
||||
'pending_object_class' => $className,
|
||||
'pending_object_id' => $this->content_id,
|
||||
'pending_report_id' => $this->id,
|
||||
'pending_added' => time(),
|
||||
'pending_rule_id' => $ruleToUse
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the report type counts
|
||||
*
|
||||
* @param boolean $totalOnly Return either an int of the total counts, or an array with the breakdown
|
||||
* @return array( 1 => 10, 2 => 3 )|INT
|
||||
*/
|
||||
public function getReportTypeCounts( $totalOnly=false )
|
||||
{
|
||||
$typeCounts = array();
|
||||
$total = 0;
|
||||
$seen = array();
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_rc_reports', array( 'rid=? and report_type > 0', $this->id ) ) as $row )
|
||||
{
|
||||
if ( isset( $seen[ $row['report_by'] ] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen[ $row['report_by'] ] = true;
|
||||
|
||||
$typeCounts[ $row['report_type'] ][ $row['report_by'] ] = true;
|
||||
}
|
||||
|
||||
$return = array();
|
||||
foreach( array_keys( \IPS\core\Reports\Types::roots() ) as $type )
|
||||
{
|
||||
if ( isset( $typeCounts[ $type ] ) )
|
||||
{
|
||||
$return[ $type ] = count( $typeCounts[ $type ] );
|
||||
$total += count( $typeCounts[ $type ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$return[ $type ] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $totalOnly ? $total : $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filters that are available for selecting table rows
|
||||
*
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Automatic Content Moderation Rules
|
||||
* @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 7 Dec 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\Reports;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group promotion node model
|
||||
*/
|
||||
class _Rules extends \IPS\Node\Model
|
||||
{
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_automatic_moderation_rules';
|
||||
|
||||
/**
|
||||
* @brief Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'rule_';
|
||||
|
||||
/**
|
||||
* @brief Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] ID Database Column
|
||||
*/
|
||||
public static $databaseColumnId = 'id';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Map
|
||||
*/
|
||||
protected static $multitonMap = array();
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'automaticmoderation';
|
||||
|
||||
/**
|
||||
* @brief [Node] Sortable
|
||||
*/
|
||||
public static $nodeSortable = TRUE;
|
||||
|
||||
/**
|
||||
* @brief [Node] Positon Column
|
||||
*/
|
||||
public static $databaseColumnOrder = 'position';
|
||||
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
|
||||
*/
|
||||
public static $titleLangPrefix = 'automaticmoderation_';
|
||||
|
||||
/**
|
||||
* @brief [Node] Enabled/Disabled Column
|
||||
*/
|
||||
public static $databaseColumnEnabledDisabled = 'enabled';
|
||||
|
||||
/**
|
||||
* Form
|
||||
*
|
||||
* @param \IPS\Helpers\Form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( &$form )
|
||||
{
|
||||
$form->addHeader( 'automaticmoderation_generic_details' );
|
||||
$form->add( new \IPS\Helpers\Form\Translatable( 'automaticmoderation_title', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? 'automaticmoderation_' . $this->id : NULL ) ) ) );
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'automaticmoderation_enabled', $this->id ? $this->enabled : 1, TRUE ) );
|
||||
$form->add( new \IPS\Helpers\Form\Number( 'automaticmoderation_points', $this->id ? $this->points_needed : 10, TRUE, array( 'min' => 1, 'max' => 9999 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('automaticmoderation_points_suffix') ) );
|
||||
|
||||
$options = array();
|
||||
foreach( \IPS\core\Reports\Types::roots() as $type )
|
||||
{
|
||||
$options[ $type->id ] = $type->_title;
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'automaticmoderation_type', $this->types ? explode( ',', $this->types ) : array(), TRUE, array( 'options' => $options ) ) );
|
||||
|
||||
/* Loop over our member filters */
|
||||
$form->addHeader( 'automaticmoderation_generic_filters' );
|
||||
$options = $this->id ? $this->_filters : array();
|
||||
|
||||
$lastApp = 'core';
|
||||
|
||||
/* We take an extra step with groups to disable invalid options */
|
||||
//$options['core_Group']['disabled_groups'] = $this->getDisabledGroups();
|
||||
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' ) as $key => $extension )
|
||||
{
|
||||
if( method_exists( $extension, 'getSettingField' ) AND $extension->availableIn( 'automatic_moderation' ) )
|
||||
{
|
||||
/* See if we need a new form header - one per app */
|
||||
$_key = explode( '_', $key );
|
||||
|
||||
if( $_key[0] != $lastApp )
|
||||
{
|
||||
$lastApp = $_key[0];
|
||||
$form->addHeader( $lastApp . '_bm_filters' );
|
||||
}
|
||||
|
||||
/* Grab our fields and add to the form */
|
||||
$fields = $extension->getSettingField( !empty( $options[ $key ] ) ? $options[ $key ] : array() );
|
||||
|
||||
foreach( $fields as $field )
|
||||
{
|
||||
$form->add( $field );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of groups that cannot be promoted
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDisabledGroups()
|
||||
{
|
||||
$return = array( \IPS\Settings::i()->guest_group );
|
||||
|
||||
foreach( \IPS\Member\Group::groups() as $group )
|
||||
{
|
||||
if( $group->g_promote_exclude )
|
||||
{
|
||||
$return[] = $group->g_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Format form values from add/edit form for save
|
||||
*
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
{
|
||||
if ( !$this->id )
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/* Save the title */
|
||||
\IPS\Lang::saveCustom( 'core', 'automaticmoderation_' . $this->id, $values['automaticmoderation_title'] );
|
||||
|
||||
/* Json-encode the rules */
|
||||
$_options = array();
|
||||
|
||||
/* Loop over bulk mail extensions to format the options */
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' ) as $key => $extension )
|
||||
{
|
||||
if( method_exists( $extension, 'save' ) AND $extension->availableIn( 'automatic_moderation' ) )
|
||||
{
|
||||
/* Grab our fields and add to the form */
|
||||
$_value = $extension->save( $values );
|
||||
|
||||
if( $_value )
|
||||
{
|
||||
$_options[ $key ] = $_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$values['rule_filters'] = json_encode( $_options );
|
||||
$values['rule_points_needed'] = $values['automaticmoderation_points'];
|
||||
$values['rule_enabled'] = $values['automaticmoderation_enabled'];
|
||||
$values['rule_types'] = $values['automaticmoderation_type'];
|
||||
|
||||
/* Now we have to remove any fields that aren't valid... */
|
||||
foreach( $values as $k => $v )
|
||||
{
|
||||
if( !in_array( $k, array( 'rule_filters', 'rule_points_needed', 'rule_enabled', 'rule_types', 'rule_position' ) ) )
|
||||
{
|
||||
unset( $values[ $k ] );
|
||||
}
|
||||
}
|
||||
|
||||
return parent::formatFormValues( $values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return our filters as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get__filters()
|
||||
{
|
||||
return json_decode( $this->filters, TRUE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch All Root Nodes
|
||||
*
|
||||
* @param string|NULL $permissionCheck The permission key to check for or NULl to not check permissions
|
||||
* @param \IPS\Member|NULL $member The member to check permissions for or NULL for the currently logged in member
|
||||
* @param mixed $where Additional WHERE clause
|
||||
* @return array
|
||||
*/
|
||||
public static function roots( $permissionCheck='view', $member=NULL, $where=array() )
|
||||
{
|
||||
if ( !count( $where ) )
|
||||
{
|
||||
$return = array();
|
||||
foreach( static::getStore() AS $node )
|
||||
{
|
||||
$return[ $node['rule_id'] ] = static::constructFromData( $node );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parent::roots( $permissionCheck, $member, $where );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Enabled
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set__enabled( $enabled )
|
||||
{
|
||||
parent::set__enabled( $enabled );
|
||||
|
||||
unset( \IPS\Data\Store::i()->automatic_moderation_rules );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data store
|
||||
*
|
||||
* @return array
|
||||
* @note Note that all records are returned, even disabled promotion rules. Enable status needs to be checked in userland code when appropriate.
|
||||
*/
|
||||
public static function getStore()
|
||||
{
|
||||
if ( !isset( \IPS\Data\Store::i()->automatic_moderation_rules ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->automatic_moderation_rules = iterator_to_array( \IPS\Db::i()->select( '*', static::$databaseTable, NULL, "rule_position ASC" )->setKeyField( 'rule_id' ) );
|
||||
}
|
||||
|
||||
return \IPS\Data\Store::i()->automatic_moderation_rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Record
|
||||
*
|
||||
* @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)
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \OutOfRangeException
|
||||
*/
|
||||
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
|
||||
{
|
||||
if ( ( $idField === NULL or $idField === 'rule_id' ) and $extraWhereClause === NULL )
|
||||
{
|
||||
$rule = static::getStore();
|
||||
if ( isset( $rule[ $id ] ) )
|
||||
{
|
||||
return static::constructFromData( $rule[ $id ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::load( $id, $idField, $extraWhereClause );
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
unset( \IPS\Data\Store::i()->automatic_moderation_rules );
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
parent::save();
|
||||
|
||||
unset( \IPS\Data\Store::i()->automatic_moderation_rules );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cache extensions so we only need to load them once
|
||||
*/
|
||||
protected static $extensions = NULL;
|
||||
|
||||
/**
|
||||
* Check if a member matches the rule
|
||||
*
|
||||
* @param \IPS\Member $member Member to check
|
||||
* @param array $typeCounts Array of type counts indexed by report_type (constants in \IPS\core\Reports\Report)
|
||||
* @return bool
|
||||
*/
|
||||
public function matches( \IPS\Member $member, $typeCounts=array() )
|
||||
{
|
||||
if ( ! \IPS\Settings::i()->automoderation_enabled )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if( static::$extensions === NULL )
|
||||
{
|
||||
static::$extensions = \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' );
|
||||
}
|
||||
|
||||
/* Nothing flagged as bad? Back you go */
|
||||
if ( ! count( $typeCounts ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Author can bypass? */
|
||||
if ( $member->group['gbw_immune_auto_mod'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Match the count threshold */
|
||||
$count = 0;
|
||||
foreach( explode( ',', $this->types ) as $type )
|
||||
{
|
||||
if ( isset( $typeCounts[ $type ] ) )
|
||||
{
|
||||
$count += $typeCounts[ $type ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $count < $this->points_needed )
|
||||
{
|
||||
/* You lose */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Loop over the filters */
|
||||
foreach( $this->_filters as $key => $filter )
|
||||
{
|
||||
if( isset( static::$extensions[ $key ] ) )
|
||||
{
|
||||
/* Ask the extension if this member matches the defined rule...if not, just return FALSE now */
|
||||
if( !static::$extensions[ $key ]->matches( $member, $filter, $this ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we are still here, then the rule matched */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ! ACP STUFF */
|
||||
|
||||
/**
|
||||
* [Node] Return the custom badge for each row
|
||||
*
|
||||
* @return NULL|array Null for no badge, or an array of badge data (0 => CSS class type, 1 => language string, 2 => optional raw HTML to show instead of language string)
|
||||
*/
|
||||
protected function get__badge()
|
||||
{
|
||||
return array(
|
||||
0 => 'ipsBadge ipsBadge_positive',
|
||||
1 => \IPS\Member::loggedIn()->language()->addToStack( 'automoderation_points_needed_badge', NULL, array( 'pluralize' => array( $this->points_needed ) ) )
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a warning if this promotion uses not existing groups
|
||||
*/
|
||||
public function get__description()
|
||||
{
|
||||
if( static::$extensions === NULL )
|
||||
{
|
||||
static::$extensions = \IPS\Application::allExtensions( 'core', 'MemberFilter', TRUE, 'core' );
|
||||
}
|
||||
|
||||
/* Loop over the filters */
|
||||
$descriptions = array();
|
||||
foreach( $this->_filters as $key => $filter )
|
||||
{
|
||||
if( isset( static::$extensions[ $key ] ) )
|
||||
{
|
||||
/* Ask the extension if this member matches the defined rule...if not, just return FALSE now */
|
||||
if( method_exists( static::$extensions[ $key ], 'getDescription' ) and $description = static::$extensions[ $key ]->getDescription( $filter ) )
|
||||
{
|
||||
$descriptions[] = $description; /* Is this descriptive enough? */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'automaticmoderation_row_desc', TRUE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $descriptions ) ) ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Report Types
|
||||
* @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 14 Dec 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\Reports;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report types node model
|
||||
*/
|
||||
class _Types extends \IPS\Node\Model
|
||||
{
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_automatic_moderation_types';
|
||||
|
||||
/**
|
||||
* @brief Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'type_';
|
||||
|
||||
/**
|
||||
* @brief Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] ID Database Column
|
||||
*/
|
||||
public static $databaseColumnId = 'id';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Map
|
||||
*/
|
||||
protected static $multitonMap = array();
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'automaticmod_types';
|
||||
|
||||
/**
|
||||
* @brief [Node] Sortable
|
||||
*/
|
||||
public static $nodeSortable = TRUE;
|
||||
|
||||
/**
|
||||
* @brief [Node] Positon Column
|
||||
*/
|
||||
public static $databaseColumnOrder = 'position';
|
||||
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
|
||||
*/
|
||||
public static $titleLangPrefix = 'automaticmod_types_';
|
||||
|
||||
/**
|
||||
* Form
|
||||
*
|
||||
* @param \IPS\Helpers\Form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( &$form )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Translatable( 'automaticmod_types_title', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? 'automaticmod_types_' . $this->id : NULL ) ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Format form values from add/edit form for save
|
||||
*
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
{
|
||||
if ( !$this->id )
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/* Save the title */
|
||||
\IPS\Lang::saveCustom( 'core', 'automaticmod_types_' . $this->id, $values['automaticmod_types_title'] );
|
||||
|
||||
unset( $values['automaticmod_types_title'] );
|
||||
|
||||
return parent::formatFormValues( $values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch All Root Nodes
|
||||
*
|
||||
* @param string|NULL $permissionCheck The permission key to check for or NULl to not check permissions
|
||||
* @param \IPS\Member|NULL $member The member to check permissions for or NULL for the currently logged in member
|
||||
* @param mixed $where Additional WHERE clause
|
||||
* @return array
|
||||
*/
|
||||
public static function roots( $permissionCheck='view', $member=NULL, $where=array() )
|
||||
{
|
||||
if ( !count( $where ) )
|
||||
{
|
||||
$return = array();
|
||||
foreach( static::getStore() AS $node )
|
||||
{
|
||||
$return[ $node['type_id'] ] = static::constructFromData( $node );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parent::roots( $permissionCheck, $member, $where );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data store
|
||||
*
|
||||
* @return array
|
||||
* @note Note that all records are returned, even disabled promotion rules. Enable status needs to be checked in userland code when appropriate.
|
||||
*/
|
||||
public static function getStore()
|
||||
{
|
||||
if ( !isset( \IPS\Data\Store::i()->automatic_moderation_types ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->automatic_moderation_types = iterator_to_array( \IPS\Db::i()->select( '*', static::$databaseTable, NULL, "type_position ASC" )->setKeyField( 'type_id' ) );
|
||||
}
|
||||
|
||||
return \IPS\Data\Store::i()->automatic_moderation_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Record
|
||||
*
|
||||
* @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)
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \OutOfRangeException
|
||||
*/
|
||||
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
|
||||
{
|
||||
if ( ( $idField === NULL or $idField === 'type_id' ) and $extraWhereClause === NULL )
|
||||
{
|
||||
$type = static::getStore();
|
||||
if ( isset( $type[ $id ] ) )
|
||||
{
|
||||
return static::constructFromData( $type[ $id ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::load( $id, $idField, $extraWhereClause );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item count
|
||||
* I'm sure you could have figured that out from the method name
|
||||
* but I'll spoon feed you, it's ok.
|
||||
*
|
||||
* @return INT
|
||||
*/
|
||||
public function getItemCount()
|
||||
{
|
||||
return intval( \IPS\Db::i()->select( 'COUNT(*)', 'core_rc_reports', array( 'report_type=?', $this->id ) )->first() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to delete or move content
|
||||
*
|
||||
* @param bool $showMoveToChildren If TRUE, will show "move to children" even if there are no children
|
||||
* @return \IPS\Helpers\Form
|
||||
*/
|
||||
public function deleteOrMoveForm( $showMoveToChildren=FALSE )
|
||||
{
|
||||
$form = new \IPS\Helpers\Form( 'delete_node_form', 'delete' );
|
||||
$form->addMessage( 'node_delete_blurb' );
|
||||
|
||||
if ( $this->getItemCount() )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Node( 'node_move_content', 0, TRUE, array( 'class' => get_class( $this ), 'disabled' => array( $this->_id ), 'disabledLang' => 'node_move_delete', 'zeroVal' => 'node_delete_content', 'subnodes' => FALSE, 'permissionCheck' => function( $node )
|
||||
{
|
||||
return true;
|
||||
} ) ) );
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle submissions of form to delete or move content
|
||||
*
|
||||
* @param array $values Values from form
|
||||
* @return void
|
||||
*/
|
||||
public function deleteOrMoveFormSubmit( $values )
|
||||
{
|
||||
if ( isset( $values['node_move_content'] ) and $values['node_move_content'] )
|
||||
{
|
||||
/* We're moving first */
|
||||
\IPS\Db::i()->update( 'core_rc_reports', array( 'report_type' => $values['node_move_content']->_id ), array( 'report_type=?', $this->id ) );
|
||||
}
|
||||
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
unset( \IPS\Data\Store::i()->automatic_moderation_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
parent::save();
|
||||
|
||||
unset( \IPS\Data\Store::i()->automatic_moderation_types );
|
||||
}
|
||||
}
|
||||
@@ -147,26 +147,29 @@ class _Rss extends \IPS\Node\Model
|
||||
$form->addHeader( 'rss_content' );
|
||||
foreach( \IPS\Content::routedClasses( TRUE, FALSE, TRUE ) AS $class )
|
||||
{
|
||||
$options = array();
|
||||
$containerClass = NULL;
|
||||
if ( isset( $class::$containerNodeClass ) )
|
||||
if ( isset( $class::$databaseColumnMap['author'] ) )
|
||||
{
|
||||
$options = array( 'togglesOn' => array( 'rss_nodes_' . $class::$title ) );
|
||||
$containerClass = $class::$containerNodeClass;
|
||||
}
|
||||
|
||||
\IPS\Member::loggedIn()->language()->words["rss_classes_{$class}"] = \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' );
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'rss_classes_' . $class, ( $this->_id ) ? in_array( $class, $this->configuration['classes'] ) : FALSE, FALSE, $options ) );
|
||||
|
||||
if ( $containerClass )
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words["nodes_{$class}"] = \IPS\Member::loggedIn()->language()->addToStack( $containerClass::$nodeTitle );
|
||||
$form->add( new \IPS\Helpers\Form\Node( "nodes_{$class}", ( $this->_id AND isset( $this->configuration['containers'][$class] ) ) ? $this->configuration['containers'][$class] : 0, FALSE, array(
|
||||
'class' => $containerClass,
|
||||
'zeroVal' => 'all',
|
||||
'multiple' => TRUE,
|
||||
'permissionCheck' => function( $val ) { return $val->can( 'view', new \IPS\Member ); }
|
||||
), NULL, NULL, NULL, "rss_nodes_{$class::$title}" ) );
|
||||
$options = array();
|
||||
$containerClass = NULL;
|
||||
if ( isset( $class::$containerNodeClass ) )
|
||||
{
|
||||
$options = array( 'togglesOn' => array( 'rss_nodes_' . $class::$title ) );
|
||||
$containerClass = $class::$containerNodeClass;
|
||||
}
|
||||
|
||||
\IPS\Member::loggedIn()->language()->words["rss_classes_{$class}"] = \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' );
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'rss_classes_' . $class, ( $this->_id ) ? in_array( $class, $this->configuration['classes'] ) : FALSE, FALSE, $options ) );
|
||||
|
||||
if ( $containerClass )
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words["nodes_{$class}"] = \IPS\Member::loggedIn()->language()->addToStack( $containerClass::$nodeTitle );
|
||||
$form->add( new \IPS\Helpers\Form\Node( "nodes_{$class}", ( $this->_id AND isset( $this->configuration['containers'][$class] ) ) ? $this->configuration['containers'][$class] : 0, FALSE, array(
|
||||
'class' => $containerClass,
|
||||
'zeroVal' => 'all',
|
||||
'multiple' => TRUE,
|
||||
'permissionCheck' => function( $val ) { return $val->can( 'view', new \IPS\Member ); }
|
||||
), NULL, NULL, NULL, "rss_nodes_{$class::$title}" ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,27 +394,34 @@ class _Rss extends \IPS\Node\Model
|
||||
{
|
||||
$member = $member ?: new \IPS\Member;
|
||||
|
||||
$search = \IPS\Content\Search\Query::init( $member );
|
||||
$search = \IPS\Content\Search\Query::init( $member );
|
||||
$filterByClass = FALSE;
|
||||
|
||||
if ( count( $this->configuration['classes'] ) )
|
||||
{
|
||||
$filters = array();
|
||||
$filterByClass = TRUE;
|
||||
$filters = array();
|
||||
|
||||
foreach( $this->configuration['classes'] AS $class )
|
||||
{
|
||||
if ( isset( $class::$firstCommentRequired ) AND $class::$firstCommentRequired )
|
||||
if( class_exists( $class, TRUE ) )
|
||||
{
|
||||
$filter = \IPS\Content\Search\ContentFilter::init( $class, TRUE, TRUE, FALSE )->onlyFirstComment();
|
||||
if ( isset( $class::$firstCommentRequired ) AND $class::$firstCommentRequired )
|
||||
{
|
||||
$filter = \IPS\Content\Search\ContentFilter::init( $class, TRUE, TRUE, FALSE )->onlyFirstComment();
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter = \IPS\Content\Search\ContentFilter::init( $class, TRUE, FALSE, FALSE );
|
||||
}
|
||||
|
||||
if ( !empty( $this->configuration['containers'][$class] ) )
|
||||
{
|
||||
$filter->onlyInContainers( $this->configuration['containers'][$class] );
|
||||
}
|
||||
|
||||
$filters[] = $filter;
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter = \IPS\Content\Search\ContentFilter::init( $class, TRUE, FALSE, FALSE );
|
||||
}
|
||||
|
||||
if ( !empty( $this->configuration['containers'][$class] ) )
|
||||
{
|
||||
$filter->onlyInContainers( $this->configuration['containers'][$class] );
|
||||
}
|
||||
|
||||
$filters[] = $filter;
|
||||
}
|
||||
|
||||
$search->filterByContent( $filters );
|
||||
@@ -425,7 +435,14 @@ class _Rss extends \IPS\Node\Model
|
||||
$search->setLimit( $this->count );
|
||||
$search->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_CREATED );
|
||||
|
||||
$results = $search->search();
|
||||
if( $filterByClass == TRUE AND !count( $filters ) )
|
||||
{
|
||||
$results = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$results = $search->search();
|
||||
}
|
||||
|
||||
/* We have to use get() to ensure CDATA tags wrap the title properly */
|
||||
$title = \IPS\Member::loggedIn()->language()->get( "rss_export_title_{$this->_id}" );
|
||||
|
||||
@@ -589,6 +589,7 @@ class _Install
|
||||
'sys_module_position' => ++$modulePos,
|
||||
'sys_module_area' => $area,
|
||||
'sys_module_default_controller' => $data['default_controller'],
|
||||
'sys_module_default' => (int) ( isset( $data['default'] ) and $data['default'] )
|
||||
) );
|
||||
|
||||
\IPS\Db::i()->insert( 'core_permission_index', array(
|
||||
@@ -651,17 +652,16 @@ class _Install
|
||||
$member->member_group_id = 4;
|
||||
$member->allow_admin_mails = 0;
|
||||
$member->joined = time();
|
||||
$member->members_pass_salt = $member->generateSalt();
|
||||
$member->members_pass_hash = $member->encryptedPassword( $this->adminPass );
|
||||
$member->setLocalPassword( $this->adminPass );
|
||||
$member->members_bitoptions['view_sigs'] = TRUE;
|
||||
$member->save();
|
||||
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $this->adminEmail ), "conf_key = 'email_out'" );
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $this->adminEmail ), "conf_key = 'email_in'" );
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $this->adminEmail ), "conf_key = 'upgrade_email'" );
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $this->diagnostics ), "conf_key = 'diagnostics_reporting'" );
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $this->diagnostics ), "conf_key = 'diagnostics_reporting' OR conf_key = 'usage_reporting'" );
|
||||
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
\IPS\Settings::i()->clearCache();
|
||||
|
||||
return array( 5 );
|
||||
}
|
||||
@@ -690,7 +690,7 @@ class _Install
|
||||
protected function step6( $data )
|
||||
{
|
||||
/* Install the default language */
|
||||
$locales = array( 'en_US', 'en_US.utf8', 'en_US.UTF8' );
|
||||
$locales = array( 'en_US', 'en_US.utf8', 'en_US.UTF8', 'english' );
|
||||
foreach ( $locales as $k => $localeCode )
|
||||
{
|
||||
try
|
||||
@@ -880,9 +880,9 @@ class _Install
|
||||
/* Insert default emoticons and reaction */
|
||||
if( $app == 'core' )
|
||||
{
|
||||
$setId = md5( uniqid() );
|
||||
$setId = mt_rand();
|
||||
$position = 0;
|
||||
\IPS\Lang::saveCustom( 'core', "core_emoticon_group_{$setId}", "Default" );
|
||||
\IPS\Lang::saveCustom( 'core', "core_emoticon_group_{$setId}", "Classic" );
|
||||
|
||||
$inserts = array();
|
||||
|
||||
|
||||
@@ -72,8 +72,9 @@ class _Upgrade
|
||||
{
|
||||
if( $e->getCode() !== 115115 )
|
||||
{
|
||||
/* We don't want to stop upgrader for this, but we can show the message if the upgrader otherwise cannot continue */
|
||||
$fileSystemItems[ $class->configurationId ] = array(
|
||||
'success' => FALSE,
|
||||
'success' => TRUE,
|
||||
'message' => $e->getMessage()
|
||||
);
|
||||
}
|
||||
@@ -132,6 +133,18 @@ class _Upgrade
|
||||
);
|
||||
}
|
||||
|
||||
/* MySQL timeouts */
|
||||
$db = $db ?: \IPS\Db::i();
|
||||
$query = $db->query( "SHOW VARIABLES LIKE '%wait_timeout%'" );
|
||||
|
||||
while( $row = $query->fetch_assoc() )
|
||||
{
|
||||
if ( $row['Variable_name'] == 'wait_timeout' AND $row['Value'] < 20 )
|
||||
{
|
||||
$return['advice'][] = \IPS\Member::loggedIn()->language()->addToStack( 'requirements_mysql_timeout', FALSE, array( 'sprintf' => array( $row['Variable_name'], $row['Value'] ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -885,10 +898,7 @@ class _Upgrade
|
||||
*/
|
||||
protected function step9( $data )
|
||||
{
|
||||
if( isset( \IPS\Data\Store::i()->settings ) )
|
||||
{
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
}
|
||||
\IPS\Settings::i()->clearCache();
|
||||
|
||||
if( isset( \IPS\Data\Store::i()->storageConfigurations ) )
|
||||
{
|
||||
@@ -942,7 +952,7 @@ class _Upgrade
|
||||
$data[2] = 0;
|
||||
}
|
||||
|
||||
$inserted = \IPS\Application::load( $app )->installJavascript( $data[2], 150 );
|
||||
$inserted = \IPS\Application::load( $app )->installJavascript( $data[2], 100 );
|
||||
|
||||
\IPS\Log::debug( "Installed javascript for " . $app, 'upgrade' );
|
||||
|
||||
@@ -1002,8 +1012,7 @@ class _Upgrade
|
||||
\IPS\Db::i()->insert( 'core_sys_conf_settings', $insert );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => intval( $value ) ), array( 'conf_key=?', 'setup_in_progress' ) );
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
\IPS\Settings::i()->changeValues( array( 'setup_in_progress' => intval( $value ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1193,8 +1202,22 @@ class _Upgrade
|
||||
if( class_exists( $classname ) )
|
||||
{
|
||||
$extension = new $classname;
|
||||
|
||||
\IPS\Task::queue( 'core', 'RepairFileUrls', array( 'storageExtension' => $k, 'count' => $extension->count() ), 1 );
|
||||
|
||||
try
|
||||
{
|
||||
\IPS\Task::queue( 'core', 'RepairFileUrls', array( 'storageExtension' => $k, 'count' => $extension->count() ), 1 );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
/*
|
||||
* Don't throw an exception if the table or a column doesn't exist, this can happen
|
||||
* when newer versions add storage configurations that haven't been set up yet.
|
||||
*/
|
||||
if( $e->getCode() !== 1146 AND $e->getCode() !== 1054 )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@ class _Service extends \IPS\Node\Model
|
||||
}
|
||||
}
|
||||
|
||||
$settingsToUpdate = array();
|
||||
foreach ( $values as $k => $v )
|
||||
{
|
||||
if( !in_array( $k, array( 'share_title', 'share_groups', 'share_autoshare' ) ) )
|
||||
@@ -247,14 +248,12 @@ class _Service extends \IPS\Node\Model
|
||||
$v = implode( ',', $v );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $v ), array( 'conf_key=?', $k ) );
|
||||
\IPS\Settings::i()->$k = $v;
|
||||
|
||||
$settingsToUpdate[ $k ] = $v;
|
||||
unset( $values[ $k ] );
|
||||
}
|
||||
}
|
||||
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
\IPS\Settings::i()->changeValues( $settingsToUpdate );
|
||||
|
||||
/* Remove prefix */
|
||||
$_values = $values;
|
||||
|
||||
@@ -261,11 +261,25 @@ class _User extends \IPS\Node\Model
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( "core_staff_directory_name_{$this->id}", FALSE, array( 'escape' => TRUE ) );
|
||||
}
|
||||
|
||||
return \IPS\Member::load( $this->type_id )->name;
|
||||
$member = \IPS\Member::load( $this->type_id );
|
||||
|
||||
if( $member->member_id )
|
||||
{
|
||||
return $member->name;
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('deleted_member');
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Member\Group::load( $this->type_id )->name;
|
||||
try
|
||||
{
|
||||
return \IPS\Member\Group::load( $this->type_id )->name;
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('deleted_group');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,17 +363,22 @@ class _User extends \IPS\Node\Model
|
||||
public function save()
|
||||
{
|
||||
parent::save();
|
||||
static::updateEmptySetting();
|
||||
static::updateEmptySetting( FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any records and update setting so we can hide the link if there is nothing
|
||||
*
|
||||
* @param bool|NULL $value If we already know the value (because we've just set it), will save a query
|
||||
* @return void
|
||||
*/
|
||||
public static function updateEmptySetting()
|
||||
public static function updateEmptySetting( $value = NULL )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => !( (bool) \IPS\Db::i()->select( 'COUNT(*)', 'core_leaders' )->first() ) ), array( 'conf_key=?', 'staff_directory_empty' ) );
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
if ( $value == NULL )
|
||||
{
|
||||
$value = !( (bool) \IPS\Db::i()->select( 'COUNT(*)', 'core_leaders', NULL, NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first() );
|
||||
}
|
||||
|
||||
\IPS\Settings::i()->changeValues( array( 'staff_directory_empty' => $value ) );
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,9 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
/**
|
||||
* Status Update Reply
|
||||
*/
|
||||
class _Reply extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \IPS\Content\Hideable
|
||||
class _Reply extends \IPS\Content\Comment implements \IPS\Content\Hideable
|
||||
{
|
||||
use \IPS\Content\Reactable;
|
||||
use \IPS\Content\Reactable, \IPS\Content\Reportable;
|
||||
|
||||
/**
|
||||
* @brief Application
|
||||
@@ -132,7 +132,7 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\ReportCenter,
|
||||
*/
|
||||
public function sendNotifications()
|
||||
{
|
||||
parent::sendNotifications();
|
||||
$sentTo = parent::sendNotifications();
|
||||
|
||||
/* Notify when somebody replies to status updates I am connected to */
|
||||
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'profile_reply', $this, array( $this ) );
|
||||
@@ -160,18 +160,7 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\ReportCenter,
|
||||
$notification->recipients->attach( \IPS\Member::load( $this->item()->member_id ) );
|
||||
}
|
||||
|
||||
$notification->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can edit?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return bool
|
||||
*/
|
||||
public function canEdit( $member=NULL )
|
||||
{
|
||||
return FALSE;
|
||||
$notification->send( $sentTo );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,7 +189,7 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\ReportCenter,
|
||||
*/
|
||||
public static function contentTableTemplate()
|
||||
{
|
||||
return array( \IPS\Theme::i()->getTemplate( 'statuses', 'core' ), 'statusReplyContentRows' );
|
||||
return array( \IPS\Theme::i()->getTemplate( 'statuses', 'core', 'front' ), 'statusReplyContentRows' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,9 +20,9 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
/**
|
||||
* Status Update Model
|
||||
*/
|
||||
class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \IPS\Content\Lockable, \IPS\Content\Hideable, \IPS\Content\Searchable, \IPS\Content\Shareable
|
||||
class _Status extends \IPS\Content\Item implements \IPS\Content\Lockable, \IPS\Content\Hideable, \IPS\Content\Searchable, \IPS\Content\Shareable
|
||||
{
|
||||
use \IPS\Content\Reactable;
|
||||
use \IPS\Content\Reactable, \IPS\Content\Reportable;
|
||||
|
||||
/* !\IPS\Patterns\ActiveRecord */
|
||||
|
||||
@@ -50,12 +50,7 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
* @brief Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [Content\Item] Include the ability to search this content item in global site searches
|
||||
*/
|
||||
public static $includeInSiteSearch = FALSE;
|
||||
|
||||
|
||||
/**
|
||||
* Columns needed to query for search result / stream view
|
||||
*
|
||||
@@ -167,7 +162,7 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
*/
|
||||
public function get_title()
|
||||
{
|
||||
return strip_tags( $this->mapped('content') );
|
||||
return trim( preg_replace( "/\s+/u", ' ', strip_tags( str_replace( '<', ' <', \IPS\Text\Parser::removeElements( $this->mapped('content'), 'div[class="ipsSpoiler"]' ) ) ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,7 +471,7 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
*/
|
||||
public function sendNotifications()
|
||||
{
|
||||
parent::sendNotifications();
|
||||
$sentTo = parent::sendNotifications();
|
||||
|
||||
/* Notify when somebody comments on my profile */
|
||||
if( $this->author()->member_id != $this->member_id )
|
||||
@@ -502,7 +497,7 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
}
|
||||
}
|
||||
|
||||
$notification->send();
|
||||
$notification->send( $sentTo );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,7 +512,7 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
{
|
||||
if ( $member->moderateNewContent() or \IPS\Settings::i()->profile_comment_approval )
|
||||
{
|
||||
return TRUE;
|
||||
return !static::modPermission( 'approve', $member, $container );
|
||||
}
|
||||
|
||||
return parent::moderateNewItems( $member, $container );
|
||||
@@ -534,17 +529,6 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
return ( $member->moderateNewContent() or \IPS\Settings::i()->profile_comment_approval );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can edit?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return bool
|
||||
*/
|
||||
public function canEdit( $member=NULL )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can delete?
|
||||
*
|
||||
@@ -564,6 +548,11 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
return parent::canDelete( $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached comment count
|
||||
*/
|
||||
protected $commentCount = NULL;
|
||||
|
||||
/**
|
||||
* Get comment count
|
||||
*
|
||||
@@ -571,55 +560,60 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
*/
|
||||
public function commentCount()
|
||||
{
|
||||
$count = parent::commentCount();
|
||||
|
||||
if( $this->canViewHiddenComments() )
|
||||
if( $this->commentCount === NULL )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$class = static::$commentClass;
|
||||
$authorCol = $class::$databasePrefix . $class::$databaseColumnMap['author'];
|
||||
$where = array( array( $class::$databasePrefix . $class::$databaseColumnMap['item'] . '=?', $this->$idColumn ) );
|
||||
$count = parent::commentCount();
|
||||
|
||||
if ( isset( static::$databaseColumnMap['hidden_comments'] ) )
|
||||
if( $this->canViewHiddenComments() )
|
||||
{
|
||||
$count += $this->mapped('hidden_comments');
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$class = static::$commentClass;
|
||||
$authorCol = $class::$databasePrefix . $class::$databaseColumnMap['author'];
|
||||
$where = array( array( $class::$databasePrefix . $class::$databaseColumnMap['item'] . '=?', $this->$idColumn ) );
|
||||
|
||||
if ( isset( static::$databaseColumnMap['hidden_comments'] ) )
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['approved'];
|
||||
$where[] = array( "{$col}=-1" );
|
||||
$count += $this->mapped('hidden_comments');
|
||||
}
|
||||
elseif( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
else
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}=-1" );
|
||||
if ( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['approved'];
|
||||
$where[] = array( "{$col}=-1" );
|
||||
}
|
||||
elseif( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}=-1" );
|
||||
}
|
||||
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
|
||||
if ( isset( static::$databaseColumnMap['unapproved_comments'] ) )
|
||||
{
|
||||
$count += $this->mapped('unapproved_comments');
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['approved'];
|
||||
$where[] = array( "{$col}=0" );
|
||||
}
|
||||
elseif( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}=1" );
|
||||
}
|
||||
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
|
||||
if ( isset( static::$databaseColumnMap['unapproved_comments'] ) )
|
||||
{
|
||||
$count += $this->mapped('unapproved_comments');
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['approved'];
|
||||
$where[] = array( "{$col}=0" );
|
||||
}
|
||||
elseif( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$col = $class::$databasePrefix . $class::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}=1" );
|
||||
}
|
||||
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
$this->commentCount = $count;
|
||||
}
|
||||
|
||||
return $count;
|
||||
return $this->commentCount;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -682,7 +676,7 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
*/
|
||||
public static function contentTableTemplate()
|
||||
{
|
||||
return array( \IPS\Theme::i()->getTemplate( 'statuses', 'core' ), 'statusContentRows' );
|
||||
return array( \IPS\Theme::i()->getTemplate( 'statuses', 'core', 'front' ), 'statusContentRows' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -738,7 +732,7 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
*/
|
||||
protected function autoShare( $className )
|
||||
{
|
||||
$className::publish( html_entity_decode( trim( strip_tags( $this->content ) ), ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8' ) );
|
||||
$className::publish( html_entity_decode( trim( strip_tags( $this->content ) ), ENT_QUOTES | ENT_DISALLOWED, 'UTF-8' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -750,4 +744,24 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
|
||||
{
|
||||
return 'status_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Can view hidden items?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @param \IPS\Node\Model|null $container Container
|
||||
* @return bool
|
||||
* @note If called without passing $container, this method falls back to global "can view hidden content" moderator permission which isn't always what you want - pass $container if in doubt or use canViewHiddenItemsContainers()
|
||||
*/
|
||||
public static function canViewHiddenItems( $member=NULL, \IPS\Node\Model $container = NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
if( $member->modPermission( "can_view_hidden_member_status" ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return parent::canViewHiddenItems( $member, $container );
|
||||
}
|
||||
}
|
||||
@@ -368,10 +368,13 @@ class _Stream extends \IPS\Node\Model
|
||||
|
||||
foreach ( \IPS\Content::routedClasses( TRUE, FALSE, TRUE ) as $class )
|
||||
{
|
||||
$classes[ $class ] = $class::$title . '_pl';
|
||||
if ( isset( $class::$containerNodeClass ) )
|
||||
if ( isset( $class::$databaseColumnMap['date'] ) )
|
||||
{
|
||||
$classToggles[ $class ][] = 'stream_containers_' . $class::$title;
|
||||
$classes[ $class ] = $class::$title . '_pl';
|
||||
if ( isset( $class::$containerNodeClass ) )
|
||||
{
|
||||
$classToggles[ $class ][] = 'stream_containers_' . $class::$title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +393,7 @@ class _Stream extends \IPS\Node\Model
|
||||
$nodeClass = $class::$containerNodeClass;
|
||||
$value = isset( $containers->$class ) ? $containers->$class : 0;
|
||||
|
||||
$field = new \IPS\Helpers\Form\Node( 'stream_containers_' . $class::$title, $value, FALSE, array( 'class' => $nodeClass, 'multiple' => TRUE, 'zeroVal' => 'all', 'permissionCheck' => $nodeClass::searchableNodesPermission(), 'forceOwner' => FALSE ), NULL, NULL, NULL, 'stream_containers_' . $class::$title );
|
||||
$field = new \IPS\Helpers\Form\Node( 'stream_containers_' . $class::$title, $value, FALSE, array( 'class' => $nodeClass, 'multiple' => TRUE, 'zeroVal' => 'all', 'permissionCheck' => $nodeClass::searchableNodesPermission(), 'forceOwner' => FALSE, 'subnodes' => FALSE ), NULL, NULL, NULL, 'stream_containers_' . $class::$title );
|
||||
$field->label = \IPS\Member::loggedIn()->language()->addToStack( $nodeClass::$nodeTitle );
|
||||
$form->add( $field );
|
||||
}
|
||||
@@ -398,39 +401,42 @@ class _Stream extends \IPS\Node\Model
|
||||
}
|
||||
|
||||
/* Clubs */
|
||||
$clubs = \IPS\Member\Club::clubs( NULL, NULL, 'name', FALSE, array(), \IPS\Settings::i()->clubs_require_approval ? array( 'approved=1' ) : NULL );
|
||||
if ( count( $clubs ) )
|
||||
if( \IPS\Settings::i()->clubs )
|
||||
{
|
||||
$clubOptions = array();
|
||||
foreach ( $clubs as $club )
|
||||
$clubs = \IPS\Member\Club::clubs( NULL, NULL, 'name', FALSE, array(), \IPS\Settings::i()->clubs_require_approval ? array( 'approved=1' ) : NULL );
|
||||
if ( is_object( $clubs ) AND $clubs->count( TRUE ) )
|
||||
{
|
||||
$clubOptions[ "c{$club->id}" ] = $club->name;
|
||||
$clubOptions = array();
|
||||
foreach ( $clubs as $club )
|
||||
{
|
||||
$clubOptions[ "c{$club->id}" ] = $club->name;
|
||||
}
|
||||
|
||||
switch ( $this->clubs )
|
||||
{
|
||||
case NULL:
|
||||
$clubSelect = 'all';
|
||||
break;
|
||||
case '0':
|
||||
$clubSelect = 'none';
|
||||
break;
|
||||
default:
|
||||
$clubSelect = 'select';
|
||||
break;
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'stream_club_select', $clubSelect, TRUE, array(
|
||||
'options' => array(
|
||||
'all' => 'stream_club_select_all',
|
||||
'none' => 'stream_club_select_none',
|
||||
'select' => 'stream_club_select_select',
|
||||
),
|
||||
'toggles' => array(
|
||||
'select' => array( 'stream_club_filter' )
|
||||
)
|
||||
), NULL, NULL, NULL, 'stream_club_select' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'stream_club_filter', explode( ',', $this->clubs ), FALSE, array( 'options' => $clubOptions, 'parse' => 'normal', 'multiple' => TRUE, 'noDefault' => TRUE ), NULL, NULL, NULL, 'stream_club_filter' ) );
|
||||
}
|
||||
|
||||
switch ( $this->clubs )
|
||||
{
|
||||
case NULL:
|
||||
$clubSelect = 'all';
|
||||
break;
|
||||
case '0':
|
||||
$clubSelect = 'none';
|
||||
break;
|
||||
default:
|
||||
$clubSelect = 'select';
|
||||
break;
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'stream_club_select', $clubSelect, TRUE, array(
|
||||
'options' => array(
|
||||
'all' => 'stream_club_select_all',
|
||||
'none' => 'stream_club_select_none',
|
||||
'select' => 'stream_club_select_select',
|
||||
),
|
||||
'toggles' => array(
|
||||
'select' => array( 'stream_club_filter' )
|
||||
)
|
||||
), NULL, NULL, NULL, 'stream_club_select' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'stream_club_filter', explode( ',', $this->clubs ), FALSE, array( 'options' => $clubOptions, 'parse' => 'none', 'multiple' => TRUE, 'noDefault' => TRUE ), NULL, NULL, NULL, 'stream_club_filter' ) );
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
@@ -445,7 +451,7 @@ class _Stream extends \IPS\Node\Model
|
||||
'custom' => array( 'stream_tags' )
|
||||
)
|
||||
) ) );
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'stream_tags', $this->tags ? explode( ',', $this->tags ) : NULL, NULL, array( 'autocomplete' => true ), NULL, NULL, NULL, 'stream_tags' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'stream_tags', $this->tags ? explode( ',', $this->tags ) : NULL, NULL, array( 'autocomplete' => array( 'minimized' => FALSE ) ), NULL, NULL, NULL, 'stream_tags' ) );
|
||||
}
|
||||
|
||||
/* Ownership */
|
||||
@@ -1213,11 +1219,11 @@ class _Stream extends \IPS\Node\Model
|
||||
{
|
||||
if ( $this->sort === 'oldest' )
|
||||
{
|
||||
$query->setOrder( \IPS\Content\Search\Query::ORDER_OLDEST_UPDATED );
|
||||
$query->setOrder( \IPS\Content\Search\Query::ORDER_OLDEST_CREATED );
|
||||
}
|
||||
else
|
||||
{
|
||||
$query->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_UPDATED );
|
||||
$query->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_COMMENTED );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ class _Warning extends \IPS\Content\Item
|
||||
}
|
||||
|
||||
/* Undo the actions */
|
||||
$consequences = array();
|
||||
foreach ( array( 'mq' => 'mod_posts', 'rpa' => 'restrict_post', 'suspend' => 'temp_ban' ) as $w => $m )
|
||||
{
|
||||
if ( $this->$w and ( $this->$w < time() or $this->$w == -1 ) )
|
||||
@@ -96,16 +97,21 @@ class _Warning extends \IPS\Content\Item
|
||||
{
|
||||
$latest = \IPS\Db::i()->select( '*', 'core_members_warn_logs', array( "wl_member=? AND wl_{$w}<>0 AND wl_id !=?", $member->member_id, $this->id ), 'wl_date DESC' )->first();
|
||||
$member->$m = $latest[ 'wl_' . $w ];
|
||||
$consequences[ $m ] = $latest['wl_id'];
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
$member->$m = 0;
|
||||
$consequences[ $m ] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Save */
|
||||
$member->save();
|
||||
|
||||
/* Log */
|
||||
$member->logHistory( 'core', 'warning', array( 'type' => 'revoke', 'wid' => $this->id, 'reason' => $this->reason, 'points' => $this->points, 'consequences' => $consequences ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,6 +332,13 @@ class _Warning extends \IPS\Content\Item
|
||||
}
|
||||
|
||||
}, \IPS\Member::loggedIn()->language()->addToStack('until'), NULL, NULL, 'warn_suspend' );
|
||||
|
||||
$member = \IPS\Member::load( \IPS\Request::i()->id );
|
||||
|
||||
if( $member->temp_ban OR $member->mod_posts OR $member->restrict_post )
|
||||
{
|
||||
$elements[] = \IPS\Member::loggedIn()->language()->addToStack('member_existing_penalties', TRUE, array( 'sprintf' => $member->name ) );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $elements;
|
||||
@@ -340,6 +353,12 @@ class _Warning extends \IPS\Content\Item
|
||||
protected function processBeforeCreate( $values )
|
||||
{
|
||||
$this->member = \IPS\Request::i()->member;
|
||||
|
||||
/* Permission Check */
|
||||
if ( !\IPS\Member::loggedIn()->canWarn( \IPS\Member::load( $this->member ) ) )
|
||||
{
|
||||
\IPS\Output::i()->error( 'no_module_permission', '2C150/3', 403, '' );
|
||||
}
|
||||
|
||||
foreach( $values['warn_punishment'] AS $p )
|
||||
{
|
||||
@@ -465,8 +484,15 @@ class _Warning extends \IPS\Content\Item
|
||||
$this->content_module = isset( $ref['module'] ) ? $ref['module'] : NULL;
|
||||
$this->content_id1 = isset( $ref['id_1'] ) ? $ref['id_1'] : NULL;
|
||||
$this->content_id2 = isset( $ref['id_2'] ) ? $ref['id_2'] : NULL;
|
||||
if ( $content = $this->content() and !$content->canView() )
|
||||
{
|
||||
$this->content_app = NULL;
|
||||
$this->content_module = NULL;
|
||||
$this->content_id1 = NULL;
|
||||
$this->content_id2 = NULL;
|
||||
}
|
||||
|
||||
/* Work out the timeframes for the penalities */
|
||||
/* Work out the timeframes for the penalties */
|
||||
if ( count( $values['warn_punishment'] ) )
|
||||
{
|
||||
foreach ( array( 'mq', 'rpa', 'suspend' ) as $f )
|
||||
@@ -529,10 +555,12 @@ class _Warning extends \IPS\Content\Item
|
||||
{
|
||||
$member->warn_level += $this->points;
|
||||
}
|
||||
$consequences = array();
|
||||
foreach ( array( 'mq' => 'mod_posts', 'rpa' => 'restrict_post', 'suspend' => 'temp_ban' ) as $k => $v )
|
||||
{
|
||||
if ( $this->$k )
|
||||
{
|
||||
$consequences[ $v ] = $this->$k;
|
||||
if ( $this->$k != -1 )
|
||||
{
|
||||
$member->$v = \IPS\DateTime::create()->add( new \DateInterval( $this->$k ) )->getTimestamp();
|
||||
@@ -545,7 +573,7 @@ class _Warning extends \IPS\Content\Item
|
||||
}
|
||||
$member->members_bitoptions['unacknowledged_warnings'] = (bool) \IPS\Settings::i()->warnings_acknowledge;
|
||||
$member->save();
|
||||
$member->logHistory( 'core', 'warning', array( 'wid' => $this->id ) );
|
||||
$member->logHistory( 'core', 'warning', array( 'wid' => $this->id, 'points' => $this->points, 'reason' => $this->reason, 'consequences' => $consequences ) );
|
||||
|
||||
parent::processAfterCreate( $comment, $values );
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user