Version 5.0.6
This commit is contained in:
1 parent
a3a4a9ccbd
commit
b89858b2ff
1214 files changed
+92828
-14859
No files matched your search
@@ -14,7 +14,6 @@ namespace IPS\core\Achievements;
|
||||
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\core\CustomBadge;
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db;
|
||||
use IPS\File;
|
||||
@@ -25,6 +24,7 @@ use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Image;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Node\CustomBadge;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Patterns\ActiveRecord;
|
||||
use IPS\Settings;
|
||||
@@ -46,6 +46,8 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class Badge extends Model
|
||||
{
|
||||
use CustomBadge;
|
||||
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
@@ -170,6 +172,13 @@ class Badge extends Model
|
||||
|
||||
return static::$badgeAwardLangBits['core_award_' . $actor . '_badge_' . $ruleId] ?? NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggle off these fields when generating a custom badge
|
||||
*
|
||||
* @var array|string[]
|
||||
*/
|
||||
public static array $customBadgeToggles = [ 'badge_image' ];
|
||||
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
@@ -184,8 +193,7 @@ class Badge extends Model
|
||||
|
||||
$form->add( new Translatable( 'badge_name', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? "core_badges_{$this->id}" : NULL ) ) ) );
|
||||
|
||||
$badge = CustomBadge::getRecordBadge( $this );
|
||||
$badge->addBadgeFieldsToForm( $form, !$this->badge_use_image, ['badge_image'] );
|
||||
parent::form( $form );
|
||||
|
||||
$form->add( new Upload( 'badge_image', $this->image ? File::get( 'core_Badges', $this->image ) : NULL, TRUE, array( 'obscure' => TRUE, 'checkImage' => TRUE, 'allowedFileTypes' => array_merge( Image::supportedExtensions(), ['svg'] ), 'storageExtension' => 'core_Badges', 'allowStockPhotos' => FALSE ), function( $val ) {
|
||||
if( !$val )
|
||||
@@ -209,8 +217,6 @@ class Badge extends Model
|
||||
}, null, null, 'badge_image' ) );
|
||||
|
||||
$form->add( new YesNo( 'badge_manually_awarded', $this->manually_awarded ) );
|
||||
|
||||
parent::form( $form );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,28 +243,16 @@ class Badge extends Model
|
||||
{
|
||||
$values[ mb_substr( $k, 6 ) ] = $v;
|
||||
}
|
||||
else if ( $k === 'badge_use_custom' )
|
||||
{
|
||||
$values['badge_use_image'] = !$v;
|
||||
if( !$values['badge_use_image'] )
|
||||
{
|
||||
$values['image'] = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$values[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a custom SVG for this badge if we're not using a custom image */
|
||||
if ( ( !array_key_exists( 'badge_use_image', $values ) and !$this->badge_use_image ) or ( array_key_exists( 'badge_use_image', $values ) and !$values['badge_use_image'] ) )
|
||||
$values['badge_use_image'] = !$values['custombadge_use_custom'];
|
||||
if( !$values['badge_use_image'] )
|
||||
{
|
||||
CustomBadge::saveRecordBadge( $this, CustomBadge::generateBadgeFromFormValues( $values ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomBadge::clearCustomBadgeFields( $values );
|
||||
$values['image'] = null;
|
||||
}
|
||||
|
||||
return $values;
|
||||
@@ -272,11 +266,17 @@ class Badge extends Model
|
||||
*/
|
||||
protected function get__icon(): mixed
|
||||
{
|
||||
if ( !$this->badge_use_image )
|
||||
if ( !$this->badge_use_image and $badge = $this->getRecordBadge() )
|
||||
{
|
||||
return CustomBadge::getRecordBadge( $this )->url();
|
||||
return $badge->file()->url;
|
||||
}
|
||||
return File::get( 'core_Badges', $this->image );
|
||||
|
||||
if( $this->image )
|
||||
{
|
||||
return File::get( 'core_Badges', $this->image );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -598,14 +598,16 @@ class Badge extends Model
|
||||
* @return array
|
||||
* @apiresponse int id ID number
|
||||
* @apiresponse string name Name
|
||||
* @apiresponse array statistcs Badge Statistics
|
||||
* @apiresponse array statistics Badge Statistics
|
||||
*/
|
||||
public function apiOutput( Member $authorizedMember = NULL ): array
|
||||
{
|
||||
$stats = $this->getBadgeStats();
|
||||
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
'name' => $this->_title,
|
||||
'statistics' => $this->getBadgeStats(),
|
||||
'statistics' => [ 'badgeCount' => $stats['badgeCount'][ $this->id ] ?? 0 ]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ namespace IPS\core\Achievements;
|
||||
use ArrayIterator;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\core\CustomBadge;
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db;
|
||||
use IPS\File;
|
||||
@@ -26,6 +25,7 @@ use IPS\Helpers\Form\Upload;
|
||||
use IPS\Image;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Node\CustomBadge;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Settings;
|
||||
@@ -45,6 +45,8 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class Rank extends Model
|
||||
{
|
||||
use CustomBadge;
|
||||
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
@@ -156,9 +158,9 @@ class Rank extends Model
|
||||
*/
|
||||
protected function get__icon(): mixed
|
||||
{
|
||||
if ( !$this->rank_use_image )
|
||||
if ( !$this->rank_use_image and $badge = $this->getRecordBadge() )
|
||||
{
|
||||
return CustomBadge::getRecordBadge( $this )->url();
|
||||
return $badge->file()?->url;
|
||||
}
|
||||
|
||||
if ( $this->icon )
|
||||
@@ -223,6 +225,18 @@ class Rank extends Model
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'achievements_awards_points', FALSE, [ 'pluralize' => [ $this->points ] ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable the number overlay for custom badges
|
||||
*/
|
||||
public static bool $customBadgeNumberOverlay = false;
|
||||
|
||||
/**
|
||||
* @brief Toggle off these fields when generating a custom badge
|
||||
*
|
||||
* @var array|string[]
|
||||
*/
|
||||
public static array $customBadgeToggles = [ 'member_ranks_icon' ];
|
||||
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
@@ -238,8 +252,7 @@ class Rank extends Model
|
||||
$form->add( new Translatable( 'member_ranks_word_custom', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? "core_member_rank_{$this->id}" : NULL ) ) ) );
|
||||
$form->add( new Number( 'member_ranks_points', $this->points ?: 0, TRUE, array( 'min' => 0 ) ) );
|
||||
|
||||
$badge = CustomBadge::getRecordBadge( $this );
|
||||
$badge->addBadgeFieldsToForm( $form, !$this->rank_use_image, ['member_ranks_icon'], 'rank', false );
|
||||
parent::form( $form );
|
||||
|
||||
$form->add( new Upload( 'member_ranks_icon', $this->icon ? File::get( 'core_Ranks', $this->icon ) : NULL, TRUE, array( 'obscure' => TRUE, 'allowedFileTypes' => array_merge( Image::supportedExtensions(), ['svg'] ), 'checkImage' => TRUE, 'storageExtension' => 'core_Ranks' ), function( $val ) {
|
||||
if( !$val )
|
||||
@@ -261,8 +274,6 @@ class Rank extends Model
|
||||
}
|
||||
}
|
||||
}, NULL, NULL, 'member_ranks_icon' ) );
|
||||
|
||||
parent::form( $form );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,7 +300,7 @@ class Rank extends Model
|
||||
{
|
||||
$values[ mb_substr( $k, 13 ) ] = $v;
|
||||
}
|
||||
else if ( $k === 'rank_use_custom' )
|
||||
else if ( $k === 'custombadge_use_custom' )
|
||||
{
|
||||
$values[ 'rank_use_image' ] = !$v;
|
||||
}
|
||||
@@ -299,17 +310,10 @@ class Rank extends Model
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a custom SVG for this badge if we're not using a custom image */
|
||||
if( !array_key_exists( 'rank_use_image', $values ) or !$values['rank_use_image'] )
|
||||
{
|
||||
CustomBadge::saveRecordBadge( $this, CustomBadge::generateBadgeFromFormValues( $values, 'rank' ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomBadge::clearCustomBadgeFields( $values, 'rank' );
|
||||
}
|
||||
/* If we're using an image, disable the flag for the custom badge */
|
||||
$values['custombadge_use_custom'] = !( isset( $values['rank_use_image'] ) and $values['rank_use_image'] );
|
||||
|
||||
return $values;
|
||||
return parent::formatFormValues( $values );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -275,13 +275,13 @@ abstract class AdminNotification extends ActiveRecord
|
||||
{
|
||||
if( Application::appIsEnabled( $notification['app'] ) )
|
||||
{
|
||||
$app = Application::load( $notification['app'] );
|
||||
if( array_key_exists( $notification['ext'], $app->extensions( $app, 'AdminNotifications', false ) ) AND class_exists( 'IPS\\' . $notification['app'] . '\extensions\core\AdminNotifications\\' . IPS::mb_ucfirst( $notification['ext'] ) ) )
|
||||
try
|
||||
{
|
||||
$class = Application::getExtensionClass( $notification['app'], 'AdminNotifications', IPS::mb_ucfirst( $notification['ext'] ) );
|
||||
$notificationObject = static::constructFromData( $notification );
|
||||
$notifications[$notificationObject->id] = $notificationObject;
|
||||
}
|
||||
else
|
||||
catch( OutOfRangeException )
|
||||
{
|
||||
/* Remove orphan entry */
|
||||
Db::i()->delete( 'core_acp_notifications', ['id=?', $notification['id']] );
|
||||
@@ -347,14 +347,18 @@ abstract class AdminNotification extends ActiveRecord
|
||||
}
|
||||
else
|
||||
{
|
||||
$classname = 'IPS\\' . $app . '\extensions\core\AdminNotifications\\' . IPS::mb_ucfirst( $extension );
|
||||
$notification = new $classname;
|
||||
$notification->app = $app;
|
||||
$notification->ext = $extension;
|
||||
$notification->extra = $extra;
|
||||
$notification->additionalData = $additionalData;
|
||||
try
|
||||
{
|
||||
$classname = Application::getExtensionClass( $app, 'AdminNotifications', IPS::mb_ucfirst( $extension ) );
|
||||
$notification = new $classname;
|
||||
$notification->app = $app;
|
||||
$notification->ext = $extension;
|
||||
$notification->extra = $extra;
|
||||
$notification->additionalData = $additionalData;
|
||||
|
||||
unset( Store::i()->acpNotifications );
|
||||
unset( Store::i()->acpNotifications );
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
|
||||
/* Is this a new notification? */
|
||||
@@ -436,7 +440,7 @@ abstract class AdminNotification extends ActiveRecord
|
||||
*/
|
||||
public static function constructFromData( array $data, bool $updateMultitonStoreIfExists = TRUE ): ActiveRecord
|
||||
{
|
||||
$classname = 'IPS\\' . $data['app'] . '\extensions\core\AdminNotifications\\' . IPS::mb_ucfirst( $data['ext'] );
|
||||
$classname = Application::getExtensionClass( $data['app'], 'AdminNotifications', IPS::mb_ucfirst( $data['ext'] ) );
|
||||
|
||||
/* Initiate an object */
|
||||
$obj = new $classname;
|
||||
|
||||
@@ -29,10 +29,6 @@ use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use function count;
|
||||
use function defined;
|
||||
use const IPS\CACHE_CONFIG;
|
||||
use const IPS\CACHE_METHOD;
|
||||
use const IPS\REDIS_CONFIG;
|
||||
use const IPS\REDIS_ENABLED;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -116,10 +112,9 @@ class Advertisement extends ActiveRecord
|
||||
*
|
||||
* @param string $location Advertisement location
|
||||
* @param int|null $position Position within a list
|
||||
* @param int $adsShown Total ads already shown in this list
|
||||
* @return Advertisement|NULL
|
||||
*/
|
||||
public static function loadByLocation( string $location, ?int $position=null, int $adsShown=0 ) : ?static
|
||||
public static function loadByLocation( string $location, ?int $position=null ) : ?static
|
||||
{
|
||||
/* If we know there are no ads, we don't need to bother */
|
||||
if ( !Settings::i()->ads_exist )
|
||||
@@ -132,57 +127,63 @@ class Advertisement extends ActiveRecord
|
||||
{
|
||||
static::$advertisements = array();
|
||||
|
||||
$where[] = array( "ad_type!=?", static::AD_EMAIL );
|
||||
$where[] = array( "ad_type!=?", static::AD_EMAIL );
|
||||
$where[] = array( "ad_active=1" );
|
||||
$where[] = array( "ad_start<?", time() );
|
||||
$where[] = array( "(ad_end=0 OR ad_end>?)", time() );
|
||||
|
||||
if( Dispatcher::hasInstance() AND ( !isset( Dispatcher::i()->dispatcherController ) OR !Dispatcher::i()->dispatcherController->isContentPage ) )
|
||||
if ( Dispatcher::hasInstance() and ( !isset( Dispatcher::i()->dispatcherController ) or !Dispatcher::i()->dispatcherController->isContentPage ) )
|
||||
{
|
||||
$where[] =array( 'ad_nocontent_page_output=?', 1 );
|
||||
$where[] = array( 'ad_nocontent_page_output=?', 1 );
|
||||
}
|
||||
|
||||
foreach( Db::i()->select( '*' ,'core_advertisements', $where ) as $row )
|
||||
foreach ( Db::i()->select( '*', 'core_advertisements', $where ) as $row )
|
||||
{
|
||||
foreach ( explode( ',', $row['ad_location'] ) as $_location )
|
||||
{
|
||||
static::$advertisements[ $_location ][] = static::constructFromData( $row );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach( static::$advertisements as $adLocation => $ads )
|
||||
{
|
||||
foreach( $ads as $index => $ad )
|
||||
foreach ( static::$advertisements as $adLocation => $ads )
|
||||
{
|
||||
/* 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 */
|
||||
if ( ! empty( $ad->exempt ) and $ad->exempt != '*' )
|
||||
foreach ( $ads as $index => $ad )
|
||||
{
|
||||
$groupsToHideFrom = array_diff( array_keys( Group::groups() ), json_decode( $ad->exempt, TRUE ) );
|
||||
|
||||
if ( Member::loggedIn()->inGroup( $groupsToHideFrom ) )
|
||||
/* 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 */
|
||||
if ( !empty( $ad->exempt ) and $ad->exempt != '*' )
|
||||
{
|
||||
unset( static::$advertisements[ $adLocation ][ $index ] );
|
||||
continue;
|
||||
$groupsToHideFrom = array_diff( array_keys( Group::groups() ), json_decode( $ad->exempt, TRUE ) );
|
||||
|
||||
if ( Member::loggedIn()->inGroup( $groupsToHideFrom ) )
|
||||
{
|
||||
unset( static::$advertisements[ $adLocation ][ $index ] );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Weed out any ads that we don't see due to the application settings */
|
||||
if ( in_array( $adLocation, static::$defaultLocations ) and isset( $ad->_additional_settings['ad_apps'] ) and is_array( $ad->_additional_settings['ad_apps'] ) and $ad->_additional_settings != 0 )
|
||||
{
|
||||
if ( !in_array( Dispatcher::i()->application->directory, $ad->_additional_settings['ad_apps'] ) )
|
||||
{
|
||||
unset( static::$advertisements[ $adLocation ][ $index ] );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Weed out any ads that we don't see due to the application settings */
|
||||
if( in_array( $adLocation, static::$defaultLocations ) AND isset( $ad->_additional_settings['ad_apps'] ) AND is_array( $ad->_additional_settings['ad_apps'] ) AND $ad->_additional_settings != 0 )
|
||||
if( !in_array( $adLocation, static::$defaultLocations ) )
|
||||
{
|
||||
if( !in_array( Dispatcher::i()->application->directory, $ad->_additional_settings['ad_apps'] ) )
|
||||
foreach ( $ads as $index => $ad )
|
||||
{
|
||||
unset( static::$advertisements[ $adLocation ][ $index ] );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove advertisements that can't be shown based on settings */
|
||||
if( $extension = static::loadExtension( $location ) )
|
||||
{
|
||||
if( !$extension->canShow( $ad, $location ) )
|
||||
{
|
||||
unset( static::$advertisements[ $adLocation][ $index ] );
|
||||
/* Remove advertisements that can't be shown based on settings */
|
||||
if ( $extension = static::loadExtension( $location ) )
|
||||
{
|
||||
if ( !$extension->canShow( $ad, $location ) )
|
||||
{
|
||||
unset( static::$advertisements[ $adLocation ][ $index ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,9 +195,15 @@ class Advertisement extends ActiveRecord
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return static::selectAdvertisement( static::$advertisements[ $location ], $position, $adsShown );
|
||||
return static::selectAdvertisement( static::$advertisements[ $location ], $position );
|
||||
}
|
||||
|
||||
/**
|
||||
* Store loaded extensions
|
||||
* @var array
|
||||
*/
|
||||
static array $_loadedExtensions = [];
|
||||
|
||||
/**
|
||||
* Find the Extension that handles this ad location
|
||||
*
|
||||
@@ -205,13 +212,19 @@ class Advertisement extends ActiveRecord
|
||||
*/
|
||||
protected static function loadExtension( string $location ) : ?AdvertisementLocationsAbstract
|
||||
{
|
||||
/* Check for a loaded extension */
|
||||
if( isset( static::$_loadedExtensions[ $location ] ) )
|
||||
{
|
||||
return static::$_loadedExtensions[ $location ];
|
||||
}
|
||||
|
||||
foreach( Application::allExtensions( 'core', 'AdvertisementLocations' ) as $ext )
|
||||
{
|
||||
/* @var AdvertisementLocationsAbstract $ext */
|
||||
$extensionSettings = $ext->getSettings( array() );
|
||||
if( array_key_exists( $location, $extensionSettings['locations'] ) )
|
||||
{
|
||||
return $ext;
|
||||
return static::$_loadedExtensions[ $location ] = $ext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,15 +394,20 @@ class Advertisement extends ActiveRecord
|
||||
return static::selectAdvertisement( $adsToCheckFrom );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Track positioning of each ad in a listing view
|
||||
* @var array
|
||||
*/
|
||||
protected static array $listingView = [];
|
||||
|
||||
/**
|
||||
* Select an advertisement from an array and return it
|
||||
*
|
||||
* @param array $ads Array of advertisements to select from
|
||||
* @param int|null $position Position within a list
|
||||
* @param int $adsShown Total ads already shown in this list
|
||||
* @return static|null
|
||||
*/
|
||||
static protected function selectAdvertisement( array $ads, ?int $position=null, int $adsShown=0 ) : ?static
|
||||
static protected function selectAdvertisement( array $ads, ?int $position=null ) : ?static
|
||||
{
|
||||
/* Reset so we don't throw an error */
|
||||
$ads = array_values( $ads );
|
||||
@@ -448,13 +466,30 @@ class Advertisement extends ActiveRecord
|
||||
|
||||
if( $indexNumber !== false )
|
||||
{
|
||||
/* Check the position and see if it's a match */
|
||||
if( $position > $indexNumber AND $position % $indexNumber === 1 )
|
||||
/* Figure out the last position it was shown */
|
||||
if( empty( static::$listingView[ $ad->id ] ) )
|
||||
{
|
||||
static::$listingView[ $ad->id ] = [];
|
||||
$lastShown = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$lastIndex = count( static::$listingView[ $ad->id ] ) - 1;
|
||||
$lastShown = static::$listingView[ $ad->id ][ $lastIndex ];
|
||||
}
|
||||
|
||||
/* Total times it was shown */
|
||||
$adsShown = count( static::$listingView[ $ad->id ] );
|
||||
|
||||
/* Check the position and see if it's a match.
|
||||
We use >= instead of == because of circulation settings; we might not have
|
||||
an exact match to the interval. */
|
||||
if( $position - $lastShown >= $indexNumber )
|
||||
{
|
||||
/* Did we already show it the maximum times? */
|
||||
if( $repeat !== false )
|
||||
{
|
||||
if( $repeat === -1 OR ( isset( $adsShown ) AND $repeat > $adsShown ) )
|
||||
if( $repeat === -1 OR $repeat > $adsShown )
|
||||
{
|
||||
$advertisement = $ad;
|
||||
break;
|
||||
@@ -469,6 +504,12 @@ class Advertisement extends ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/* Store the position so that we can track it for the next round */
|
||||
if( isset( $advertisement ) and $position !== null )
|
||||
{
|
||||
static::$listingView[ $advertisement->id ][] = $position;
|
||||
}
|
||||
|
||||
return $advertisement ?? null;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace IPS\core\Assignments;
|
||||
use IPS\Content;
|
||||
use IPS\Content\Comment;
|
||||
use IPS\Content\Item;
|
||||
use IPS\core\modules\admin\stats\points;
|
||||
use IPS\Data\Store;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Member;
|
||||
@@ -60,14 +62,26 @@ class Assignment extends ActiveRecord
|
||||
protected static array $multitonMap = [];
|
||||
|
||||
/**
|
||||
* @return Item
|
||||
* @throws OutOfRangeException
|
||||
* @brief [ActiveRecord] Caches
|
||||
* @note Defined cache keys will be cleared automatically as needed
|
||||
*/
|
||||
public function get_item() : Item
|
||||
protected array $caches = array( 'openAssignments' );
|
||||
|
||||
/**
|
||||
* @return Item|null
|
||||
*/
|
||||
public function get_item() : ?Item
|
||||
{
|
||||
/* @var Item $class */
|
||||
$class = $this->item_class;
|
||||
return $class::load( $this->item_id );
|
||||
try
|
||||
{
|
||||
$class = $this->item_class;
|
||||
return $class::load( $this->item_id );
|
||||
}
|
||||
catch(OutOfRangeException $e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,6 +283,71 @@ class Assignment extends ActiveRecord
|
||||
return DateTime::ts( $this->reply_time )->roundedDiff( DateTime::ts( $this->date ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactly what you think it does.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function totalOpenAssignments() : int
|
||||
{
|
||||
try
|
||||
{
|
||||
$cache = Store::i()->openAssignments;
|
||||
}
|
||||
catch( OutOfRangeException )
|
||||
{
|
||||
$cache = [
|
||||
'members' => iterator_to_array(
|
||||
Db::i()->select( 'assign_to, count(assign_id) as total', static::$databaseTable, [ 'assign_type=? and assign_closed=?', static::ASSIGNMENT_MEMBER, 0 ], null, null, 'assign_to' )
|
||||
->setKeyField( 'assign_to' )
|
||||
->setValueField( 'total' )
|
||||
),
|
||||
'teams' => iterator_to_array(
|
||||
Db::i()->select( 'assign_to, count(assign_id) as total', static::$databaseTable, [ 'assign_type=? and assign_closed=?', static::ASSIGNMENT_TEAM, 0 ], null, null, 'assign_to' )
|
||||
->setKeyField( 'assign_to' )
|
||||
->setValueField( 'total' )
|
||||
)
|
||||
];
|
||||
Store::i()->openAssignments = $cache;
|
||||
}
|
||||
|
||||
$totalOpenForMember = $cache['members'][ Member::loggedIn()->member_id ] ?? 0;
|
||||
|
||||
/* Do we have teams? */
|
||||
if( $teams = Member::loggedIn()->teams() )
|
||||
{
|
||||
foreach( $teams as $teamId => $team )
|
||||
{
|
||||
$totalOpenForMember += $cache['teams'][ $teamId ] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $totalOpenForMember;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member can access the assignments list
|
||||
*
|
||||
* @param Member|null $member
|
||||
* @return bool
|
||||
*/
|
||||
public static function canAccessAssignments( ?Member $member=null ) : bool
|
||||
{
|
||||
if( !Bridge::i()->featureIsEnabled( 'assignments' ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$member = $member ?? Member::loggedIn();
|
||||
if( static::canAssignOnAny( $member ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the member is on any team OR they have an assignment, let it through */
|
||||
return ( $teams = $member->teams() or $member->totalAssignments() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all assignments for this member or team
|
||||
*
|
||||
|
||||
@@ -42,28 +42,22 @@ class Table extends TableHelper
|
||||
public function getRows( array $advancedSearchValues=NULL ): array
|
||||
{
|
||||
$this->sortBy = in_array( $this->sortBy, $this->sortOptions ) ? $this->sortBy : 'assign_date';
|
||||
$sortBy = $this->sortBy . ' ' . ( $this->sortDirection and strtolower( $this->sortDirection ) == 'asc' ? 'asc' : 'desc' );
|
||||
$sortBy = $this->sortBy . ' ' . ( ( $this->sortDirection and strtolower( $this->sortDirection ) == 'asc' ) ? 'asc' : 'desc' );
|
||||
|
||||
/* If we can assign content, we can see all assignments.
|
||||
Otherwise, find anything assigned to the logged in member */
|
||||
$where = [];
|
||||
if( !Assignment::canAssignOnAny() or ( isset( Request::i()->filter ) and Request::i()->filter == 'assign_mine' ) )
|
||||
if( !Assignment::canAssignOnAny() or ( isset( Request::i()->filter ) and Request::i()->filter == 'assign_mine' ) or isset( Request::i()->overview ) and Request::i()->overview )
|
||||
{
|
||||
$clause = [
|
||||
"(assign_type=? and assign_to=?)"
|
||||
];
|
||||
$binds = [
|
||||
Assignment::ASSIGNMENT_MEMBER,
|
||||
Member::loggedIn()->member_id
|
||||
];
|
||||
|
||||
if( $teams = Member::loggedIn()->teams() )
|
||||
{
|
||||
$clause[] = "(assign_type=? and " . Db::i()->in( 'assign_to', array_keys( $teams ) ) . ")";
|
||||
$binds[] = Assignment::ASSIGNMENT_TEAM;
|
||||
$where[] = [ '( (assign_type=? and assign_to=?) or (assign_type=? and assign_to in (' . implode( ",", array_keys( Member::loggedIn()->teams() ) ) . ') ) )', Assignment::ASSIGNMENT_MEMBER, Member::loggedIn()->member_id, Assignment::ASSIGNMENT_TEAM ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = [ 'assign_type=?', Assignment::ASSIGNMENT_MEMBER ];
|
||||
$where[] = [ 'assign_to=?', Member::loggedIn()->member_id ];
|
||||
}
|
||||
|
||||
$where = array_merge( array( implode( " OR ", $clause ) ), $binds );
|
||||
}
|
||||
|
||||
if( isset( Request::i()->filter ) )
|
||||
|
||||
@@ -10,21 +10,14 @@
|
||||
|
||||
namespace IPS\core;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\File;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Http\Url\Raw;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Patterns\ActiveRecord;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use IPS\Xml\DOMDocument;
|
||||
use OutOfRangeException;
|
||||
use UnderflowException;
|
||||
use function is_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -32,90 +25,71 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
exit;
|
||||
}
|
||||
|
||||
class CustomBadge {
|
||||
class CustomBadge extends ActiveRecord
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
* @brief [ActiveRecord] Database table
|
||||
* @note This MUST be over-ridden
|
||||
*/
|
||||
protected ?array $icon = null;
|
||||
public static ?string $databaseTable = 'core_custom_badges';
|
||||
|
||||
protected int $numberOverlay = 0;
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
* @note This needs to be declared in any child classes as well, only declaring here for editor code-complete/error-check functionality
|
||||
*/
|
||||
protected static array $multitons = array();
|
||||
|
||||
protected int $sides = 5;
|
||||
/**
|
||||
* Set Default Values (overriding $defaultValues)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setDefaultValues() : void
|
||||
{
|
||||
$this->shape = 'circle';
|
||||
$this->sides = 5;
|
||||
$this->rotation = 0;
|
||||
$this->number_overlay = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_icon() : array
|
||||
{
|
||||
return isset( $this->_data['icon'] ) ? json_decode( $this->_data['icon'], true ) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $val
|
||||
* @return void
|
||||
*/
|
||||
public function set_icon( mixed $val ) : void
|
||||
{
|
||||
$this->_data['icon'] = is_array( $val ) ? json_encode( $val ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved SVG file
|
||||
*
|
||||
* @return File|null
|
||||
*/
|
||||
public function file() : ?File
|
||||
{
|
||||
if( $this->file )
|
||||
{
|
||||
try
|
||||
{
|
||||
return File::get( 'core_CustomBadges', $this->file );
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var "square"|"circle"|"ngon"|"star"|"flower"
|
||||
*/
|
||||
protected string $shape = "circle";
|
||||
|
||||
protected string $foreground = '';
|
||||
|
||||
protected string $background = '';
|
||||
|
||||
protected string $border = '';
|
||||
|
||||
protected int $rotation = 0;
|
||||
|
||||
protected string $svgCache = '';
|
||||
|
||||
protected array $_data = [];
|
||||
|
||||
/**
|
||||
|
||||
* @param string $shape
|
||||
* @param string $foreground
|
||||
* @param string $background
|
||||
* @param string $border
|
||||
* @param string|array|null $icon
|
||||
* @param int $rotation
|
||||
* @param int $sides
|
||||
* @param int $numberOverlay
|
||||
* @param string $svg The svg
|
||||
*/
|
||||
public function __construct( string $shape, string $foreground, string $background, string $border, string|array|null $icon, int $rotation=0, int $sides=5, int $numberOverlay=0, string $svg = '' )
|
||||
{
|
||||
if ( is_string( $icon ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$icon = json_decode( $icon, true );
|
||||
}
|
||||
catch ( Exception ) {}
|
||||
}
|
||||
|
||||
if ( isset( $icon[0] ) AND is_array( $icon[0] ) )
|
||||
{
|
||||
$icon = $icon[0];
|
||||
}
|
||||
|
||||
if ( !is_array( $icon ) OR !isset( $icon['raw'] ) OR !isset( $icon['type'] ) )
|
||||
{
|
||||
$icon = null;
|
||||
}
|
||||
|
||||
$this->shape = $shape;
|
||||
$this->foreground = $foreground;
|
||||
$this->background = $background;
|
||||
$this->icon = $icon;
|
||||
$this->border = $border;
|
||||
$this->rotation = $rotation % 90; // since the minimum number of points/sides is 4, any rotation greater than 90 degrees can be simplified
|
||||
$this->sides = $sides;
|
||||
$this->numberOverlay = $numberOverlay;
|
||||
$this->svgCache = $svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SVG url of this item
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
if ( !$this->svgCache )
|
||||
{
|
||||
$this->svgCache = static::generateSVG( $this->shape, $this->foreground, $this->background, $this->border, $this->compileIconElement(), $this->rotation, $this->sides, $this->numberOverlay );
|
||||
}
|
||||
return $this->svgCache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -125,298 +99,80 @@ class CustomBadge {
|
||||
*/
|
||||
public function url() : Url
|
||||
{
|
||||
if ( isset( $this->_data['id'] ) )
|
||||
{
|
||||
return Url::createFromString( rtrim( Settings::i()->base_url, '/' ) . '/applications/core/interface/icons/icons.php?icon=' . ( $this->_data['id'] ) . '&v=' . md5( json_encode( $this->_data['raw'] ) ) );
|
||||
}
|
||||
|
||||
return new Raw( (string) $this, Raw::TYPE_SVG );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActiveRecord $record
|
||||
* @return CustomBadge
|
||||
*/
|
||||
public static function getRecordBadge( ActiveRecord $record ) : static
|
||||
{
|
||||
$class = trim( $record::class, '\\' );
|
||||
$idCol = $record::$databaseColumnId;
|
||||
$id = $record->$idCol;
|
||||
|
||||
try
|
||||
{
|
||||
$row = Db::i()->select( '*', 'core_custom_badges', ['class=? AND active_record_id=?', $class, $id] )->first();
|
||||
$badge = new static(
|
||||
$row['shape'],
|
||||
$row['foreground'],
|
||||
$row['background'],
|
||||
$row['border'],
|
||||
$row['icon'],
|
||||
$row['rotation'],
|
||||
$row['sides'],
|
||||
$row['number_overlay'],
|
||||
$row['raw']
|
||||
);
|
||||
|
||||
$badge->_data = $row;
|
||||
return $badge;
|
||||
}
|
||||
catch ( UnderflowException ) {}
|
||||
|
||||
return new static( 'circle', '#ffffff', '#eeb95f', '#f7d36f', null, 0, 5, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a badge by the id in the core_custom_badges database
|
||||
*
|
||||
* @param int $id The id of the badge
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @throws OutOfRangeException
|
||||
*/
|
||||
public static function load( int $id ) : static
|
||||
{
|
||||
try
|
||||
{
|
||||
$row = Db::i()->select( '*', 'core_custom_badges', [ 'id=?', $id ] )->first();
|
||||
$badge = new static (
|
||||
$row['shape'],
|
||||
$row['foreground'],
|
||||
$row['background'],
|
||||
$row['border'],
|
||||
$row['icon'],
|
||||
$row['rotation'],
|
||||
$row['sides'],
|
||||
$row['number_overlay'],
|
||||
$row['raw']
|
||||
);
|
||||
|
||||
$badge->_data = $row;
|
||||
return $badge;
|
||||
}
|
||||
catch ( UnderflowException )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActiveRecord $record
|
||||
* @param CustomBadge $badge
|
||||
* @return void
|
||||
*/
|
||||
public static function saveRecordBadge( ActiveRecord $record, CustomBadge $badge ) : void
|
||||
{
|
||||
$class = trim( $record::class, '\\' );
|
||||
$idCol = $record::$databaseColumnId;
|
||||
$id = $record->$idCol;
|
||||
$data = (string) $badge;
|
||||
|
||||
$id = Db::i()->insert(
|
||||
'core_custom_badges',
|
||||
[
|
||||
'sides' => $badge->sides,
|
||||
'foreground' => $badge->foreground,
|
||||
'background' => $badge->background,
|
||||
'border' => $badge->border,
|
||||
'shape' => $badge->shape,
|
||||
'icon' => json_encode( $badge->icon ),
|
||||
'rotation' => $badge->rotation,
|
||||
'number_overlay' => $badge->numberOverlay,
|
||||
'raw' => $data,
|
||||
'class' => $class,
|
||||
'active_record_id' => $id
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
$cacheKey = 'ips__custom_badge_' . $id;
|
||||
Store::i()->$cacheKey = $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add elements to the form which are responsible for creating custom badges, and loads the JS and CSS that forms the interactive and grid-style UI
|
||||
*
|
||||
* @param Form $form
|
||||
* @param bool $defaultEnabled
|
||||
* @param array $togglesOff
|
||||
* @param string $prefix
|
||||
* @param bool $includeNumberOverlay
|
||||
* @return void
|
||||
*/
|
||||
public function addBadgeFieldsToForm( Form $form, bool $defaultEnabled = false, array $togglesOff = [], string $prefix = 'badge', bool $includeNumberOverlay = true ) : void
|
||||
{
|
||||
if ( $prefix !== 'badge' )
|
||||
{
|
||||
foreach (['border', 'foreground', 'background', 'shape', 'icon', 'sides', 'rotation', 'number_overlay', 'use_custom'] as $key)
|
||||
{
|
||||
Member::loggedIn()->language()->words["{$prefix}_{$key}"] = Member::loggedIn()->language()->get( "badge_{$key}" );
|
||||
}
|
||||
}
|
||||
|
||||
$form->class .= " ipsBadgePicker--form";
|
||||
|
||||
$form->add( new Form\YesNo(
|
||||
"{$prefix}_use_custom",
|
||||
$defaultEnabled,
|
||||
true,
|
||||
[
|
||||
'togglesOff' => $togglesOff,
|
||||
'togglesOn' => [
|
||||
"{$form->id}_header_badge_preview",
|
||||
"{$prefix}_border",
|
||||
"{$prefix}_foreground",
|
||||
"{$prefix}_background",
|
||||
"{$prefix}_shape",
|
||||
"{$prefix}_icon",
|
||||
"{$prefix}_sides",
|
||||
"{$prefix}_rotation",
|
||||
"{$prefix}_number_overlay",
|
||||
'preview'
|
||||
]
|
||||
]
|
||||
));
|
||||
|
||||
$form->addHeader( 'badge_preview' );
|
||||
|
||||
Output::i()->addJsfiles( 'admin_badgepreview.js', 'core' );
|
||||
$form->addHtml( Theme::i()->getTemplate( 'achievements', 'core', 'admin' )->badgePreview( (string) $this, $prefix ) );
|
||||
|
||||
$form->add( new Form\Radio( "{$prefix}_shape", $this->shape, true, ['options' => [
|
||||
'square' => 'badge_square',
|
||||
'circle' => 'badge_circle',
|
||||
'ngon' => 'badge_n_gon',
|
||||
'star' => 'badge_star',
|
||||
'flower' => 'badge_flower'
|
||||
], 'toggles' => [
|
||||
'ngon' => ["{$prefix}_sides", "{$prefix}_rotation"],
|
||||
'star' => ["{$prefix}_sides", "{$prefix}_rotation"],
|
||||
'flower' => ["{$prefix}_sides", "{$prefix}_rotation"]
|
||||
], 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-shape']], null, null, null, "{$prefix}_shape") );
|
||||
|
||||
$form->add( new Form\Icon( "{$prefix}_icon", $this->icon ? [$this->icon] : null, false, ['useSvgIcon' => true, 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-icon']], null, null, null, "{$prefix}_icon" ) );
|
||||
|
||||
$form->add( new Form\Color( "{$prefix}_background", $this->background, true, ['rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-background']], null, null, null, "{$prefix}_background" ) );
|
||||
$form->add( new Form\Color( "{$prefix}_border", $this->border, true, ['rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-border']], null, null, null, "{$prefix}_border" ) );
|
||||
|
||||
$form->add( new Form\Color( "{$prefix}_foreground", $this->foreground, true, ['rgba' => true, 'swatches' => true, 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-foreground']], null, null, null, "{$prefix}_foreground" ) );
|
||||
|
||||
/* The overlay is optional */
|
||||
if ( $includeNumberOverlay )
|
||||
{
|
||||
$form->add( new Form\Number( "{$prefix}_number_overlay", $this->numberOverlay, false, [ 'step' => 1, 'min' => 1, 'max' => 999, 'unlimited' => 0, 'unlimitedLang' => 'none', 'rowClasses' => ['ipsFieldRow--badge-creator'] ], null, null, null, "{$prefix}_number_overlay" ) );
|
||||
}
|
||||
|
||||
$form->add( new Form\Number( "{$prefix}_sides", $this->sides, true, ['step' => 1, 'min' => 4, 'max' => 12, 'range' => true, 'rowClasses' => ['ipsFieldRow--badge-creator']], null, null, null, "{$prefix}_sides" ) );
|
||||
$form->add( new Form\Number( "{$prefix}_rotation", $this->rotation, true, ['step' => 1, 'min' => 0, 'max' => 90, 'range' => true, 'rowClasses' => ['ipsFieldRow--badge-creator']], null, null, null, "{$prefix}_rotation" ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SVG of a badge from the form values; to be used on the values of a form that was passed to Badge::addBadgeFieldsToForm()
|
||||
*
|
||||
* @param array $values
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function generateBadgeFromFormValues( array &$values, string $prefix = 'badge' ) : static
|
||||
{
|
||||
$badge = new static(
|
||||
$values["{$prefix}_shape"],
|
||||
$values["{$prefix}_foreground"],
|
||||
$values["{$prefix}_background"],
|
||||
$values["{$prefix}_border"],
|
||||
$values["{$prefix}_icon"][0] ?? null,
|
||||
$values["{$prefix}_rotation"],
|
||||
$values["{$prefix}_sides"],
|
||||
$values["{$prefix}_number_overlay"] ?? 0
|
||||
);
|
||||
|
||||
static::clearCustomBadgeFields( $values, $prefix );
|
||||
|
||||
return $badge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the custom badge fields from the form values
|
||||
*
|
||||
* @param array $values
|
||||
* @param string $prefix
|
||||
* @return void
|
||||
*/
|
||||
public static function clearCustomBadgeFields( array &$values, string $prefix = 'badge' ) : void
|
||||
{
|
||||
foreach ( ['shape', 'foreground', 'background', 'border', 'icon', 'rotation', 'sides', 'number_overlay', 'use_custom'] as $key )
|
||||
{
|
||||
unset( $values["{$prefix}_{$key}"] );
|
||||
}
|
||||
return Url::createFromString( rtrim( Settings::i()->base_url, '/' ) . '/applications/core/interface/icons/icons.php?icon=' . ( $this->id ) . '&v=' . md5( json_encode( $this->raw ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SVG for a badge
|
||||
*
|
||||
* @param "square"|"ngon"|"circle"|"star"|"flower" $shape The shape of the icon
|
||||
* @param string $foreground The color of the foreground
|
||||
* @param string $background The color of the background
|
||||
* @param string $border The border color
|
||||
* @param string $icon The icon to fill inside the svg
|
||||
* @param int $rotation The rotation of the icon
|
||||
* @param int $sides The number of sides
|
||||
* @param int $numberOverlay The overlay that is shown over the icon
|
||||
* @return string
|
||||
* @param bool $preview
|
||||
* @return void
|
||||
*/
|
||||
public static function generateSVG( string $shape, string $foreground, string $background, string $border, string $icon, int $rotation=0, int $sides=5, int $numberOverlay=0 ) : string
|
||||
public function generateSVG( bool $preview=false ) : void
|
||||
{
|
||||
$foreground = $foreground ?: 'currentColor';
|
||||
$numberOverlay = static::getBadgeNumberOverlay( $numberOverlay );
|
||||
$method = 'generateSVG_' . $this->shape;
|
||||
$this->raw = $this->$method();
|
||||
|
||||
return match ( $shape )
|
||||
/* If we're just doing a preview, don't save anything */
|
||||
if( $preview )
|
||||
{
|
||||
'square' => static::generateSVG_square( $foreground, $background, $border, $icon, $numberOverlay ),
|
||||
'ngon' => static::generateSVG_ngon( $foreground, $background, $border, $icon, $rotation, $sides, $numberOverlay ),
|
||||
'circle' => static::generateSVG_circle( $foreground, $background, $border, $icon, $numberOverlay ),
|
||||
'star' => static::generateSVG_star( $foreground, $background, $border, $icon, $rotation, $sides, $numberOverlay ),
|
||||
'flower' => static::generateSVG_flower( $foreground, $background, $border, $icon, $rotation, $sides, $numberOverlay ),
|
||||
default => throw new InvalidArgumentException( 'invalid_svg_shape' ),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we already have a file, delete it */
|
||||
if( $file = $this->file() )
|
||||
{
|
||||
$file->delete();
|
||||
}
|
||||
|
||||
$this->file = (string) File::create( 'core_CustomBadges', 'custombadge-' . $this->id . '.svg', $this->raw );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an icon without the background
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateSVG_no_shape() : string
|
||||
{
|
||||
$foreground = $this->foreground ?: 'currentColor';
|
||||
$icon = $this->compileIconElement();
|
||||
$numberOverlay = static::getBadgeNumberOverlay( $this->number_overlay );
|
||||
|
||||
return <<<SVG
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" height="48">
|
||||
<g color="{$foreground}" data-fgcolor-placeholder="color">
|
||||
{$icon}
|
||||
{$numberOverlay}
|
||||
</g>
|
||||
</svg>
|
||||
SVG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a square icon
|
||||
*
|
||||
* @param string $foreground
|
||||
* @param string $background
|
||||
* @param string $border
|
||||
* @param string $icon
|
||||
* @param string $numberOverlay
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateSVG_square( string $foreground, string $background, string $border, string $icon, string $numberOverlay='' ) : string
|
||||
protected function generateSVG_square() : string
|
||||
{
|
||||
return static::generateSVG_ngon( $foreground, $background, $border, $icon, 45, 4, $numberOverlay );
|
||||
$this->rotation = 45;
|
||||
$this->sides = 4;
|
||||
return $this->generateSVG_ngon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an n-gon
|
||||
*
|
||||
* @param string $foreground
|
||||
* @param string $background
|
||||
* @param string $border
|
||||
* @param string $icon
|
||||
* @param int $rotation
|
||||
* @param int $sides
|
||||
* @param string $numberOverlay
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateSVG_ngon( string $foreground, string $background, string $border, string $icon, int $rotation=0, int $sides=5, string $numberOverlay='' ) : string
|
||||
protected function generateSVG_ngon() : string
|
||||
{
|
||||
$points = static::getRadialPoints( $sides, ($sides === 4 and $rotation % 90 === 45) ? sqrt( 47.4*47.4*2 ) : 47.4 );
|
||||
$points = static::getRadialPoints( $this->sides, ( $this->sides === 4 and $this->rotation % 90 === 45) ? sqrt( 47.4*47.4*2 ) : 47.4 );
|
||||
$icon = $this->compileIconElement();
|
||||
$numberOverlay = static::getBadgeNumberOverlay( $this->number_overlay );
|
||||
$foreground = $this->foreground ?: 'currentColor';
|
||||
|
||||
$path = "";
|
||||
foreach ( $points as $point )
|
||||
{
|
||||
@@ -426,7 +182,7 @@ class CustomBadge {
|
||||
return <<<SVG
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" height="48">
|
||||
<g color="{$foreground}" data-fgcolor-placeholder="color">
|
||||
<path d="{$path} Z" stroke="{$border}" stroke-linejoin="round" stroke-width="5" fill="{$background}" transform="rotate({$rotation})" transform-origin="center" data-bgcolor-placeholder="fill" data-bordercolor-placeholder="stroke" />
|
||||
<path d="{$path} Z" stroke="{$this->border}" stroke-linejoin="round" stroke-width="5" fill="{$this->background}" transform="rotate({$this->rotation})" transform-origin="center" data-bgcolor-placeholder="fill" data-bordercolor-placeholder="stroke" />
|
||||
{$icon}
|
||||
{$numberOverlay}
|
||||
</g>
|
||||
@@ -437,20 +193,18 @@ SVG;
|
||||
/**
|
||||
* Generate a circle badge icon
|
||||
*
|
||||
* @param string $foreground
|
||||
* @param string $background
|
||||
* @param string $border
|
||||
* @param string $icon
|
||||
* @param string $numberOverlay
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateSVG_circle( string $foreground, string $background, string $border, string $icon, string $numberOverlay='' ) : string
|
||||
protected function generateSVG_circle() : string
|
||||
{
|
||||
$icon = $this->compileIconElement();
|
||||
$numberOverlay = static::getBadgeNumberOverlay( $this->number_overlay );
|
||||
$foreground = $this->foreground ?: 'currentColor';
|
||||
|
||||
return <<<SVG
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" height="48">
|
||||
<g color="{$foreground}" data-fgcolor-placeholder="color">
|
||||
<circle cx="50" cy="50" r="47.4" stroke="{$border}" stroke-width="5" fill="{$background}" data-bgcolor-placeholder="fill" data-bordercolor-placeholder="stroke" />
|
||||
<circle cx="50" cy="50" r="47.4" stroke="{$this->border}" stroke-width="5" fill="{$this->background}" data-bgcolor-placeholder="fill" data-bordercolor-placeholder="stroke" />
|
||||
{$icon}
|
||||
{$numberOverlay}
|
||||
</g>
|
||||
@@ -462,22 +216,17 @@ SVG;
|
||||
/**
|
||||
* Generate a star svg badge
|
||||
*
|
||||
* @param string $foreground
|
||||
* @param string $background
|
||||
* @param string $border
|
||||
* @param string $icon
|
||||
* @param int $rotation
|
||||
* @param int $sides
|
||||
* @param string $numberOverlay
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateSVG_star( string $foreground, string $background, string $border, string $icon, int $rotation=0, int $sides=5, string $numberOverlay='' ) : string
|
||||
protected function generateSVG_star() : string
|
||||
{
|
||||
$innerPoints = static::getRadialPoints( $sides, (0.75 + (0.095 * ( $sides - 4 ) / 8)) * ( 45 * ( cos( pi() / $sides ) ) ), 0.5 );
|
||||
$outerPoints = static::getRadialPoints( $sides, 47.4 );
|
||||
$icon = $this->compileIconElement();
|
||||
$numberOverlay = static::getBadgeNumberOverlay( $this->number_overlay );
|
||||
$foreground = $this->foreground ?: 'currentColor';
|
||||
$innerPoints = static::getRadialPoints( $this->sides, (0.75 + (0.095 * ( $this->sides - 4 ) / 8)) * ( 45 * ( cos( pi() / $this->sides ) ) ), 0.5 );
|
||||
$outerPoints = static::getRadialPoints( $this->sides, 47.4 );
|
||||
$path = "";
|
||||
for ( $i = 0; $i < $sides; $i++ )
|
||||
for ( $i = 0; $i < $this->sides; $i++ )
|
||||
{
|
||||
$path .= !$path ? 'M ' : ' L ';
|
||||
$path .= "{$outerPoints[$i][0]} {$outerPoints[$i][1]} L {$innerPoints[$i][0]} {$innerPoints[$i][1]}";
|
||||
@@ -485,7 +234,7 @@ SVG;
|
||||
return <<<SVG
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" height="48">
|
||||
<g color="{$foreground}" data-fgcolor-placeholder="color">
|
||||
<path d="{$path} Z" stroke="{$border}" stroke-width="5" stroke-linejoin="round" fill="{$background}" transform="rotate({$rotation})" transform-origin="center" data-bgcolor-placeholder="fill" data-bordercolor-placeholder="stroke" />
|
||||
<path d="{$path} Z" stroke="{$this->border}" stroke-width="5" stroke-linejoin="round" fill="{$this->background}" transform="rotate({$this->rotation})" transform-origin="center" data-bgcolor-placeholder="fill" data-bordercolor-placeholder="stroke" />
|
||||
{$icon}
|
||||
{$numberOverlay}
|
||||
</g>
|
||||
@@ -496,32 +245,27 @@ SVG;
|
||||
/**
|
||||
* Generate a flower svg badge
|
||||
*
|
||||
*
|
||||
* @param string $foreground
|
||||
* @param string $background
|
||||
* @param string $border
|
||||
* @param string $icon
|
||||
* @param int $rotation
|
||||
* @param int $sides
|
||||
* @param string $numberOverlay
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateSVG_flower( string $foreground, string $background, string $border, string $icon, int $rotation=0, int $sides=5, string $numberOverlay='' ) : string
|
||||
protected function generateSVG_flower() : string
|
||||
{
|
||||
$r = 45 * ((sin(pi() / $sides)) / (sin(pi() / $sides) + 1));
|
||||
$icon = $this->compileIconElement();
|
||||
$numberOverlay = static::getBadgeNumberOverlay( $this->number_overlay );
|
||||
$foreground = $this->foreground ?: 'currentColor';
|
||||
|
||||
$r = 45 * ((sin(pi() / $this->sides)) / (sin(pi() / $this->sides) + 1));
|
||||
$innerRadius = 45 - $r;
|
||||
$innerPoints = static::getRadialPoints( $sides, $innerRadius );
|
||||
$innerPoints = static::getRadialPoints( $this->sides, $innerRadius );
|
||||
$innerPoints[] = [$innerPoints[0][0], $innerPoints[0][1]];
|
||||
|
||||
|
||||
$ro = 51 * ((sin(pi() / $sides)) / (sin(pi() / $sides) + 1));
|
||||
$ro = 51 * ((sin(pi() / $this->sides)) / (sin(pi() / $this->sides) + 1));
|
||||
$outerRadius = 51 - $ro;
|
||||
$outerPoints = static::getRadialPoints( $sides, $outerRadius );
|
||||
$outerPoints = static::getRadialPoints( $this->sides, $outerRadius );
|
||||
$outerPoints[] = [$outerPoints[0][0], $outerPoints[0][1]];
|
||||
|
||||
$path = "";
|
||||
for ( $i = 0; $i < $sides + 1; $i++ )
|
||||
for ( $i = 0; $i < $this->sides + 1; $i++ )
|
||||
{
|
||||
if ( !$path )
|
||||
{
|
||||
@@ -534,7 +278,7 @@ SVG;
|
||||
}
|
||||
|
||||
$outerPath = "";
|
||||
for ( $i = 0; $i < $sides + 1; $i++ )
|
||||
for ( $i = 0; $i < $this->sides + 1; $i++ )
|
||||
{
|
||||
if ( !$outerPath )
|
||||
{
|
||||
@@ -549,8 +293,8 @@ SVG;
|
||||
return <<<SVG
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" height="48">
|
||||
<g color="{$foreground}" data-fgcolor-placeholder="color">
|
||||
<path d="{$outerPath} Z" stroke="{$border}" stroke-width="1" stroke-join="miter" fill="{$border}" transform="rotate({$rotation})" transform-origin="center" data-bordercolor-placeholder="fill,stroke" />
|
||||
<path d="{$path} Z" fill="{$background}" transform="rotate({$rotation})" transform-origin="center" data-bgcolor-placeholder="fill" />
|
||||
<path d="{$outerPath} Z" stroke="{$this->border}" stroke-width="1" stroke-join="miter" fill="{$this->border}" transform="rotate({$this->rotation})" transform-origin="center" data-bordercolor-placeholder="fill,stroke" />
|
||||
<path d="{$path} Z" fill="{$this->background}" transform="rotate({$this->rotation})" transform-origin="center" data-bgcolor-placeholder="fill" />
|
||||
{$icon}
|
||||
{$numberOverlay}
|
||||
</g>
|
||||
@@ -568,11 +312,18 @@ SVG;
|
||||
$iconData = $this->icon;
|
||||
$foreground = $this->foreground;
|
||||
|
||||
if ( $iconData === null )
|
||||
if ( $iconData === null or ( !isset( $iconData['type'] ) and !isset( $iconData['raw'] ) ) )
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
$sizeOffset = (($this->icon_size - 1) * 3);
|
||||
if ( $this->shape === 'no_shape' )
|
||||
{
|
||||
$sizeOffset += 13;
|
||||
}
|
||||
|
||||
|
||||
if ( $iconData['type'] === 'fa' )
|
||||
{
|
||||
$doc = new DOMDocument();
|
||||
@@ -593,20 +344,26 @@ SVG;
|
||||
break;
|
||||
}
|
||||
$escapedIcon = rawurlencode( $doc->saveXML( $root ?? null ) );
|
||||
$size = 42 + round( $sizeOffset * 2, 2 );
|
||||
$offset = (100 - $size) / 2;
|
||||
return <<<SVG
|
||||
<image href="data:image/svg+xml,{$escapedIcon}" width="42" height="42" x="29" y="29" data-embedded-fg-color="" />
|
||||
<image href="data:image/svg+xml,{$escapedIcon}" width="{$size}" height="{$size}" x="{$offset}" y="{$offset}" data-embedded-fg-color="" />
|
||||
SVG;
|
||||
}
|
||||
else
|
||||
{
|
||||
// complicated, but emojis don't center well. This was tested on MacOS using Apple Emoji font and accounts for whether the badge uses a background shape or not. Might be worth adding a note that emojis might not look correct on all devices (as the font is device-specific)
|
||||
$size = 50 + round( $sizeOffset * 2, 2 );
|
||||
$offset = 50 + round( ($this->shape === "no_shape" ? 24 : 18) + (($this->iconSize - 1) * ($this->shape === 'no_shape' ? 12.5 : 8.75) / 4), 2 );
|
||||
|
||||
return <<<SVG
|
||||
<style>
|
||||
svg text.svg__text_icon:not(#x) {
|
||||
line-height: 50px;
|
||||
line-height: {$size}px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<text text-anchor="middle" font-size="50" x="50" y="70" class="svg__text_icon" data-number-overlay="" >{$iconData['raw']}</text>
|
||||
<text text-anchor="middle" font-size="{$size}" x="50" y="{$offset}" class="svg__text_icon">{$iconData['raw']}</text>
|
||||
SVG;
|
||||
}
|
||||
}
|
||||
@@ -620,9 +377,7 @@ SVG;
|
||||
*/
|
||||
protected static function getBadgeNumberOverlay( int $number ) : string
|
||||
{
|
||||
if ( $number )
|
||||
{
|
||||
return <<<SVG
|
||||
return <<<SVG
|
||||
<style>
|
||||
svg text.svg__text_overlay:not(#x) {
|
||||
vertical-align: middle;
|
||||
@@ -633,8 +388,6 @@ SVG;
|
||||
<circle cx="75" cy="75" r="20" fill="#334155" stroke="#fff" stroke-width="3" data-number-overlay="" />
|
||||
<text class="svg__text_overlay" x="75" y="81.5" fill="#fff" text-anchor="middle" font-size="18" font-weight="bolder" font-family="sans-serif" data-number-overlay="" >{$number}</text>
|
||||
SVG;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -669,4 +422,30 @@ SVG;
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$this->generateSVG();
|
||||
parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() : void
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
if( $file = $this->file() )
|
||||
{
|
||||
$file->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,7 +365,7 @@ class DeletionLog extends ActiveRecord
|
||||
{
|
||||
return $class::load( $this->content_id );
|
||||
}
|
||||
catch( UnderflowException $e )
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
/* Orphaned item */
|
||||
$this->delete();
|
||||
|
||||
@@ -59,14 +59,14 @@ class Table extends TableHelper
|
||||
public function getRows( array $advancedSearchValues=NULL ): array
|
||||
{
|
||||
$this->sortBy = in_array( $this->sortBy, $this->sortOptions ) ? $this->sortBy : 'dellog_deleted_date';
|
||||
$sortBy = $this->sortBy . ' ' . ( $this->sortDirection and strtolower( $this->sortDirection ) == 'asc' ? 'asc' : 'desc' );
|
||||
$sortBy = $this->sortBy . ' ' . ( ( $this->sortDirection and strtolower( $this->sortDirection ) == 'asc' ) ? 'asc' : 'desc' );
|
||||
|
||||
/* Where Clause */
|
||||
$where = array();
|
||||
$where[] = array( Db::i()->findInSet( 'dellog_content_permissions', Member::loggedIn()->permissionArray() ) . " OR dellog_content_permissions=?", '*' );
|
||||
$where[] = array( "( " . Db::i()->findInSet( 'dellog_content_permissions', Member::loggedIn()->permissionArray() ) . " OR dellog_content_permissions=? )", '*' );
|
||||
|
||||
/* Return only content from enabled apps */
|
||||
$where[] = Db::i()->in( 'dellog_content_class', array_values( Content::routedClasses( TRUE, TRUE ) ) );
|
||||
$where[] = array( Db::i()->in( 'dellog_content_class', array_values( Content::routedClasses( TRUE, TRUE ) ) ) );
|
||||
|
||||
|
||||
if ( $advancedSearchValues )
|
||||
|
||||
@@ -16,6 +16,8 @@ use BadMethodCallException;
|
||||
use IPS\Application;
|
||||
use IPS\Content;
|
||||
use IPS\Content\Filter;
|
||||
use IPS\Content\Hideable;
|
||||
use IPS\Content\Item;
|
||||
use IPS\Content\Tag;
|
||||
use IPS\Content\Taggable;
|
||||
use IPS\DateTime;
|
||||
@@ -151,6 +153,13 @@ class Digest
|
||||
try
|
||||
{
|
||||
$item = $class::load( $row['tag_meta_id'] );
|
||||
|
||||
/* Make sure the item is valid for a digest */
|
||||
if( !$this->includeItem( $item ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$areaPlainOutput .= Email::template( $row['tag_meta_app'], 'digests__item', 'plaintext', array( $item, $this->mail ) );
|
||||
$areaHtmlOutput .= Email::template( $row['tag_meta_app'], 'digests__item', 'html', array( $item, $this->mail ) );
|
||||
|
||||
@@ -243,23 +252,15 @@ class Digest
|
||||
{
|
||||
try
|
||||
{
|
||||
/* @var Item $item */
|
||||
$item = $class::load( $follow['follow_rel_id'] );
|
||||
|
||||
/* Check the view permission */
|
||||
if( !$item->canView( $this->member ) )
|
||||
/* Make sure the item is valid for a digest */
|
||||
if( !$this->includeItem( $item ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Make sure the item is not archived */
|
||||
if ( isset( $item::$archiveClass ) and method_exists( $item, 'isArchived' ) )
|
||||
{
|
||||
if ( $item->isArchived() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Force custom profile fields not to be returned, as they can reference templates */
|
||||
if( isset( $item::$commentClass ) )
|
||||
{
|
||||
@@ -305,6 +306,40 @@ class Digest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item should be included in the digest.
|
||||
* We have several areas above that don't rely on the Filters,
|
||||
* so we need to check it here.
|
||||
*
|
||||
* @param Item $item
|
||||
* @return bool
|
||||
*/
|
||||
protected function includeItem( Item $item ) : bool
|
||||
{
|
||||
/* Check the view permission */
|
||||
if( !$item->canView( $this->member ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Skip content pending deletion */
|
||||
if( $item->hidden() === -2 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Make sure the item is not archived */
|
||||
if ( isset( $item::$archiveClass ) and method_exists( $item, 'isArchived' ) )
|
||||
{
|
||||
if ( $item->isArchived() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Digest
|
||||
|
||||
@@ -472,12 +472,13 @@ class Feature extends ActiveRecord
|
||||
/**
|
||||
* Promote stream of internally promoted items
|
||||
*
|
||||
* @param int|array $limit Number of items to fetch
|
||||
* @param string $sortField Sort by field
|
||||
* @param string $sortDirection Sort by direction (asc, desc)
|
||||
* @param int|array $limit Number of items to fetch
|
||||
* @param string $sortField Sort by field
|
||||
* @param string $sortDirection Sort by direction (asc, desc)
|
||||
* @param DateTime|null $oldestDate
|
||||
* @return array
|
||||
*/
|
||||
public static function internalStream( int|array $limit=10, string $sortField='promote_added', string $sortDirection='desc') : array
|
||||
public static function internalStream( int|array $limit=10, string $sortField='promote_added', string $sortDirection='desc', ?DateTime $oldestDate=null ) : array
|
||||
{
|
||||
$_key = md5( $limit . $sortField . $sortDirection );
|
||||
|
||||
@@ -487,8 +488,14 @@ class Feature extends ActiveRecord
|
||||
}
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach( Db::i()->select( '*', static::$databaseTable, array( 'promote_hide=0' ), $sortField . ' ' . $sortDirection, $limit ) as $row )
|
||||
$where = [ [ 'promote_hide=0' ] ];
|
||||
|
||||
if ( $oldestDate )
|
||||
{
|
||||
$where[] = [ 'promote_added > ?', $oldestDate->getTimestamp() ];
|
||||
}
|
||||
|
||||
foreach( Db::i()->select( '*', static::$databaseTable, $where, $sortField . ' ' . $sortDirection, $limit ) as $row )
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -154,11 +154,12 @@ class FrontNavigation
|
||||
/**
|
||||
* Delete front navigation items by application
|
||||
*
|
||||
* @param Application $app Application deleted
|
||||
* @param Application|string $app Application deleted
|
||||
* @return void
|
||||
*/
|
||||
public static function deleteByApplication( Application $app ) : void
|
||||
public static function deleteByApplication( Application|string $app ) : void
|
||||
{
|
||||
$app = ( $app instanceof Application ) ? $app->directory : $app;
|
||||
foreach( Db::i()->select( '*', 'core_menu', array( array( 'extension=?', 'CustomItem' ) ) ) as $row )
|
||||
{
|
||||
$config = json_decode( $row['config'], TRUE );
|
||||
@@ -169,7 +170,7 @@ class FrontNavigation
|
||||
{
|
||||
parse_str( $config['menu_custom_item_url'], $data );
|
||||
|
||||
if ( ! empty( $data['app'] ) and $data['app'] === $app->directory )
|
||||
if ( ! empty( $data['app'] ) and $data['app'] === $app )
|
||||
{
|
||||
Db::i()->delete( 'core_menu', array( 'id=?', $row['id'] ) );
|
||||
}
|
||||
@@ -178,7 +179,7 @@ class FrontNavigation
|
||||
}
|
||||
}
|
||||
|
||||
Db::i()->delete( 'core_menu', array( 'app=?', $app->directory ) );
|
||||
Db::i()->delete( 'core_menu', array( 'app=?', $app ) );
|
||||
|
||||
unset( Store::i()->frontNavigation );
|
||||
}
|
||||
@@ -349,9 +350,9 @@ class FrontNavigation
|
||||
$return = array();
|
||||
foreach ( $frontNavigation[0] as $item )
|
||||
{
|
||||
$class = 'IPS\\' . $item['app'] . '\extensions\core\FrontNavigation\\' . $item['extension'];
|
||||
if ( class_exists( $class ) )
|
||||
try
|
||||
{
|
||||
$class = Application::getExtensionClass( $item['app'], 'FrontNavigation', $item['extension'] );
|
||||
$object = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'], $item['menu_types'], json_decode( (string) $item['icon'], TRUE ), $item['parent'] );
|
||||
if ( !$this->activePrimaryNavBar )
|
||||
{
|
||||
@@ -359,6 +360,7 @@ class FrontNavigation
|
||||
}
|
||||
$this->roots[ $item['id'] ] = $object;
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,11 +393,12 @@ class FrontNavigation
|
||||
{
|
||||
foreach ( $frontNavigation[$i] as $item )
|
||||
{
|
||||
$class = 'IPS\\' . $item['app'] . '\extensions\core\FrontNavigation\\' . $item['extension'];
|
||||
if ( class_exists( $class ) )
|
||||
try
|
||||
{
|
||||
$class = Application::getExtensionClass( $item['app'], 'FrontNavigation', $item['extension'] );
|
||||
$this->subBars[ $item['parent'] ][ $item['id'] ] = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'], $item['menu_types'], json_decode( (string) $item['icon'], TRUE ), $item['parent'] );
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ namespace IPS\core\FrontNavigation;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\core\FrontNavigation;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Request;
|
||||
use IPS\Theme;
|
||||
use OutOfRangeException;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
@@ -60,11 +62,11 @@ abstract class FrontNavigationAbstract
|
||||
{
|
||||
foreach ( $frontNavigation[ $this->id ] as $item )
|
||||
{
|
||||
$class = 'IPS\\' . $item['app'] . '\extensions\core\FrontNavigation\\' . $item['extension'];
|
||||
if ( class_exists( $class ) )
|
||||
try
|
||||
{
|
||||
$this->subItems[ $item['id'] ] = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'], $item['menu_types'], json_decode( (string) $item['icon'], TRUE ) );
|
||||
}
|
||||
$class = Application::getExtensionClass( $item['app'], 'FrontNavigation', $item['extension'] );
|
||||
$this->subItems[$item['id']] = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'], $item['menu_types'], json_decode( (string)$item['icon'], TRUE ) );
|
||||
} catch ( OutOfRangeException ){}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,21 @@ class Conversation extends Item
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide the "Compose New" button in the messenger
|
||||
* This differs from @see canCreate() because some groups may have a limit per day,
|
||||
* in which case we show the button, because it will tell them they have
|
||||
* reached their limit.
|
||||
* Other groups may have no permission at all, so we hide the button entirely.
|
||||
*
|
||||
* @param Member $member
|
||||
* @return bool
|
||||
*/
|
||||
public static function showComposeButton( Member $member ) : bool
|
||||
{
|
||||
return static::canCreate( $member ) and $member->group['g_pm_perday'] != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a given member create this type of content?
|
||||
*
|
||||
@@ -205,6 +220,17 @@ class Conversation extends Item
|
||||
/* Have we exceeded our limit for the day/minute? */
|
||||
if ( $member->group['g_pm_perday'] !== -1 )
|
||||
{
|
||||
/* Members that have a zero limit can never send */
|
||||
if( $member->group['g_pm_perday'] == 0 )
|
||||
{
|
||||
if ( $showError )
|
||||
{
|
||||
Output::i()->error( $member->language()->addToStack( 'module_no_permission' ), '1C149/25', 429, '' );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$messagesSentToday = Db::i()->select( 'COUNT(*) AS count, MAX(mt_date) AS max', 'core_message_topics', array( 'mt_starter_id=? AND mt_date>?', $member->member_id, DateTime::create()->sub( new DateInterval( 'P1D' ) )->getTimeStamp() ) )->first();
|
||||
if ( $messagesSentToday['count'] >= $member->group['g_pm_perday'] )
|
||||
{
|
||||
@@ -584,7 +610,7 @@ class Conversation extends Item
|
||||
*/
|
||||
public function get_activeParticipants() : int
|
||||
{
|
||||
return count( array_filter( $this->maps, function( $map )
|
||||
return count( array_filter( $this->maps(), function( $map )
|
||||
{
|
||||
return $map['map_user_active'];
|
||||
} ) );
|
||||
|
||||
@@ -434,6 +434,8 @@ class Field extends CustomField
|
||||
$form->add( new YesNo( 'pf_member_edit', $this->id ? $this->member_edit : TRUE, FALSE, array(), NULL, NULL, NULL, 'pf_member_edit' ) );
|
||||
$form->add( new Radio( 'pf_member_hide', $this->id ? $this->member_hide : 'all', TRUE, array( 'options' => array( 'hide' => 'custom_fields_hide', 'staff' => 'custom_fields_staff', 'owner' => 'custom_fields_staff_owner', 'all' => 'custom_fields_all' ), 'toggles' => array( 'staff' => array( 'pf_profile_format' ), 'all' => array( 'pf_profile_format' ), 'owner' => array( 'pf_profile_format' ) ) ), NULL, NULL, NULL, 'pf_member_hide' ) );
|
||||
$form->add( new Radio( 'pf_topic_hide', $this->id ? $this->topic_hide : 'hide', TRUE, array( 'options' => array( 'hide' => 'custom_fields_hide', 'staff' => 'custom_fields_staff', 'all' => 'custom_fields_all' ), 'toggles' => array( 'staff' => array( 'pf_format' ), 'all' => array( 'pf_format' ) ) ), NULL, NULL, NULL, 'pf_topic_hide' ) );
|
||||
$form->add( new YesNo( 'pf_contains_pii', $this->id ? $this->contains_pii : FALSE, FALSE ) );
|
||||
|
||||
|
||||
$form->addHeader( 'pfield_displayoptions' );
|
||||
$form->add( new Select( 'pf_search_type', $this->id ? $this->search_type : 'loose', FALSE, array( 'options' => array( 'exact' => 'pf_search_type_exact', 'loose' => 'pf_search_type_loose', '' => 'pf_search_type_none' ) ), NULL, NULL, NULL, 'pf_search_type' ) );
|
||||
|
||||
@@ -223,7 +223,7 @@ class Import extends Model
|
||||
*/
|
||||
public function get__extension() : object
|
||||
{
|
||||
$classname = 'IPS\\' . $this->_application->directory . '\extensions\core\RssImport\RssImport';
|
||||
$classname = Application::getExtensionClass( $this->_application->directory, 'RssImport', 'RssImport' );
|
||||
return new $classname;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user