Version 4.1.2

This commit is contained in:
Neo committed 2025-12-19 05:32:19 -08:00
1 parent 3ba79295ee
commit 28bd025b35
1131 files changed
+63658 -20579

No files matched your search

@@ -96,15 +96,16 @@ class _Advertisement extends \IPS\Patterns\ActiveRecord
}
}
/* Weed out any we don't see due to our group. This is done after loading the advertisements so that the cache can be properly primed regardless of group. */
/* Weed out any we don't see due to our group. This is done after loading the advertisements so that the cache can be properly primed regardless of group. Note that $ad->exempt, is, confusingly who to SHOW to, not who is exempt */
foreach( static::$advertisements as $adLocation => $ads )
{
foreach( $ads as $index => $ad )
{
if ( $ad->exempt != '*' )
{
$groupsToHideFrom = array_diff( array_keys(\IPS\Member\Group::groups()), json_decode( $ad->exempt, TRUE ) );
if ( \IPS\Member::loggedIn()->inGroup( $groupsToHideFrom ) )
{
unset( static::$advertisements[ $adLocation ][ $index ] );
+21 -16
View File
@@ -53,15 +53,14 @@ class _Table extends \IPS\Helpers\Table\Db
if( is_subclass_of( $className, 'IPS\Content\Item' ) )
{
$this->rowsTemplate = $className::contentTableTemplate();
$this->rowsTemplate = $className::manageFollowRows();
$containerClass = $className::$containerNodeClass;
$this->group = 'follow_id';
$this->joins = array(
array( 'from' => 'core_permission_index', 'where' => array( "core_permission_index.app=? AND core_permission_index.perm_type=?", $containerClass::$permApp, $containerClass::$permType ) ),
$this->joins = array(
array( 'from' => $className::$databaseTable, 'where' => array( $className::$databaseTable . '.' . $className::$databasePrefix . $className::$databaseColumnId . '=core_follow.follow_rel_id AND core_follow.follow_app=?', $currentType[0] ) )
);
/* Exclude hidden items */
if ( in_array( 'IPS\Content\Hideable', class_implements( $className ) ) and !$className::canViewHiddenItems() )
{
@@ -78,6 +77,17 @@ class _Table extends \IPS\Helpers\Table\Db
}
}
/* Additional where */
$joinContainer = $className::$containerNodeClass ? TRUE : FALSE;
$joins = array();
$extraWhere = $className::followWhere( $joinContainer, $joins );
if ( $joinContainer )
{
$containerClass = $className::$containerNodeClass;
$this->joins[] = array( 'from' => $containerClass::$databaseTable, 'where' => $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::$databaseColumnId . '=' . $className::$databaseTable . '.' . $className::$databasePrefix . $className::$databaseColumnMap['container'], 'type' => 'STRAIGHT_JOIN' );
}
/* Permissions */
if ( in_array( 'IPS\Content\Permissions', class_implements( $className ) ) )
{
@@ -85,10 +95,9 @@ class _Table extends \IPS\Helpers\Table\Db
$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'] . "='*' )";
}
/* Additional where */
$joinContainer = FALSE;
$joins = array();
if ( $extraWhere = $className::followWhere( $joinContainer, $joins ) )
$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 ) );
if ( $extraWhere )
{
if( isset( $extraWhere['item'] ) )
{
@@ -104,11 +113,7 @@ class _Table extends \IPS\Helpers\Table\Db
$this->where = array_merge( array( $this->where ), $extraWhere );
}
}
if ( $joinContainer )
{
$containerClass = $className::$containerNodeClass;
$this->joins[] = array( 'from' => $containerClass::$databaseTable, 'where' => $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::$databaseColumnId . '=' . $className::$databaseTable . '.' . $className::$databasePrefix . $className::$databaseColumnMap['container'] );
}
$this->joins = array_merge( $this->joins, $joins );
}
elseif( $className == "\IPS\Member" )
@@ -117,17 +122,17 @@ class _Table extends \IPS\Helpers\Table\Db
}
else
{
$this->rowsTemplate = $className::nodeTableTemplate();
$this->rowsTemplate = $className::manageFollowNodeRow();
$this->joins = array(
array( 'from' => 'core_permission_index', 'where' => array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=?", $className::$permApp, $className::$permType, $className::$databaseTable . '.' . $className::$databasePrefix . $className::$databaseColumnId ) ),
array( 'from' => $className::$databaseTable, 'where' => array( $className::$databaseTable . '.' . $className::$databasePrefix . $className::$databaseColumnId . '=follow_rel_id AND follow_app=?', $currentType[0] ) ),
array( 'from' => 'core_permission_index', 'where' => array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $className::$databaseTable . '.' . $className::$databasePrefix . $className::$databaseColumnId, $className::$permApp, $className::$permType ) ),
);
/* Permission check? */
if ( in_array( 'IPS\Node\Permissions', class_implements( $className ) ) )
{
$this->where[1] .= ' 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()->groups ) . ' OR ' . 'core_permission_index.perm_' . $className::$permissionMap['view'] . "='*' )";
}
/* Additional where */
@@ -0,0 +1,408 @@
<?php
/**
* @brief Front Navigation Handler
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Core
* @since 30 Jun 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\core;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Front Navigation Extension: Custom Item
*/
class _FrontNavigation
{
/**
* @brief Singleton Instances
* @note This needs to be declared in any child classes as well, only declaring here for editor code-complete/error-check functionality
*/
protected static $instance = NULL;
/**
* Get instance
*
* @return static
*/
public static function i()
{
if( static::$instance === NULL )
{
$classname = get_called_class();
static::$instance = new $classname;
}
return static::$instance;
}
/**
* Get data store
*
* @param bool $noStore If true, will skip datastore and get from DB (used for ACP preview)
* @return array
*/
public static function frontNavigation( $noStore=FALSE )
{
if ( $noStore or !isset( \IPS\Data\Store::i()->frontNavigation ) )
{
$frontNavigation = array( 0 => array(), 1 => array() );
$select = \IPS\Db::i()->select( '*', 'core_menu', NULL, 'position' );
if ( count( $select ) )
{
foreach ( $select as $item )
{
if ( \IPS\Application::appIsEnabled( $item['app'] ) )
{
$frontNavigation[ intval( $item['parent'] ) ][ $item['id'] ] = $item;
}
}
}
elseif ( !$noStore )
{
static::buildDefaultFrontNavigation();
return static::frontNavigation();
}
if ( $noStore )
{
return $frontNavigation;
}
\IPS\Data\Store::i()->frontNavigation = $frontNavigation;
}
return \IPS\Data\Store::i()->frontNavigation;
}
/**
* Delete front navigation items by application
*
* @param \IPS\Application $app Application deleted
* @return void
*/
public static function deleteByApplication( \IPS\Application $app )
{
foreach( \IPS\Db::i()->select( '*', 'core_menu', array( array( 'extension=?', 'CustomItem' ) ) ) as $row )
{
$config = json_decode( $row['config'], TRUE );
if ( isset( $config['menu_custom_item_url'] ) and $config['menu_custom_item_url'] and isset( $config['internal'] ) and $config['internal'] )
{
try
{
$data = \IPS\Http\Url::internal( $config['menu_custom_item_url'], NULL, $config['internal'] )->getFriendlyUrlData();
if ( ! empty( $data['app'] ) and $data['app'] === $app->directory )
{
\IPS\Db::i()->delete( 'core_menu', array( 'id=?', $row['id'] ) );
}
}
catch( \Exception $e ) { }
}
}
\IPS\Db::i()->delete( 'core_menu', array( 'app=?', $app->directory ) );
unset( \IPS\Data\Store::i()->frontNavigation );
}
/**
* Build default front navigation
*
* @return void
*/
public static function buildDefaultFrontNavigation()
{
\IPS\Db::i()->delete( 'core_menu' );
$topLevelPosition = 1;
/* Browse */
\IPS\Db::i()->insert( 'core_menu', array(
'id' => 1,
'app' => 'core',
'extension' => 'CustomItem',
'config' => json_encode( array( 'menu_custom_item_url' => '', 'internal' => '' ) ),
'position' => $topLevelPosition++,
'parent' => NULL,
'permissions' => '*',
) );
/* Activity */
\IPS\Db::i()->insert( 'core_menu', array(
'id' => 2,
'app' => 'core',
'extension' => 'CustomItem',
'config' => json_encode( array( 'menu_custom_item_url' => 'app=core&module=discover&controller=streams', 'internal' => 'discover_all' ) ),
'position' => $topLevelPosition++,
'parent' => NULL,
'permissions' => '*',
) );
/* Commerce requires a bit of a special set up */
if ( \IPS\Application::appIsEnabled('nexus') )
{
/* Store */
\IPS\Db::i()->insert( 'core_menu', array(
'id' => 4,
'app' => 'nexus',
'extension' => 'Store',
'config' => json_encode( array() ),
'position' => $topLevelPosition++,
'parent' => NULL,
'permissions' => '*',
) );
/* Support */
\IPS\Db::i()->insert( 'core_menu', array(
'id' => 5,
'app' => 'core',
'extension' => 'CustomItem',
'config' => json_encode( array( 'menu_custom_item_url' => 'app=nexus&module=support&controller=home', 'internal' => 'support' ) ),
'position' => $topLevelPosition++,
'parent' => NULL,
'permissions' => '*',
) );
/* My Details */
\IPS\Db::i()->insert( 'core_menu', array(
'id' => 6,
'app' => 'core',
'extension' => 'Menu',
'config' => json_encode( array() ),
'position' => 5,
'parent' => 4,
'permissions' => '*',
) );
}
/* Browse subitems */
$communityApps = array( 'forums', 'gallery', 'downloads', 'cms', 'blog', 'calendar', 'chat' );
$browsePosition = 1;
$toInsert = array();
foreach ( $communityApps as $app )
{
if ( \IPS\Application::appIsEnabled( $app ) )
{
foreach ( array_keys( \IPS\Application::load( $app )->extensions( 'core', 'FrontNavigation', FALSE ) ) as $key )
{
$config = array();
if ( $app === 'cms' and $key === 'Cms' )
{
/* Cms.php was added to prevent upgrades breaking */
continue;
}
if ( $app === 'cms' and $key === 'Pages' )
{
try
{
$defaultPage = \IPS\cms\Pages\Page::getDefaultPage();
$config = array( 'menu_content_page' => $defaultPage->id, 'menu_title_page_type' => 0 );
}
catch( \OutOfRangeException $ex )
{
continue;
}
}
$toInsert[] = array(
'app' => $app,
'extension' => $key,
'config' => json_encode( $config ),
'position' => $browsePosition++,
'parent' => 1,
'permissions' => '',
);
}
}
}
/* Insert Apps */
\IPS\Db::i()->insert( 'core_menu', $toInsert );
/* Other 'browse' items */
foreach ( array( 'Guidelines', 'StaffDirectory', 'OnlineUsers' ) as $k )
{
\IPS\Db::i()->insert( 'core_menu', array(
'app' => 'core',
'extension' => $k,
'config' => json_encode( array() ),
'position' => $browsePosition++,
'parent' => 1,
'permissions' => '',
) );
}
/* Activity Subitems */
$activityPosition = 1;
foreach ( array( 'AllActivity', 'YourActivityStreams' ) as $k )
{
\IPS\Db::i()->insert( 'core_menu', array(
'app' => 'core',
'extension' => $k,
'config' => json_encode( array() ),
'position' => $activityPosition++,
'parent' => 2,
'permissions' => '',
) );
}
foreach ( array( 1, 2 ) as $k )
{
try
{
\IPS\core\Stream::load( $k );
\IPS\Db::i()->insert( 'core_menu', array(
'app' => 'core',
'extension' => 'YourActivityStreamsItem',
'config' => json_encode( array( 'menu_stream_id' => $k ) ),
'position' => $activityPosition++,
'parent' => 2,
'permissions' => '',
) );
}
catch ( \Exception $e ) { }
}
foreach ( array( 'Search' ) as $k )
{
\IPS\Db::i()->insert( 'core_menu', array(
'app' => 'core',
'extension' => $k,
'config' => json_encode( array() ),
'position' => $activityPosition++,
'parent' => 2,
'permissions' => '',
) );
}
/* Commerce requires a bit of a special set up */
if ( \IPS\Application::appIsEnabled('nexus') )
{
$storePosition = 1;
foreach ( array( 'Donations', 'Orders', 'Purchases' ) as $k )
{
\IPS\Db::i()->insert( 'core_menu', array(
'app' => 'nexus',
'extension' => $k,
'config' => json_encode( array() ),
'position' => $storePosition++,
'parent' => 4,
'permissions' => '',
) );
}
$myDetailsPosition = 1;
foreach ( array( 'Info', 'Addresses', 'Cards', 'Credit', 'Alternatives', 'Referrals' ) as $k )
{
\IPS\Db::i()->insert( 'core_menu', array(
'app' => 'nexus',
'extension' => $k,
'config' => json_encode( array() ),
'position' => $myDetailsPosition++,
'parent' => 6,
'permissions' => '',
) );
}
$supportPosition = 1;
foreach ( array( 'Support', 'NetworkStatus' ) as $k )
{
\IPS\Db::i()->insert( 'core_menu', array(
'app' => 'nexus',
'extension' => $k,
'config' => json_encode( array() ),
'position' => $supportPosition++,
'parent' => 5,
'permissions' => '',
) );
}
}
/* Other apps */
$toGiveToParents = array();
$parentIds = array();
foreach ( \IPS\Application::allExtensions( 'core', 'FrontNavigation', FALSE, NULL, NULL, FALSE ) as $key => $class )
{
$exploded = explode( '_', $key );
if ( !in_array( $exploded[0], array_merge( $communityApps, array( 'core', 'nexus' ) ) ) )
{
$parentIds[ $exploded[0] ][ $exploded[1] ] = \IPS\Db::i()->insert( 'core_menu', array(
'app' => $exploded[0],
'extension' => $exploded[1],
'config' => json_encode( array() ),
'position' => $topLevelPosition++,
'parent' => NULL,
'permissions' => '',
) );
}
}
}
/**
* @brief The active primary navigation bar
*/
public $activePrimaryNavBar = NULL;
/**
* Get roots
*
* @param bool $noStore If true, will skip datastore and get from DB (used for ACP preview)
* @return array
*/
public function roots( $noStore=FALSE )
{
$frontNavigation = static::frontNavigation( $noStore );
$return = array();
foreach ( $frontNavigation[0] as $item )
{
$class = 'IPS\\' . $item['app'] . '\extensions\core\FrontNavigation\\' . $item['extension'];
$object = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'] );
if ( !$this->activePrimaryNavBar )
{
$this->activePrimaryNavBar = $item['id'];
}
$return[ $item['id'] ] = $object;
}
return $return;
}
/**
* Get sub-bars
*
* @param bool $noStore If true, will skip datastore and get from DB (used for ACP preview)
* @return array
*/
public function subBars( $noStore=FALSE )
{
$frontNavigation = static::frontNavigation( $noStore );
$return = array();
$parentIDs = array();
// Changed so that empty sub bars don't add an array to their parent, allowing us to do count( $subBars ) and figure
// out if there's any to show.
foreach ( $frontNavigation[0] as $item )
{
$parentIDs[] = $item['id'];
}
foreach ( $parentIDs as $i )
{
if ( isset( $frontNavigation[$i] ) )
{
foreach ( $frontNavigation[$i] as $item )
{
$class = 'IPS\\' . $item['app'] . '\extensions\core\FrontNavigation\\' . $item['extension'];
$return[ $item['parent'] ][ $item['id'] ] = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'] );
}
}
}
return $return;
}
}
@@ -0,0 +1,200 @@
<?php
/**
* @brief Abstract Front Navigation Extension
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Core
* @since 30 Jun 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\core\FrontNavigation;
/* 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;
}
/**
* Front Navigation Extension: Custom Item
*/
abstract class _FrontNavigationAbstract
{
const DEFAULT_LOCATION_ROOT = NULL;
const DEFAULT_LOCATION_BROWSE = 1;
const DEFAULT_LOCATION_ACTIVITY = 2;
const DEFAULT_LOCATION_MORE = 3;
/**
* Get sub items of an item
*
* @return array
*/
public function subItems()
{
$frontNavigation = \IPS\core\FrontNavigation::frontNavigation();
$return = array();
if ( isset( $frontNavigation[ $this->id ] ) )
{
foreach ( $frontNavigation[ $this->id ] as $item )
{
$class = 'IPS\\' . $item['app'] . '\extensions\core\FrontNavigation\\' . $item['extension'];
$return[ $item['id'] ] = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'] );
}
}
return $return;
}
/**
* Default Location
*
* @return string
*/
public static function defaultLocation()
{
return static::DEFAULT_LOCATION_BROWSE;
}
/**
* Allow multiple instances?
*
* @return string
*/
public static function allowMultiple()
{
return FALSE;
}
/**
* Get configuration fields
*
* @param array $configuration The existing configuration, if editing an existing item
* @param int $id The ID number of the existing item, if editing
* @return array
*/
public static function configuration( $existingConfiguration, $id = NULL )
{
return array();
}
/**
* Parse configuration fields
*
* @param array $configuration The values received from the form
* @return array
*/
public static function parseConfiguration( $configuration, $id )
{
return $configuration;
}
/**
* @brief The configuration
*/
protected $configuration;
/**
* @brief The ID number
*/
public $id;
/**
* @brief The permissions
*/
public $permissions;
/**
* Constructor
*
* @param array $configuration The configuration
* @param int $id The ID number
* @param string $permissions The permissions (* or comma-delimited list of groups)
* @return void
*/
public function __construct( $configuration, $id, $permissions )
{
$this->configuration = $configuration;
$this->id = $id;
$this->permissions = $permissions;
}
/**
* Permissions can be inherited?
*
* @return bool
*/
public static function permissionsCanInherit()
{
return TRUE;
}
/**
* Can access?
*
* @return bool
*/
public function canView()
{
if ( $this->permissions and $this->permissions != '*' )
{
return \IPS\Member::loggedIn()->inGroup( explode( ',', $this->permissions ) );
}
return TRUE;
}
/**
* Get Title
*
* @return string
*/
abstract public function title();
/**
* Get Link
*
* @return \IPS\Http\Url
*/
abstract public function link();
/**
* Get Attributes
*
* @return string
*/
public function attributes()
{
return '';
}
/**
* Is Active?
*
* @return bool
*/
public function active()
{
foreach ( $this->subItems() as $item )
{
if ( $item->active() )
{
return TRUE;
}
}
return FALSE;
}
/**
* Children
*
* @return array
*/
public function children()
{
return NULL;
}
}
@@ -612,4 +612,76 @@ class _Conversation extends \IPS\Content\Item
$this->delete();
}
}
/**
* @brief Particpant blurb
*/
public $participantBlurb = NULL;
/**
* Get participant blurb
*
* @return string
*/
public function participantBlurb()
{
if( $this->participantBlurb !== NULL )
{
return $this->participantBlurb;
}
$people = array();
foreach( \IPS\Db::i()->select( 'member_id, name', 'core_members', array( \IPS\Db::i()->in( 'member_id', array_keys( $this->maps() ) ) ) ) as $member )
{
if ( $member['member_id'] === \IPS\Member::loggedIn()->member_id )
{
$member['name'] = ( $member['member_id'] == $this->starter_id ) ? \IPS\Member::loggedIn()->language()->addToStack('participant_you_upper') : \IPS\Member::loggedIn()->language()->addToStack('participant_you_lower');
}
$people[ $member['member_id'] ] = $member['name'];
}
/* Move the starter to the front of the array */
$starter = $people[ $this->starter_id ];
unset( $people[ $this->starter_id ] );
array_unshift( $people, $starter );
unset( $starter );
if ( count( $people ) == 1 )
{
$id = key( $people );
$name = array_pop( $people );
$this->participantBlurb = \IPS\Member::loggedIn()->member_id === $id ? \IPS\Member::loggedIn()->language()->addToStack( 'participant_you_upper' ) : $name;
}
elseif ( count( $people ) == 2 )
{
$this->participantBlurb = \IPS\Member::loggedIn()->language()->addToStack( 'participant_two', FALSE, array( 'sprintf' => $people ) );
}
else
{
$count = 0;
$others = array();
$sprintf = array();
foreach( $people as $id => $name )
{
if ( $count > 1 )
{
$others[] = $name;
}
else
{
$sprintf[] = $name;
}
$count++;
}
$sprintf[] = \IPS\Member::loggedIn()->language()->formatList( $others );
$sprintf[] = count( $others );
$this->participantBlurb = \IPS\Member::loggedIn()->language()->addToStack( 'participant_three_plus', FALSE, array( 'pluralize' => array( count( $others ) ), 'sprintf' => $sprintf ) );
}
return $this->participantBlurb;
}
}
@@ -171,6 +171,17 @@ class _Field extends \IPS\CustomField
}
static::$editorOptions['autoSaveKey'] = md5( get_called_class() . '-' . $row['pf_id'] ) . ( isset( \IPS\Request::i()->id ) ? '-' . \IPS\Request::i()->id : '' );
if( $row['pf_type'] == 'Editor' )
{
static::$editorOptions['attachIds'] = array( ( isset( \IPS\Request::i()->id ) ? \IPS\Request::i()->id : 0 ), $row['pf_id'] );
}
if ( $location === STAFF )
{
/* @link https://community.invisionpower.com/4bugtrack/active-reports/required-profile-fields-shouldnt-be-required-for-admins-r8058/ */
$row['pf_not_null'] = 0;
}
$return[ $groupId ][ $row['pf_id'] ] = static::constructFromData( $row )->buildHelper( $values[ 'field_' . $row['pf_id'] ] );
}
@@ -178,6 +189,32 @@ class _Field extends \IPS\CustomField
return $return;
}
/**
* 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 )
{
$result = parent::load( $id, $idField, $extraWhereClause );
static::$editorOptions['autoSaveKey'] = md5( get_called_class() . '-' . $result->id ) . ( isset( \IPS\Request::i()->id ) ? '-' . \IPS\Request::i()->id : '' );
if( $result->type == 'Editor' )
{
static::$editorOptions['attachIds'] = array( ( isset( \IPS\Request::i()->id ) ? \IPS\Request::i()->id : 0 ), $result->id );
}
return $result;
}
/**
* Get Values
@@ -140,7 +140,7 @@ class _Facebook extends ProfileSyncAbstract
if( isset( $statusData['message'] ) )
{
$status = \IPS\core\Statuses\Status::createItem( $this->member, $this->member->ip_address, new \IPS\DateTime( $statusData['updated_time'] ) );
$status->content = nl2br( $statusData['message'], FALSE );
$status->content = nl2br( \IPS\Text\Parser::utf8mb4SafeDecode( $statusData['message'] ), FALSE );
return $status;
}
}
@@ -146,7 +146,7 @@ class _Google extends ProfileSyncAbstract
if ( $status['object']['content'] )
{
$statusObj = \IPS\core\Statuses\Status::createItem( $this->member, $this->member->ip_address, new \IPS\DateTime( $status['published'] ) );
$statusObj->content = nl2br( $status['object']['content'], FALSE );
$statusObj->content = nl2br( \IPS\Text\Parser::utf8mb4SafeDecode( $status['object']['content'] ), FALSE );
return $statusObj;
}
}
@@ -38,7 +38,7 @@ abstract class _ProfileSyncAbstract
$services = array();
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . '/applications/core/sources/ProfileSync' ) as $file )
{
if ( !$file->isDot() and mb_substr( $file, -4 ) === '.php' )
if ( !$file->isDot() and mb_substr( $file, -4 ) === '.php' and mb_substr( $file, 0, 2 ) != '._' )
{
require_once $file->getPathname();
$class = 'IPS\core\ProfileSync\\' . mb_substr( $file, 0, -4 );
@@ -173,6 +173,7 @@ abstract class _ProfileSyncAbstract
*/
public function save( $values )
{
/* Get existing settings */
$settings = $this->member->profilesync ? json_decode( $this->member->profilesync, TRUE ) : array(
static::$loginKey => array(
'photo' => FALSE,
@@ -180,12 +181,30 @@ abstract class _ProfileSyncAbstract
'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'] : ( isset( $settings[ static::$loginKey ]['status'] ) ? $settings[ static::$loginKey ]['status'] : FALSE ),
);
'status' => isset( $values['profilesync_status'] ) ? ( $values['profilesync_status'] == 'none' ) ? FALSE : $values['profilesync_status'] : ( 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 */
@@ -227,7 +246,7 @@ abstract class _ProfileSyncAbstract
}
}
}
catch ( \IPS\Http\Request\Exception $e ) {}
catch ( \RuntimeException $e ) {}
}
if ( $settings['cover'] and $this->member->group['gbw_allow_upload_bgimage'] )
{
@@ -240,9 +259,13 @@ abstract class _ProfileSyncAbstract
catch ( \Exception $e ) { }
}
$this->member->pp_cover_photo = (string) $this->cover();
try
{
$this->member->pp_cover_photo = (string) $this->cover();
}
catch ( \RuntimeException $e ) {}
}
if ( $settings['status'] and $settings['status'] == 'import' )
if ( $settings['status'] and $settings['status'] == 'import' and !$this->member->group['gbw_no_status_import'] )
{
if ( $status = $this->status() )
{
@@ -161,7 +161,7 @@ class _Twitter extends ProfileSyncAbstract
if ( isset( $user['status'] ) )
{
$status = \IPS\core\Statuses\Status::createItem( $this->member, $this->member->ip_address, new \IPS\DateTime( $user['status']['created_at'] ) );
$status->content = $user['status']['text'];
$status->content = \IPS\Text\Parser::utf8mb4SafeDecode( $user['status']['text'] );
return $status;
}
+19 -12
View File
@@ -34,23 +34,24 @@ class _Install
/* PHP Version */
$phpVersion = PHP_VERSION;
if ( version_compare( PHP_VERSION, '5.3.0' ) >= 0 )
if ( version_compare( $phpVersion, '5.3.0' ) >= 0 )
{
$return['requirements']['PHP'][] = array(
$return['requirements']['PHP']['version'] = array(
'success' => TRUE,
'message' => \IPS\Member::loggedIn()->language()->addToStack( 'requirements_php_version_success', FALSE, array( 'sprintf' => array( $phpVersion ) ) )
);
}
else
{
$return['requirements']['PHP'][] = array(
$return['requirements']['PHP']['version'] = array(
'success' => FALSE,
'message' => \IPS\Member::loggedIn()->language()->addToStack( 'requirements_php_version_fail', FALSE, array( 'sprintf' => array( $phpVersion ) ) ),
);
}
if ( version_compare( PHP_VERSION, '5.4.0' ) == -1 )
if ( version_compare( $phpVersion, '5.4.0' ) == -1 )
{
$return['advice'][] = \IPS\Member::loggedIn()->language()->addToStack( 'requirements_php_version_advice', FALSE, array( 'sprintf' => array( $phpVersion ) ) );
$return['requirements']['PHP']['version']['message'] = \IPS\Member::loggedIn()->language()->addToStack( 'requirements_php_version_success_53x', FALSE, array( 'sprintf' => array( $phpVersion ) ) );
$return['advice']['php'] = \IPS\Member::loggedIn()->language()->addToStack( 'requirements_php_version_advice', FALSE, array( 'sprintf' => array( $phpVersion ) ) );
}
/* cURL or allow_url_fopen */
@@ -623,6 +624,7 @@ class _Install
\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'" );
unset( \IPS\Data\Store::i()->settings );
@@ -807,15 +809,15 @@ class _Install
{
return $this->appLoop( $data, function( $app )
{
if( !file_exists( \IPS\ROOT_PATH . '/plugins/hooks.txt' ) )
if( !file_exists( \IPS\ROOT_PATH . '/plugins/hooks.php' ) )
{
@touch( \IPS\ROOT_PATH . '/plugins/hooks.txt' );
@chmod( \IPS\ROOT_PATH . '/plugins/hooks.txt', 0777 );
@touch( \IPS\ROOT_PATH . '/plugins/hooks.php' );
@chmod( \IPS\ROOT_PATH . '/plugins/hooks.php', 0777 );
}
if( !is_writable( \IPS\ROOT_PATH . '/plugins/hooks.txt' ) )
if( !is_writable( \IPS\ROOT_PATH . '/plugins/hooks.php' ) )
{
throw new \RuntimeException( sprintf( \IPS\Member::loggedIn()->language()->get("hook_file_not_writable"), \IPS\ROOT_PATH . '/plugins/hooks.txt' ) );
throw new \RuntimeException( sprintf( \IPS\Member::loggedIn()->language()->get("hook_file_not_writable"), \IPS\ROOT_PATH . '/plugins/hooks.php' ) );
}
try
@@ -842,16 +844,21 @@ class _Install
$inserts = array();
foreach( json_decode( file_get_contents( \IPS\ROOT_PATH . '/' . \IPS\CP_DIRECTORY ."/install/emoticons/data.json" ) ) as $type => $file )
foreach( json_decode( file_get_contents( \IPS\ROOT_PATH . '/' . \IPS\CP_DIRECTORY ."/install/emoticons/data.json" ), TRUE ) as $type => $file )
{
$fileObj = \IPS\File::create( 'core_Emoticons', $file, file_get_contents( \IPS\ROOT_PATH . '/' . \IPS\CP_DIRECTORY . "/install/emoticons/" . $file ), 'emoticons', FALSE, NULL, FALSE );
$fileObj = \IPS\File::create( 'core_Emoticons', $file['image'], file_get_contents( \IPS\ROOT_PATH . '/' . \IPS\CP_DIRECTORY . "/install/emoticons/" . $file['image'] ), 'emoticons', FALSE, NULL, FALSE );
$fileObj2x = isset( $file['image_2x'] ) ? \IPS\File::create( 'core_Emoticons', $file['image_2x'], file_get_contents( \IPS\ROOT_PATH . '/' . \IPS\CP_DIRECTORY . "/install/emoticons/" . $file['image_2x'] ), 'emoticons', FALSE, NULL, FALSE ) : NULL;
$imageProperties = @getimagesize( \IPS\ROOT_PATH . '/' . \IPS\CP_DIRECTORY . "/install/emoticons/" . $file['image'] );
$inserts[] = array(
'typed' => $type,
'image' => (string) $fileObj,
'image_2x' => (string) $fileObj2x,
'clickable' => TRUE,
'emo_set' => $setId,
'emo_position' => ++$position,
'width' => isset( $imageProperties[0] ) ? $imageProperties[0] : 0,
'height' => isset( $imageProperties[1] ) ? $imageProperties[1] : 0
);
}
+6 -6
View File
@@ -79,7 +79,7 @@ class _Upgrade
);
}
}
catch ( \LogicException $e )
catch ( \Exception $e )
{
$fileSystemItems[ $class->configurationId ] = array(
'success' => FALSE,
@@ -673,15 +673,15 @@ class _Upgrade
{
return $this->appLoop( $data, function( $app )
{
if( !file_exists( \IPS\ROOT_PATH . '/plugins/hooks.txt' ) )
if( !file_exists( \IPS\ROOT_PATH . '/plugins/hooks.php' ) )
{
@touch( \IPS\ROOT_PATH . '/plugins/hooks.txt' );
@chmod( \IPS\ROOT_PATH . '/plugins/hooks.txt', 0777 );
@touch( \IPS\ROOT_PATH . '/plugins/hooks.php' );
@chmod( \IPS\ROOT_PATH . '/plugins/hooks.php', 0777 );
}
if( !is_writable( \IPS\ROOT_PATH . '/plugins/hooks.txt' ) )
if( !is_writable( \IPS\ROOT_PATH . '/plugins/hooks.php' ) )
{
throw new \RuntimeException( sprintf( \IPS\Member::loggedIn()->language()->get("hook_file_not_writable"), \IPS\ROOT_PATH . '/plugins/hooks.txt' ) );
throw new \RuntimeException( sprintf( \IPS\Member::loggedIn()->language()->get("hook_file_not_writable"), \IPS\ROOT_PATH . '/plugins/hooks.php' ) );
}
try
@@ -83,7 +83,7 @@ class _Service extends \IPS\Node\Model
}
/**
* Fetch All Services
* Fetch All Nodes
*
* @return array
*/
@@ -102,6 +102,49 @@ class _Service extends \IPS\Node\Model
return $return;
}
/**
* Fetch All Share Services
*
* @param \IPS\Http\Url $url URL to the content [optional - if omitted, some services will figure out on their own]
* @param string $title Default text for the content, usually the title [optional - if omitted, some services will figure out on their own]
* @param \IPS\Member|NULL $member Member the links will display to or NULL for currently logged in member
* @return array
*/
public static function getAllServices( \IPS\Http\Url $url, $title, \IPS\Member $member = NULL )
{
$member = $member ?: \IPS\Member::loggedIn();
$services = array();
foreach( static::shareLinks() as $node )
{
if( $node->enabled and ( $node->groups === "*" or $member->inGroup( explode( ',', $node->groups ) ) ) )
{
try
{
$services[ $node->key ] = $node->getService( $url, $title );
}
catch ( \LogicException $e ) { }
}
}
return $services;
}
/**
* Get \IPS\Content\ShareServices class
*
* @param \IPS\Http\Url $url URL to the content [optional - if omitted, some services will figure out on their own]
* @param string $title Default text for the content, usually the title [optional - if omitted, some services will figure out on their own]
* @return array
*/
public function getService( \IPS\Http\Url $url, $title )
{
if( file_exists( \IPS\ROOT_PATH . '/system/Content/ShareServices/' . ucwords( $this->key ) . '.php' ) )
{
$className = "IPS\\Content\\ShareServices\\" . ucwords( $this->key );
return new $className( $url, $title );
}
throw new \LogicException;
}
/**
* [Node] Get buttons to display in tree
@@ -186,7 +186,7 @@ class _Group extends \IPS\Node\Model
}
else
{
foreach ( \IPS\Db::i()->select( '*', 'core_members', array( 'member_group_id=?', $child->type_id ), 'name' ) as $m )
foreach ( \IPS\Db::i()->select( '*', 'core_members', array( 'member_group_id=? OR FIND_IN_SET( ?, mgroup_others )', $child->type_id, $child->type_id ), 'name' ) as $m )
{
if ( !isset( $members[ $m['member_id'] ] ) )
{
+12 -2
View File
@@ -88,7 +88,7 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\ReportCenter,
/**
* @brief [Content\Comment] Comment Template
*/
public static $commentTemplate = array( array( 'profile', 'core', 'front' ), 'statusReplyContainer' );
public static $commentTemplate = array( array( 'statuses', 'core', 'front' ), 'statusReplyContainer' );
/**
* @brief [Content] Key for hide reasons
@@ -118,7 +118,7 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\ReportCenter,
if( !isset( $this->_url[ $_key ] ) )
{
$member = \IPS\Member::load( $this->item()->member_id );
$this->_url[ $_key ] = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$member->member_id}&status={$this->id}", 'front', 'profile', array( $member->members_seo_name ) )->setQueryString( 'type', 'status' );
$this->_url[ $_key ] = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$member->member_id}&status={$this->item()->id}&reply={$this->id}", 'front', 'profile', array( $member->members_seo_name ) )->setQueryString( 'type', 'status' );
if ( $action )
{
@@ -185,4 +185,14 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\ReportCenter,
return parent::canDelete( $member );
}
/**
* Get template for content tables
*
* @return callable
*/
public static function contentTableTemplate()
{
return array( \IPS\Theme::i()->getTemplate( 'statuses', 'core' ), 'statusReplyContentRows' );
}
}
+100 -42
View File
@@ -21,7 +21,7 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
/**
* Status Update Model
*/
class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \IPS\Content\Reputation, \IPS\Content\Lockable, \IPS\Content\Hideable
class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \IPS\Content\Reputation, \IPS\Content\Lockable, \IPS\Content\Hideable, \IPS\Content\Searchable
{
/* !\IPS\Patterns\ActiveRecord */
@@ -55,6 +55,21 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
*/
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
*
* @return array|string
*/
public static function basicDataColumns()
{
return '*';
}
/**
* Save Changed Columns
*
@@ -164,6 +179,16 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
return strip_tags( $this->mapped('content') );
}
/**
* Get container ID for search index
*
* @return int
*/
public function searchIndexContainer()
{
return $this->member_id;
}
/**
* Get mapped value
*
@@ -174,7 +199,8 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
{
if ( $key === 'title' )
{
return $this->title;
/* We do not want the content (which is mapped to title in $databaseColumnMap) to be added into core_search_index.index_title as the field is only varchar(255) */
return mb_substr( $this->title, 0, 85 );
}
return parent::mapped( $key );
@@ -204,20 +230,33 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
{
return FALSE;
}
/* Disabled at the group level? */
if( \IPS\Member::loggedIn()->group['gbw_no_status_update'] )
if ( $member->pp_setting_count_comments === 0 or !\IPS\Settings::i()->profile_comments )
{
return FALSE;
}
/* Or at the profile level? */
if( \IPS\Member::loggedIn()->members_bitoptions['bw_no_status_update'] )
return TRUE;
}
/**
* Can a given member reply to a status update?
*
* @param \IPS\Member $member The member
* @param \IPS\Node\Model|NULL $container Container
* @param bool $showError If TRUE, rather than returning a boolean value, will display an error
* @return bool
*/
public static function canCreateReply( \IPS\Member $member, \IPS\Node\Model $container=NULL, $showError=FALSE )
{
/* Can we access the module? */
if ( !parent::canCreate( $member, $container, $showError ) )
{
return FALSE;
}
if ( !$member->pp_setting_count_comments or !\IPS\Settings::i()->profile_comments )
/* We have to be logged in */
if ( !$member->member_id )
{
return FALSE;
}
@@ -249,25 +288,19 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
return FALSE;
}
/* And not restricted */
if ( $member->members_bitoptions['bw_no_status_update'] )
{
return FALSE;
}
/* Is the user being ignored */
if ( $profileOwner->isIgnoring( $member, 'messages' ) )
{
return FALSE;
}
/* Disabled at the group level? */
if( \IPS\Member::loggedIn()->group['gbw_no_status_update'] )
{
return FALSE;
}
/* Or at the profile level? */
if( \IPS\Member::loggedIn()->members_bitoptions['bw_no_status_update'] )
{
return FALSE;
}
if ( !$profileOwner->pp_setting_count_comments and $member->member_id != \IPS\Request::i()->id )
if ( $profileOwner->pp_setting_count_comments === 0 )
{
return FALSE;
}
@@ -375,9 +408,9 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
}
/**
* @brief Cached URLs
* @brief Cached URL
*/
protected $_url = array();
protected $_url = NULL;
/**
* Get URL
@@ -387,32 +420,31 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
*/
public function url( $action=NULL )
{
$_key = md5( $action );
if( !isset( $this->_url[ $_key ] ) )
if( $this->_url === NULL )
{
$member = \IPS\Member::load( $this->member_id );
$this->_url[ $_key ] = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$member->member_id}&status={$this->id}&type=status", 'front', 'profile', array( $member->members_seo_name ) );
$this->_url = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$member->member_id}&status={$this->id}&type=status", 'front', 'profile', array( $member->members_seo_name ) );
}
if ( $action )
$return = $this->_url;
if ( $action )
{
if ( $action == 'edit' )
{
if ( $action == 'edit' )
{
$this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( 'do', 'editStatus' );
}
else
{
$this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( array( 'do' => $action, 'type' => 'status' ) );
}
$return = $return->setQueryString( 'do', 'editStatus' );
}
else
{
$return = $return->setQueryString( array( 'do' => $action, 'type' => 'status' ) );
}
if ( $action == 'moderate' AND \IPS\Request::i()->controller == 'feed' )
{
$this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( '_fromFeed', 1 );
}
if ( $action == 'moderate' AND \IPS\Request::i()->controller == 'feed' )
{
$return = $return->setQueryString( '_fromFeed', 1 );
}
}
return $this->_url[ $_key ];
return $return;
}
/**
@@ -553,7 +585,33 @@ class _Status extends \IPS\Content\Item implements \IPS\Content\ReportCenter, \I
*/
public static function contentTableTemplate()
{
return array( \IPS\Theme::i()->getTemplate( 'profile', 'core' ), 'statusContentRows' );
return array( \IPS\Theme::i()->getTemplate( 'statuses', 'core' ), 'statusContentRows' );
}
/**
* Get HTML for search result display
*
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the author. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem )
{
$status = static::constructFromData( $itemData );
$status->reputation = $reputationData;
$profileOwner = isset( $itemData['profile'] ) ? $itemData['profile'] : NULL;
$profileOwnerData = $profileOwner ?: $authorData;
$status->_url = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$profileOwnerData['member_id']}&status={$itemData['status_id']}&type=status", 'front', 'profile', array( $profileOwnerData['members_seo_name'] ) );
return \IPS\Theme::i()->getTemplate( 'statuses', 'core', 'front' )->statusContainer( $status, $authorData, $profileOwner, $view == 'condensed' );
}
/**
+802
View File
@@ -0,0 +1,802 @@
<?php
/**
* @brief Content Discovery Stream
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @since 1 Jul 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\core;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Content Discovery Stream
*/
class _Stream extends \IPS\Node\Model
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'core_streams';
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'position';
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'streams';
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'stream_title_';
/**
* @brief [Node] ACP Restrictions
* @code
array(
'app' => 'core', // The application key which holds the restrictrions
'module' => 'foo', // The module key which holds the restrictions
'map' => array( // [Optional] The key for each restriction - can alternatively use "prefix"
'add' => 'foo_add',
'edit' => 'foo_edit',
'permissions' => 'foo_perms',
'delete' => 'foo_delete'
),
'all' => 'foo_manage', // [Optional] The key to use for any restriction not provided in the map (only needed if not providing all 4)
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
* @encode
*/
protected static $restrictions = array(
'app' => 'core',
'module' => 'discovery',
'all' => 'streams_manage',
);
/**
* @brief The default stream either set by member or admin
*/
protected static $defaultStream = NULL;
/**
* 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() )
{
$where[] = array( 'member IS NULL' );
return parent::roots( $permissionCheck, $member, $where );
}
/**
* Fetch the default stream, or NULL
*
* @return \IPS\core\Stream|null
*/
public static function defaultStream()
{
/* Check the member first */
if ( \IPS\Member::loggedIn()->member_id )
{
$default = \IPS\Member::loggedIn()->defaultStream;
if ( $default !== NULL )
{
try
{
if ( $default )
{
return static::load( $default );
}
else
{
return static::allActivityStream();
}
}
catch( \Exception $e )
{
return NULL;
}
}
}
/* Still here? Check menu */
try
{
return static::load( \IPS\Db::i()->select( 'id', 'core_streams', array( array( '`default`=?', 1 ) ) )->first() );
}
catch( \Exception $e )
{
return NULL;
}
}
/**
* "All Activity" Stream
*
* @return \IPS\core\Stream
*/
public static function allActivityStream()
{
$stream = new static;
$stream->id = 0;
$stream->include_comments = TRUE;
return $stream;
}
/**
* [Node] Get Title
*
* @return string|null
*/
protected function get__title()
{
if ( $this->id )
{
return $this->title ?: parent::get__title();
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('all_activity');
}
}
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param string $titleType 'Text' or 'Translatable
* @pram bool $titleRequired Is the title field required?
* @return void
*/
public function form( &$form, $titleType='Translatable', $titleRequired=TRUE )
{
/* Title */
if ( $titleType )
{
$titleClass = '\IPS\Helpers\Form\\' . $titleType;
$form->add( new $titleClass( 'stream_title', ( $this->id and $titleType === 'Text' ) ? $this->_title : NULL, $titleRequired, array( 'app' => 'core', 'key' => ( $this->id ? "stream_title_{$this->id}" : NULL ) ) ) );
}
/* All content or specific content? */
$form->add( new \IPS\Helpers\Form\Radio( 'stream_include_comments', $this->include_comments, TRUE, array( 'options' => array(
1 => 'stream_include_comments_1',
0 => 'stream_include_comments_0'
) ) ) );
/* All content or specific content? */
$form->add( new \IPS\Helpers\Form\Radio( 'stream_classes_type', $this->classes ? 1 : 0, TRUE, array(
'options' => array( 0 => 'stream_classes_type_all', 1 => 'stream_classes_type_custom' ),
'toggles' => array( 1 => array( 'stream_classes' ) )
) ) );
/* Work out all the different classes */
$classes = array();
$classContainers = array();
$classToggles = array();
foreach ( \IPS\Content::routedClasses( TRUE, FALSE, TRUE ) as $class )
{
$classes[ $class ] = $class::$title . '_pl';
if ( isset( $class::$containerNodeClass ) )
{
$classContainers[] = $class;
$classToggles[ $class ][] = 'stream_containers_' . $class::$title;
}
}
/* Add the fields for them */
$currentContainers = $this->containers ? json_decode( $this->containers, TRUE ) : array();
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'stream_classes', $this->classes ? explode( ',', $this->classes ) : array(), NULL, array( 'options' => $classes, 'toggles' => $classToggles ), NULL, NULL, NULL, 'stream_classes' ) );
foreach ( $classContainers as $class )
{
$field = new \IPS\Helpers\Form\Node( 'stream_containers_' . $class::$title, isset( $currentContainers[ $class ] ) ? $currentContainers[ $class ] : array(), NULL, array( 'class' => $class::$containerNodeClass, 'multiple' => TRUE, 'permissionCheck' => 'view' ), NULL, NULL, NULL, 'stream_containers_' . $class::$title );
$containerClass = $class::$containerNodeClass;
$field->label = \IPS\Member::loggedIn()->language()->addToStack( $containerClass::$nodeTitle );
$form->add( $field );
}
/* Tags */
if ( \IPS\Settings::i()->tags_enabled )
{
$form->add( new \IPS\Helpers\Form\Radio( 'stream_tags_type', $this->tags ? 'custom' : 'all', TRUE, array(
'options' => array(
'all' => 'stream_tags_all',
'custom' => 'stream_tags_custom'
),
'toggles' => array(
'custom' => array( 'stream_tags' )
)
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'stream_tags', $this->tags ? explode( ',', $this->tags ) : NULL, NULL, array( 'autocomplete' => array() ), NULL, NULL, NULL, 'stream_tags' ) );
}
/* Ownership */
$form->add( new \IPS\Helpers\Form\Radio( 'stream_ownership', $this->ownership, TRUE, array(
'options' => array(
'all' => 'stream_ownership_all',
'started' => 'stream_ownership_started',
'postedin' => 'stream_ownership_postedin',
'custom' => 'stream_ownership_custom',
),
'toggles' => array(
'custom' => array( 'stream_custom_members' )
)
) ) );
$form->add( new \IPS\Helpers\Form\Member( 'stream_custom_members', $this->custom_members ? array_map( array( 'IPS\Member', 'load' ), explode( ',', $this->custom_members ) ) : NULL, NULL, array( 'multiple' => NULL ), NULL, NULL, NULL, 'stream_custom_members' ) );
/* Read */
$form->add( new \IPS\Helpers\Form\Radio( 'stream_read', $this->read, TRUE, array( 'options' => array(
'all' => 'stream_read_all',
'unread' => 'stream_read_unread',
) ) ) );
/* Follow */
$form->add( new \IPS\Helpers\Form\Radio( 'stream_follow', $this->follow, TRUE, array(
'options' => array(
'all' => 'stream_follow_all',
'followed' => 'stream_follow_followed',
),
'toggles' => array(
'followed' => array( 'stream_followed_types' )
)
) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'stream_followed_types', $this->followed_type ? explode( ',', $this->followed_types ) : array( 'items' ), NULL, array( 'options' => array(
'containers' => 'stream_followed_types_areas',
'items' => 'stream_followed_types_items',
'members' => 'stream_followed_types_members',
) ), NULL, NULL, NULL, 'stream_followed_types' ) );
/* Date */
$form->add( new \IPS\Helpers\Form\Radio( 'stream_date_type', $this->date_type, TRUE, array(
'options' => array(
'all' => 'stream_date_type_all',
'last_visit' => 'stream_date_type_last_visit',
'relative' => 'stream_date_type_relative',
'custom' => 'stream_date_type_custom',
),
'toggles' => array(
'relative' => array( 'stream_date_relative_days' ),
'custom' => array( 'stream_date_range' )
)
) ) );
$form->add( new \IPS\Helpers\Form\Number( 'stream_date_relative_days', $this->date_relative_days ?: 7, NULL, array(), function( $val )
{
if ( \IPS\Request::i()->stream_date_type == 'relative' and !$val )
{
throw new \DomainException('form_required');
}
}, \IPS\Member::loggedIn()->language()->addToStack('stream_date_relative_days_prefix'), \IPS\Member::loggedIn()->language()->addToStack('days'), 'stream_date_relative_days' ) );
$form->add( new \IPS\Helpers\Form\DateRange( 'stream_date_range', array( 'start' => $this->date_start, 'end' => $this->date_end ), NULL, array(), function( $val )
{
if ( \IPS\Request::i()->stream_date_type == 'custom' and !$val['start'] and !$val['end'] )
{
throw new \DomainException('form_required');
}
}, NULL, NULL, 'stream_date_range' ) );
/* Sort */
$form->add( new \IPS\Helpers\Form\Radio( 'stream_sort', $this->sort, TRUE, array( 'options' => array(
'newest' => 'stream_sort_newest',
'oldest' => 'stream_sort_oldest',
) ) ) );
if ( \IPS\Dispatcher::i()->controllerLocation === 'admin' )
{
$form->add( new \IPS\Helpers\Form\YesNo( 'stream_default', $this->default, FALSE ) );
}
}
/**
* [Node] Format form values from add/edit form for save
*
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
{
/* Title */
if ( !$this->id )
{
$this->save();
}
if ( isset( $values['stream_title'] ) and is_array( $values['stream_title'] ) )
{
\IPS\Lang::saveCustom( 'core', "stream_title_{$this->id}", $values['stream_title'] );
$values['stream_title'] = NULL;
}
unset( $values['__custom_stream'] );
/* Sort out stream_classes_type */
if ( $values['stream_classes_type'] )
{
$classes = array();
$containers = NULL;
foreach ( $values['stream_classes'] as $class )
{
$classes[] = $class;
if ( isset( $values[ 'stream_containers_' . $class::$title ] ) and $values[ 'stream_containers_' . $class::$title ] )
{
$containers[ $class ] = array_keys( $values[ 'stream_containers_' . $class::$title ] );
}
}
$values['stream_classes'] = implode( ',', $classes );
$values['stream_containers'] = $containers ? json_encode( $containers ) : NULL;
}
else
{
$values['stream_classes'] = NULL;
$values['stream_containers'] = NULL;
}
unset( $values['stream_classes_type'] );
/* And tags */
if ( $values['stream_tags_type'] == 'all' or !$values['stream_tags'] )
{
$values['stream_tags'] = NULL;
}
else
{
$values['stream_tags'] = implode( ',', $values['stream_tags'] );
}
unset( $values['stream_tags_type'] );
/* And follows */
$values['stream_followed_types'] = ( $values['stream_follow'] == 'followed' ? implode( ',', $values['stream_followed_types'] ) : NULL );
/* And members */
if ( $values['stream_ownership'] == 'custom' )
{
$members = array();
foreach ( $values['stream_custom_members'] as $member )
{
$members[] = $member->member_id;
}
$values['stream_custom_members'] = implode( ',', $members );
}
else
{
$values['stream_custom_members'] = NULL;
}
/* And dates */
if ( $values['stream_date_type'] )
{
$values['stream_date_start'] = $values['stream_date_range']['start'] ? $values['stream_date_range']['start']->getTimestamp() : NULL;
$values['stream_date_end'] = $values['stream_date_range']['end'] ? $values['stream_date_range']['end']->getTimestamp() : NULL;
}
unset( $values['stream_date_range'] );
if ( \IPS\Dispatcher::i()->controllerLocation === 'admin' and ! empty( $values['stream_default'] ) )
{
$where = ( $this->id ) ? array( 'id !=?', $this->id ) : NULL;
\IPS\Db::i()->update( 'core_streams', array( 'default' => 0 ), $where );
}
/* Remove stream_ prefix */
$_values = $values;
$values = array();
foreach ( $_values as $k => $v )
{
if( mb_substr( $k, 0, 15 ) === 'stream_classes_' or mb_substr( $k, 0, 18 ) === 'stream_containers_' )
{
continue;
}
if( mb_substr( $k, 0, 7 ) === 'stream_' )
{
$values[ mb_substr( $k, 7 ) ] = $v;
}
else
{
$values[ $k ] = $v;
}
}
/* Return */
return $values;
}
/**
* Get blurb
*
* @return string
*/
public function blurb()
{
if ( $this->classes )
{
$classes = array();
foreach ( explode( ',', $this->classes ) as $class )
{
if ( class_exists( $class ) )
{
if ( in_array( 'IPS\Content\Review', class_parents( $class ) ) )
{
$classes[ $class::$itemClass ]['reviews'] = $class;
}
elseif ( in_array( 'IPS\Content\Comment', class_parents( $class ) ) )
{
$classes[ $class::$itemClass ]['comments'] = $class;
}
elseif ( in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
$classes[ $class ]['items'] = $class;
}
}
}
$types = array();
$allowedContainers = $this->containers ? json_decode( $this->containers, TRUE ) : array();
foreach ( $classes as $itemClass => $subClasses )
{
$_types = array();
foreach ( $subClasses as $class )
{
$_types[] = \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl', FALSE, array( 'strtolower' => TRUE ) );
}
$_types = \IPS\Member::loggedIn()->language()->formatList( $_types );
if ( isset( $allowedContainers[ $itemClass ] ) )
{
$containers = array();
$containerClass = $itemClass::$containerNodeClass;
foreach ( $allowedContainers[ $itemClass ] as $id )
{
try
{
$containers[] = $containerClass::loadAndCheckPerms( $id )->_title;
}
catch ( \OutOfRangeException $e ) { }
}
$containers = \IPS\Member::loggedIn()->language()->formatList( $containers );
$types[] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_blurb_in_containers', FALSE, array( 'sprintf' => array( $_types, $containers ) ) );
}
else
{
$types[] = $_types;
}
}
$type = \IPS\Member::loggedIn()->language()->formatList( $types );
}
else
{
$type = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_all');
}
$terms = array();
if ( $this->tags )
{
$terms[] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_includes_tags', FALSE, array( 'sprintf' => \IPS\Member::loggedIn()->language()->formatList( array_map( function( $val ){
return '\'' . $val . '\'';
}, explode( ',', $this->tags ) ), \IPS\Member::loggedIn()->language()->get('or_list_format') ) ) );
}
switch ( $this->ownership )
{
case 'started':
$terms[] = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_i_started');
break;
case 'postedin':
$terms[] = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_i_posted_in');
break;
case 'custom':
$memberNames = array();
foreach ( explode( ',', $this->custom_members ) as $memberId )
{
$_member = \IPS\Member::load( $memberId );
if ( $_member->member_id )
{
$memberNames[] = $_member->name;
}
}
$terms[] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_blurb_by_members', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $memberNames, \IPS\Member::loggedIn()->language()->get('or_list_format') ) ) ) );
break;
}
if ( $this->read == 'unread' )
{
$terms[] = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_unread');
}
if ( $this->follow == 'followed' )
{
$followTerms = array();
foreach ( explode( ',', $this->followed_types ) as $followType )
{
switch ( $followType )
{
case 'containers':
$followTerms[] = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_following_containers');
break;
case 'items':
$followTerms[] = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_following_items');
break;
case 'members':
$followTerms[] = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_following_members');
break;
}
}
$terms[] = \IPS\Member::loggedIn()->language()->formatList( $followTerms, \IPS\Member::loggedIn()->language()->get('or_list_format') );
}
switch ( $this->date_type )
{
case 'last_visit':
$terms[] = \IPS\Member::loggedIn()->language()->addToStack('stream_blurb_since_last_visit');
break;
case 'relative':
$terms[] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_blurb_relative', FALSE, array( 'sprintf' => array( $this->date_relative_days ) ) );
break;
case 'custom':
if ( $this->date_start and $this->date_end )
{
$terms[] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_blurb_date_between', FALSE, array( 'sprintf' => array( \IPS\DateTime::ts( $this->date_start ), \IPS\DateTime::ts( $this->date_end ) ) ) );
}
elseif ( $this->date_start )
{
$terms[] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_blurb_date_after', FALSE, array( 'sprintf' => array( \IPS\DateTime::ts( $this->date_start ) ) ) );
}
elseif ( $this->date_end )
{
$terms[] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_blurb_date_before', FALSE, array( 'sprintf' => array( \IPS\DateTime::ts( $this->date_end ) ) ) );
}
break;
}
if ( count( $terms ) )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'steam_blurb_with_terms', FALSE, array( 'sprintf' => array( $type, \IPS\Member::loggedIn()->language()->formatList( $terms ) ) ) );
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack( 'steam_blurb_no_terms', FALSE, array( 'sprintf' => array( $type ) ) );
}
}
/**
* Get results
*
* @param \IPS\Member|null $member The member to get the results as
* @return \IPS\Content\Search\Query
*/
public function query( $member = NULL )
{
/* Init */
$query = \IPS\Content\Search\Query::init( $member );
/* Content Filters */
$filters = array();
$allowedContainers = $this->containers ? json_decode( $this->containers, TRUE ) : array();
if ( $this->classes )
{
/* Translate how we store this into the format needed for filterByContent */
$classes = array();
foreach ( explode( ',', $this->classes ) as $class )
{
if ( class_exists( $class ) )
{
$classes[ $class ]['items'] = TRUE;
if ( $this->include_comments and isset( $class::$commentClass ) )
{
$classes[ $class ]['comments'] = TRUE;
}
if ( $this->include_comments and isset( $class::$reviewClass ) )
{
$classes[ $class ]['reviews'] = TRUE;
}
}
}
/* Build the filters */
foreach ( $classes as $class => $options )
{
/* Init */
if ( isset( $options['items'] ) and !isset( $options['comments'] ) and in_array( 'IPS\Content\Item', class_parents( $class ) ) and $class::$firstCommentRequired )
{
$filter = \IPS\Content\Search\ContentFilter::init( $class, TRUE, TRUE, isset( $options['reviews'] ) )->onlyLastComment();
}
else
{
$filter = \IPS\Content\Search\ContentFilter::init( $class, isset( $options['items'] ), isset( $options['comments'] ), isset( $options['reviews'] ) );
}
/* Are we restricted to certain containers? */
if ( isset( $allowedContainers[ $class ] ) )
{
$filter->onlyInContainers( $allowedContainers[ $class ] );
}
/* Add to the array */
$filters[] = $filter;
}
}
elseif ( !$this->include_comments )
{
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
{
foreach ( $object->classes as $class )
{
if ( in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
if ( $class::$firstCommentRequired )
{
$filters[] = \IPS\Content\Search\ContentFilter::init( $class, TRUE, TRUE, FALSE )->onlyLastComment();
}
else
{
$filters[] = \IPS\Content\Search\ContentFilter::init( $class, TRUE, FALSE, FALSE );
}
}
}
}
}
if ( count( $filters ) )
{
$query->filterByContent( $filters );
}
/* Ownership */
switch ( $this->ownership )
{
case 'started':
$query->filterByItemAuthor( \IPS\Member::loggedIn() );
break;
case 'postedin':
$query->filterByItemsIPostedIn();
break;
case 'custom':
$query->filterByAuthor( explode( ',', $this->custom_members ) );
break;
}
/* Read */
if ( $this->read == 'unread' )
{
$query->filterByUnread();
}
/* Follow */
if ( $this->follow == 'followed' )
{
$followTypes = explode( ',', $this->followed_types );
$query->filterByFollowed( in_array( 'containers', $followTypes ), in_array( 'items', $followTypes ), in_array( 'members', $followTypes ) );
}
/* Date */
switch ( $this->date_type )
{
case 'last_visit':
$query->filterByLastUpdatedDate( \IPS\DateTime::ts( \IPS\Member::loggedIn()->last_visit ) );
break;
case 'relative':
$query->filterByLastUpdatedDate( \IPS\DateTime::create()->sub( new \DateInterval( 'P' . intval( $this->date_relative_days ) . 'D' ) ) );
case 'custom':
$query->filterByLastUpdatedDate( $this->date_start ? \IPS\DateTime::ts( $this->date_start ) : NULL, $this->date_end ? \IPS\DateTime::ts( $this->date_end ) : NULL );
break;
}
/* Sort */
if ( $this->include_comments )
{
if ( $this->sort === 'oldest' )
{
$query->setOrder( \IPS\Content\Search\Query::ORDER_OLDEST_CREATED );
}
else
{
$query->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_CREATED );
}
}
else
{
if ( $this->sort === 'oldest' )
{
$query->setOrder( \IPS\Content\Search\Query::ORDER_OLDEST_UPDATED );
}
else
{
$query->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_UPDATED );
}
}
/* Return */
return $query;
}
/**
* URL to this stream
*
* @return \IPS\Http\Url
*/
public function url()
{
if ( $this->id )
{
switch ( $this->id )
{
case 1:
$furlKey = 'discover_unread';
break;
case 2:
$furlKey = 'discover_istarted';
break;
case 3:
$furlKey = 'discover_followed';
break;
case 4:
$furlKey = 'discover_following';
break;
case 5:
$furlKey = 'discover_posted';
break;
default:
$furlKey = 'discover_stream';
break;
}
return \IPS\Http\Url::internal( "app=core&module=discover&controller=streams&id={$this->id}", 'front', $furlKey );
}
else
{
return \IPS\Http\Url::internal( "app=core&module=discover&controller=streams", 'front', 'discover_all' );
}
}
/**
* Save Changed Columns
*
* @return void
*/
public function save()
{
parent::save();
if ( !$this->member )
{
unset( \IPS\Data\Store::i()->globalStreamIds );
}
}
/**
* [ActiveRecord] Delete Record
*
* @return void
*/
public function delete()
{
if ( $this->member )
{
parent::delete();
}
else
{
parent::delete();
unset( \IPS\Data\Store::i()->globalStreamIds );
}
}
}
+11 -3
View File
@@ -86,8 +86,9 @@ class _Reason extends \IPS\Node\Model
$form->add( new \IPS\Helpers\Form\Custom( 'wr_remove', $this->id ? array( $this->remove, $this->remove_unit ) : NULL, FALSE, array(
'getHtml' => function( $element )
{
return \IPS\Theme::i()->getTemplate( 'members' )->warningTime( $element->name, $element->value );
return \IPS\Theme::i()->getTemplate( 'members' )->warningTime( $element->name, $element->value, 'after', 'never' );
}
, 'unlimited' => -1, 'unlimitedLang' => 'never'
) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'wr_remove_override', $this->id ? $this->remove_override : TRUE ) );
}
@@ -102,8 +103,15 @@ class _Reason extends \IPS\Node\Model
{
if( isset( $values['wr_remove'] ) )
{
$values['remove'] = (int) $values['wr_remove'][0];
$values['remove_unit'] = $values['wr_remove'][1];
if( isset( $values['wr_remove'][3] ) or $values['wr_remove'][0] < 1 )
{
$values['remove'] = -1;
}
else
{
$values['remove'] = (int) $values['wr_remove'][0];
$values['remove_unit'] = $values['wr_remove'][1];
}
unset( $values['wr_remove'] );
}