Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

@@ -11,41 +11,59 @@
namespace IPS\calendar\extensions\core\AchievementAction;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\calendar\Calendar;
use IPS\calendar\Event;
use IPS\core\Achievements\Actions\AchievementActionAbstract;
use IPS\core\Achievements\Rule;
use IPS\Db;
use IPS\Db\Select;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Number;
use IPS\Http\Url;
use IPS\Member;
use IPS\Theme;
use OutOfRangeException;
use function count;
use function defined;
use function in_array;
use function is_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Achievement Action Extension
*/
class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction // NOTE: Other classes exist to provided bases for common situations, like where node-based filters will be required
class Rsvp extends AchievementActionAbstract // NOTE: Other classes exist to provided bases for common situations, like where node-based filters will be required
{
/**
* Get filter form elements
*
* @param array|NULL $filters Current filter values (if editing)
* @param \IPS\Http\Url $url The URL the form is being shown on
* @param Url $url The URL the form is being shown on
* @return array
*/
public function filters( ?array $filters, \IPS\Http\Url $url ): array
public function filters( ?array $filters, Url $url ): array
{
$return = array();
$return['nodes'] = new \IPS\Helpers\Form\Node( 'achievement_filter_Rsvp_nodes', ( $filters and isset( $filters['nodes'] ) and $filters['nodes'] ) ? $filters['nodes'] : 0, FALSE, [
$return['nodes'] = new Node( 'achievement_filter_Rsvp_nodes', ( $filters and isset( $filters['nodes'] ) and $filters['nodes'] ) ? $filters['nodes'] : 0, FALSE, [
'url' => $url,
'class' => 'IPS\calendar\Calendar',
'showAllNodes' => TRUE,
'multiple' => TRUE,
], NULL, \IPS\Member::loggedIn()->language()->addToStack( 'achievement_filter_NewContentItem_node_prefix', FALSE, [ 'sprintf' => [
\IPS\Member::loggedIn()->language()->addToStack( 'rsvp', FALSE ),
\IPS\Member::loggedIn()->language()->addToStack( 'calendars_sg', FALSE, [ 'strtolower' => TRUE ] )
], NULL, Member::loggedIn()->language()->addToStack( 'achievement_filter_NewContentItem_node_prefix', FALSE, [ 'sprintf' => [
Member::loggedIn()->language()->addToStack( 'rsvp', FALSE ),
Member::loggedIn()->language()->addToStack( 'calendars', FALSE, [ 'strtolower' => TRUE ] )
] ] ) );
$return['nodes']->label = \IPS\Member::loggedIn()->language()->addToStack( 'achievement_filter_NewContentItem_node', FALSE, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack( 'calendars_sg', FALSE, [ 'strtolower' => TRUE ] ) ] ] );
$return['nodes']->label = Member::loggedIn()->language()->addToStack( 'achievement_filter_NewContentItem_node', FALSE, [ 'sprintf' => [ Member::loggedIn()->language()->addToStack( 'calendars', FALSE, [ 'strtolower' => TRUE ] ) ] ] );
$return['milestone'] = new \IPS\Helpers\Form\Number( 'achievement_filter_Rsvp_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_Rsvp_nth_suffix') );
$return['milestone']->label = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewContentItem_nth');
$return['milestone'] = new Number( 'achievement_filter_Rsvp_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_Rsvp_nth_suffix') );
$return['milestone']->label = Member::loggedIn()->language()->addToStack('achievement_filter_NewContentItem_nth');
return $return;
}
@@ -77,16 +95,16 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
* 1 to the value being considered for milestones
*
* @param \IPS\Member $subject The subject member
* @param Member $subject The subject member
* @param array $filters The value returned by formatFilterValues()
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed|null $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @return bool
*/
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
public function filtersMatch( Member $subject, array $filters, mixed $extra = NULL ): bool
{
if ( isset( $filters['nodes'] ) )
{
if ( !\in_array( $extra->container()->_id, $filters['nodes'] ) )
if ( !in_array( $extra->container()->_id, $filters['nodes'] ) )
{
return FALSE;
}
@@ -123,11 +141,11 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed|null $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
public function awardOther( mixed $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra->author() ];
}
@@ -136,11 +154,11 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
* Get identifier to prevent the member being awarded points for the same action twice
* Must be unique within within of this domain, must not exceed 32 chars.
*
* @param \IPS\Member $subject The subject member
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param Member $subject The subject member
* @param mixed|null $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @return string
*/
public function identifier( \IPS\Member $subject, $extra = NULL ): string
public function identifier( Member $subject, mixed $extra = NULL ): string
{
return 'Rsvp:' . $extra->id . ':' . $subject->member_id;
}
@@ -156,38 +174,37 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
{
$exploded = explode( ':', $identifier );
$sprintf = [];
try
{
$item = \IPS\calendar\Event::load( $exploded[1] );
$item = Event::load( $exploded[1] );
$sprintf = [ 'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $item->url(), TRUE, $item->mapped('title') ?: $item->indefiniteArticle(), FALSE )
Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $item->url(), TRUE, $item->mapped('title') ?: $item->indefiniteArticle(), FALSE )
] ];
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
$sprintf = [ 'sprintf' => [ Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
}
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Rsvp_log', FALSE, $sprintf );
return Member::loggedIn()->language()->addToStack( 'AchievementAction__Rsvp_log', FALSE, $sprintf );
}
/**
* Get "description" for rule
*
* @param \IPS\core\Achievements\Rule $rule The rule
* @param Rule $rule The rule
* @return string|NULL
*/
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
public function ruleDescription( Rule $rule ): ?string
{
$conditions = [];
if ( isset( $rule->filters['milestone'] ) )
{
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
],
'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('rsvp') ]
'sprintf' => [ Member::loggedIn()->language()->addToStack('rsvp') ]
] );
}
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
@@ -195,8 +212,8 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
$conditions[] = $nodeCondition;
}
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Rsvp_title' ),
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
Member::loggedIn()->language()->addToStack( 'AchievementAction__Rsvp_title' ),
$conditions
);
}
@@ -204,10 +221,10 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
/**
* Get "description" for rule (usually a description of the rule's filters)
*
* @param \IPS\core\Achievements\Rule $rule The rule
* @param Rule $rule The rule
* @return string|NULL
*/
protected function _nodeFilterDescription( \IPS\core\Achievements\Rule $rule ): ?string
protected function _nodeFilterDescription( Rule $rule ): ?string
{
if ( isset( $rule->filters['nodes'] ) )
{
@@ -216,20 +233,20 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
{
try
{
$nodeNames[] = \IPS\calendar\Calendar::load( $id )->_title;
$nodeNames[] = Calendar::load( $id )->_title;
}
catch ( \OutOfRangeException $e ) {}
catch ( OutOfRangeException $e ) {}
}
if ( $nodeNames )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location', FALSE, [
return Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location', FALSE, [
'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'location',
\count( $nodeNames ) === 1 ? $nodeNames[0] : \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location_val', FALSE, [ 'sprintf' => [
\count( $nodeNames ),
\IPS\Member::loggedIn()->language()->addToStack( 'calendars', FALSE, [ 'strtolower' => TRUE ] )
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'location',
count( $nodeNames ) === 1 ? $nodeNames[0] : Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location_val', FALSE, [ 'sprintf' => [
count( $nodeNames ),
Member::loggedIn()->language()->addToStack( 'calendars', FALSE, [ 'strtolower' => TRUE ] )
] ] ),
\count( $nodeNames ) === 1 ? NULL : $nodeNames
count( $nodeNames ) === 1 ? NULL : $nodeNames
)
],
] );
@@ -239,74 +256,29 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
return NULL;
}
/**
* Rebuild points and badges based on the content this rule manages
*
* @param array $data The rebuild data
* @param array $filters The value returned by formatFilterValues()
* @param int $lastId The last ID returned by a previous iteration
* @param int $limit The limit of how many to rebuild
* @param \IPS\DataTime|null $time Any time limit to add
* @return array
*/
public function processRebuild( $data, $filters, $lastId, $limit, \IPS\DateTime $time = NULL ): array
{
$where = ( $time ? [ ['rsvp_id > ? and rsvp_date >= ?', $lastId, $time->getTimestamp() ] ] : [ [ 'rsvp_id > ?', $lastId ] ] );
/* Try and limit the number of rows for the good of humanity */
$query = $this->getQuery( '*', $where, $limit, 'rsvp_id ASC', $filters );
$done = 0;
foreach( $query as $row )
{
try
{
\IPS\Member::load( $row['rsvp_member_id'] )->achievementAction( 'calendar', 'Rsvp', [ \IPS\calendar\Event::load( $row['rsvp_event_id'] ) ] );
}
catch( \Exception $e ){}
$done++;
$lastId = $row['rsvp_id'];
}
return [ 'processed' => $done, 'lastId' => $lastId ];
}
/**
* Get rebuild data
*
* @param array $data Data stored with the queue item
* @param array $filters The value returned by formatFilterValues()
* @param \IPS\DataTime|null $time Any time limit to add
* @return void
*/
public function preRebuildData( &$data, $filters, \IPS\DateTime $time = NULL )
{
$data['count'] = $this->getQuery( 'COUNT(*)', ( $time ? [ ['rsvp_date >= ?', $time->getTimestamp() ] ] : [] ), NULL, NULL, $filters )->first();
}
/**
* Get a query to use for multiple methods within this extension
* @param string $select Select for the query
* @param array|NULL $where Where for the query
* @param int|NULL $limit Limit for the query
* @param string|NULL $order Order by for the query
* @param array $filters Rule filters
* @return \IPS\Db\Select
* @param string $select Select for the query
* @param array|null $where Where for the query
* @param int|null $limit Limit for the query
* @param string|null $order Order by for the query
* @param array $filters Rule filters
* @return Select
*/
public function getQuery( $select, $where, $limit, $order, $filters ): \IPS\Db\Select
public function getQuery( string $select, ?array $where, ?int $limit, ?string $order, array $filters ): Select
{
$joinContainers = FALSE;
$extraJoinCondition = NULL;
$where = \is_array( $where ) ? $where : array();
$where = is_array( $where ) ? $where : array();
/* Limit by node */
if ( isset( $filters['nodes'] ) )
{
$joinContainers = TRUE;
$extraJoinCondition = ' AND ' . \IPS\Db::i()->in( 'calendar_events.event_calendar_id', $filters['nodes'] );
$extraJoinCondition = ' AND ' . Db::i()->in( 'calendar_events.event_calendar_id', $filters['nodes'] );
}
$query = \IPS\Db::i()->select( $select, 'calendar_event_rsvp', $where, $order, $limit );
$query = Db::i()->select( $select, 'calendar_event_rsvp', $where, $order, $limit );
if ( $joinContainers )
{
@@ -315,4 +287,32 @@ class _Rsvp extends \IPS\core\Achievements\Actions\AbstractAchievementAction //
return $query;
}
/**
* Get rebuild data
*
* @return array
*/
static public function rebuildData(): array
{
return [ [
'table' => 'calendar_event_rsvp',
'pkey' => 'rsvp_id',
'where' => [ [ 'status=?', Event::RSVP_YES ] ],
] ];
}
/**
* Process the rebuild row
*
* @param array $row Row from database
* @param array $data Data collected when starting rebuild [table, pkey...]
* @return void
*/
public static function rebuildRow( array $row, array $data ) : void
{
$event = Event::load( $row['rsvp_event_id'] );
$member = Member::load( $row['rsvp_member_id'] );
$member->achievementAction( 'calendar', 'Rsvp', $event );
}
}
@@ -12,35 +12,42 @@
namespace IPS\calendar\extensions\core\Announcements;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\core\Announcements\Announcement;
use IPS\Extensions\AnnouncementsAbstract;
use IPS\Helpers\Form\FormAbstract;
use IPS\Helpers\Form\Node;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Announcements Extension
*/
class _Calendars
class Calendars extends AnnouncementsAbstract
{
/**
* @brief ID Field
*/
public static $idField = "id";
public static string $idField = "id";
/**
* @brief Controller classes
*/
public static $controllers = array( "IPS\\calendar\\modules\\front\\calendar\\view" );
public static array $controllers = array( "IPS\\calendar\\modules\\front\\calendar\\view" );
/**
* Get Setting Field
*
* @param \IPS\core\Announcements\Announcement $announcement
* @return Form element
* @param Announcement|null $announcement
* @return FormAbstract Form element
*/
public function getSettingField( $announcement )
public function getSettingField( ?Announcement $announcement ): FormAbstract
{
return new \IPS\Helpers\Form\Node( 'announce_calendars', ( $announcement AND $announcement->ids ) ? explode( ",", $announcement->ids ) : 0, FALSE, array( 'class' => 'IPS\calendar\Calendar', 'zeroVal' => 'any', 'multiple' => TRUE ), NULL, NULL, NULL, 'announce_calendars' );
return new Node( 'announce_calendars', ( $announcement AND $announcement->ids ) ? explode( ",", $announcement->ids ) : 0, FALSE, array( 'class' => 'IPS\calendar\Calendar', 'zeroVal' => 'any', 'multiple' => TRUE ), NULL, NULL, NULL, 'announce_calendars' );
}
}
@@ -12,36 +12,33 @@
namespace IPS\calendar\extensions\core\ContentRouter;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application\Module;
use IPS\Extensions\ContentRouterAbstract;
use IPS\Member;
use IPS\Member\Group;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Content Router extension: Calendar
*/
class _Calendar
class Calendar extends ContentRouterAbstract
{
/**
* @brief Content Item Classes
*/
public $classes = array();
/**
* @brief Can be shown in similar content
*/
public $similarContent = FALSE;
/**
* Constructor
*
* @param \IPS\Member|IPS\Member\Group|NULL $member If checking access, the member/group to check for, or NULL to not check access
* @param Member|Group|NULL $member If checking access, the member/group to check for, or NULL to not check access
* @return void
*/
public function __construct( $member = NULL )
public function __construct( Group|Member $member = NULL )
{
if ( $member === NULL or $member->canAccessModule( \IPS\Application\Module::get( 'calendar', 'calendar', 'front' ) ) )
if ( $member === NULL or $member->canAccessModule( Module::get( 'calendar', 'calendar', 'front' ) ) )
{
$this->classes[] = 'IPS\calendar\Event';
}
@@ -1,57 +0,0 @@
<?php
/**
* @brief Create Menu Extension
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Calendar
* @since 23 Dec 2013
*/
namespace IPS\calendar\extensions\core\CreateMenu;
/* 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;
}
/**
* Create Menu Extension
*/
class _Event
{
/**
* Get Items
*
* @return array
*/
public function getItems()
{
if ( \IPS\calendar\Calendar::canOnAny( 'add', NULL, \IPS\Settings::i()->club_nodes_in_apps ? array() : array( array( 'cal_club_id IS NULL' ) ) ) )
{
if ( !\IPS\Settings::i()->club_nodes_in_apps and $theOnlyNode = \IPS\calendar\Calendar::theOnlyNode() )
{
return array(
'event' => array(
'link' => \IPS\Http\Url::internal( "app=calendar&module=calendar&controller=submit&do=submit&id=" . $theOnlyNode->_id, 'front', 'calendar_submit' ),
)
);
}
else
{
return array(
'event' => array(
'link' => \IPS\Http\Url::internal( "app=calendar&module=calendar&controller=submit&_new=1", 'front', 'calendar_submit' ),
'extraData' => array( 'data-ipsDialog' => true, 'data-ipsDialog-size' => "narrow", ),
'title' => 'select_calendar'
)
);
}
}
return array();
}
}
Whitespace-only changes.
@@ -12,35 +12,40 @@
namespace IPS\calendar\extensions\core\EditorLocations;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\calendar\Event;
use IPS\calendar\Event\Comment;
use IPS\calendar\Event\Review;
use IPS\Content;
use IPS\Extensions\EditorLocationsAbstract;
use IPS\Helpers\Form\Editor;
use IPS\Http\Url;
use IPS\Member;
use IPS\Node\Model;
use LogicException;
use OutOfRangeException;
use RuntimeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Editor Extension: Calendar event text and comments
*/
class _Calendar
class Calendar extends EditorLocationsAbstract
{
/**
* Can we use HTML in this editor?
*
* @param \IPS\Member $member The member
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canUseHtml( $member )
{
return NULL;
}
/**
* Can we use attachments in this editor?
*
* @param \IPS\Member $member The member
* @param Member $member
* @param Editor $field
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canAttach( $member )
public function canAttach( Member $member, Editor $field ): bool|null
{
return NULL;
}
@@ -49,11 +54,11 @@ class _Calendar
* Can whatever is posted in this editor be moderated?
* If this returns TRUE, we must ensure the content is ran through word, link and image filters
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @param Member $member The member
* @param Editor $field The editor field
* @return bool
*/
public function canBeModerated( $member, $field )
public function canBeModerated( Member $member, Editor $field ): bool
{
/* Creating/editing an event */
if ( preg_match( '/^calendar-event(?:\-\d+)?$/', $field->options['autoSaveKey'] ) )
@@ -70,31 +75,32 @@ class _Calendar
{
if ( \IPS\IN_DEV )
{
throw new \RuntimeException( 'Unknown canBeModerated: ' . $field->options['autoSaveKey'] );
throw new RuntimeException( 'Unknown canBeModerated: ' . $field->options['autoSaveKey'] );
}
return FALSE;
return parent::canBeModerated( $member, $field );
}
}
/**
* Permission check for attachments
*
* @param \IPS\Member $member The member
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @param array $attachment The attachment data
* @param bool $viewOnly If true, just check if the user can see the attachment rather than download it
* @param Member $member The member
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @param array $attachment The attachment data
* @param bool $viewOnly If true, just check if the user can see the attachment rather than download it
* @return bool
*/
public function attachmentPermissionCheck( $member, $id1, $id2, $id3, $attachment, $viewOnly=FALSE )
public function attachmentPermissionCheck( Member $member, ?int $id1, ?int $id2, ?string $id3, array $attachment, bool $viewOnly=FALSE ): bool
{
try
{
$event = \IPS\calendar\Event::load( $id1 );
$event = Event::load( $id1 );
return $event->canView( $member );
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
return FALSE;
}
@@ -103,28 +109,28 @@ class _Calendar
/**
* Attachment lookup
*
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @return \IPS\calendar\Event\Review|\IPS\calendar\Event\Comment|\IPS\calendar\Event
* @throws \LogicException
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @return Content|Member|Model|Url|null
* @throws LogicException
*/
public function attachmentLookup( $id1, $id2, $id3 )
public function attachmentLookup( ?int $id1=null, ?int $id2=null, ?string $id3=null ): Model|Content|Url|Member|null
{
if ( $id2 )
{
if ( $id3 === 'review' )
{
return \IPS\calendar\Event\Review::load( $id2 );
return Review::load( $id2 );
}
else
{
return \IPS\calendar\Event\Comment::load( $id2 );
return Comment::load( $id2 );
}
}
else
{
return \IPS\calendar\Event::load( $id1 );
return Event::load( $id1 );
}
}
}
@@ -12,36 +12,35 @@
namespace IPS\calendar\extensions\core\EditorLocations;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Content;
use IPS\Extensions\EditorLocationsAbstract;
use IPS\Helpers\Form\Editor;
use IPS\Http\Url;
use IPS\Member;
use IPS\Node\Model;
use LogicException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Editor Extension: Venue
*/
class _Venue
class Venue extends EditorLocationsAbstract
{
/**
* Can we use HTML in this editor?
*
* @param \IPS\Member $member The member
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canUseHtml( $member )
{
return NULL;
}
/**
* Can we use attachments in this editor?
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @param Member $member The member
* @param Editor $field The editor field
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canAttach( $member, $field )
public function canAttach( Member $member, Editor $field ): ?bool
{
return FALSE;
}
@@ -49,7 +48,7 @@ class _Venue
/**
* Permission check for attachments
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
@@ -57,7 +56,7 @@ class _Venue
* @param bool $viewOnly If true, just check if the user can see the attachment rather than download it
* @return bool
*/
public function attachmentPermissionCheck( $member, $id1, $id2, $id3, $attachment, $viewOnly=FALSE )
public function attachmentPermissionCheck( Member $member, ?int $id1, ?int $id2, ?string $id3, array $attachment, bool $viewOnly=FALSE ): bool
{
return FALSE;
}
@@ -65,14 +64,14 @@ class _Venue
/**
* Attachment lookup
*
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @return \IPS\Http\Url|\IPS\Content|\IPS\Node\Model
* @throws \LogicException
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @return Content|Member|Model|Url|null
* @throws LogicException
*/
public function attachmentLookup( $id1, $id2, $id3 )
public function attachmentLookup( ?int $id1=null, ?int $id2=null, ?string $id3=null ): Model|Content|Url|Member|null
{
// return \IPS\Http\Url::internal( ... );
throw new LogicException;
}
}
@@ -12,86 +12,78 @@
namespace IPS\calendar\extensions\core\FileStorage;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Db;
use IPS\Extensions\FileStorageAbstract;
use IPS\File;
use UnderflowException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* File Storage Extension: Events
*/
class _Events
class Events extends FileStorageAbstract
{
/**
* Count stored files
*
* @return int
*/
public function count()
public function count(): int
{
return \IPS\Db::i()->select( 'COUNT(*)', 'calendar_events', 'event_cover_photo IS NOT NULL' )->first();
return Db::i()->select( 'COUNT(*)', 'calendar_events', 'event_cover_photo IS NOT NULL' )->first();
}
/**
* Move stored files
*
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
* @param int $storageConfiguration New storage configuration ID
* @param int|NULL $oldConfiguration Old storage configuration ID
* @throws \Underflowexception When file record doesn't exist. Indicating there are no more files to move
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
* @param int $storageConfiguration New storage configuration ID
* @param int|null $oldConfiguration Old storage configuration ID
* @return void
*@throws Underflowexception When file record doesn't exist. Indicating there are no more files to move
*/
public function move( $offset, $storageConfiguration, $oldConfiguration=NULL )
public function move( int $offset, int $storageConfiguration, int $oldConfiguration=NULL ) : void
{
$record = \IPS\Db::i()->select( '*', 'calendar_events', 'event_cover_photo IS NOT NULL', 'event_id', array( $offset, 1 ) )->first();
$record = Db::i()->select( '*', 'calendar_events', 'event_cover_photo IS NOT NULL', 'event_id', array( $offset, 1 ) )->first();
try
{
$file = \IPS\File::get( $oldConfiguration ?: 'calendar_Events', $record['event_cover_photo'] )->move( $storageConfiguration );
$file = File::get( $oldConfiguration ?: 'calendar_Events', $record['event_cover_photo'] )->move( $storageConfiguration );
if ( (string) $file != $record['event_cover_photo'] )
{
\IPS\Db::i()->update( 'calendar_events', array( 'event_cover_photo' => (string) $file ), array( 'event_id=?', $record['event_id'] ) );
Db::i()->update( 'calendar_events', array( 'event_cover_photo' => (string) $file ), array( 'event_id=?', $record['event_id'] ) );
}
}
catch( \Exception $e )
catch( Exception $e )
{
/* Any issues are logged and the \IPS\Db::i()->update not run as the exception is thrown */
}
}
/**
* Fix all URLs
*
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
* @return void
*/
public function fixUrls( $offset )
{
$record = \IPS\Db::i()->select( '*', 'calendar_events', 'event_cover_photo IS NOT NULL', 'event_id', array( $offset, 1 ) )->first();
if ( $new = \IPS\File::repairUrl( $record['event_cover_photo'] ) )
{
\IPS\Db::i()->update( 'calendar_events', array( 'event_cover_photo' => $new ), array( 'event_id=?', $record['event_id'] ) );
}
}
/**
* Check if a file is valid
*
* @param string $file The file path to check
* @param File|string $file The file path to check
* @return bool
*/
public function isValidFile( $file )
public function isValidFile( File|string $file ): bool
{
try
{
$record = \IPS\Db::i()->select( '*', 'calendar_events', array( 'event_cover_photo=?', (string) $file ) )->first();
$record = Db::i()->select( '*', 'calendar_events', array( 'event_cover_photo=?', (string) $file ) )->first();
return TRUE;
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
return FALSE;
}
@@ -102,15 +94,15 @@ class _Events
*
* @return void
*/
public function delete()
public function delete() : void
{
foreach( \IPS\Db::i()->select( '*', 'calendar_events', 'event_cover_photo IS NOT NULL' ) as $event )
foreach( Db::i()->select( '*', 'calendar_events', 'event_cover_photo IS NOT NULL' ) as $event )
{
try
{
\IPS\File::get( 'calendar_Events', $event['event_cover_photo'] )->delete();
File::get( 'calendar_Events', $event['event_cover_photo'] )->delete();
}
catch( \Exception $e ){}
catch( Exception $e ){}
}
}
}
@@ -12,64 +12,78 @@
namespace IPS\calendar\extensions\core\FrontNavigation;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application\Module;
use IPS\core\FrontNavigation;
use IPS\core\FrontNavigation\FrontNavigationAbstract;
use IPS\Dispatcher;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Front Navigation Extension: Calendar
*/
class _Calendar extends \IPS\core\FrontNavigation\FrontNavigationAbstract
class Calendar extends FrontNavigationAbstract
{
/**
* @var string Default icon
*/
public string $defaultIcon = '\f133';
/**
* Get Type Title which will display in the AdminCP Menu Manager
*
* @return string
*/
public static function typeTitle()
public static function typeTitle(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('frontnavigation_calendar');
return Member::loggedIn()->language()->addToStack('frontnavigation_calendar');
}
/**
* Can the currently logged in user access the content this item links to?
*
* @return bool
* @return bool
*/
public function canAccessContent()
public function canAccessContent(): bool
{
return \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'calendar', 'calendar' ) );
return Member::loggedIn()->canAccessModule( Module::get( 'calendar', 'calendar' ) );
}
/**
* Get Title
*
* @return string
* @return string
*/
public function title()
public function title(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('frontnavigation_calendar');
return Member::loggedIn()->language()->addToStack('frontnavigation_calendar');
}
/**
* Get Link
*
* @return \IPS\Http\Url
* @return string|Url|null
*/
public function link()
public function link(): Url|string|null
{
return \IPS\Http\Url::internal( "app=calendar&module=calendar&controller=view", 'front', 'calendar' );
return Url::internal( "app=calendar&module=calendar&controller=view", 'front', 'calendar' );
}
/**
* Is Active?
*
* @return bool
* @return bool
*/
public function active()
public function active(): bool
{
return !\IPS\core\FrontNavigation::$clubTabActive and \IPS\Dispatcher::i()->application->directory === 'calendar';
return !FrontNavigation::$clubTabActive and !FrontNavigation::nodeExtensionIsActive( 'calendar' ) and Dispatcher::i()->application->directory === 'calendar';
}
}
@@ -12,26 +12,35 @@
namespace IPS\calendar\extensions\core\LiveSearch;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\calendar\Calendar;
use IPS\Db;
use IPS\Dispatcher;
use IPS\Extensions\LiveSearchAbstract;
use IPS\Member;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief ACP Live Search Extension
*/
class _Calendars
class Calendars extends LiveSearchAbstract
{
/**
* Check we have access
*
* @return bool
*/
public function hasAccess()
public function hasAccess(): bool
{
/* Check Permissions */
return \IPS\Member::loggedIn()->hasAcpRestriction( 'calendar', 'calendars', 'calendars_manage' );
return Member::loggedIn()->hasAcpRestriction( 'calendar', 'calendars', 'calendars_manage' );
}
/**
@@ -40,7 +49,7 @@ class _Calendars
* @param string $searchTerm Search Term
* @return array Array of results
*/
public function getResults( $searchTerm )
public function getResults( string $searchTerm ): array
{
/* Init */
$results = array();
@@ -50,10 +59,10 @@ class _Calendars
if( $this->hasAccess() )
{
/* Perform the search */
$calendars = \IPS\Db::i()->select(
$calendars = Db::i()->select(
"*",
'calendar_calendars',
array( "cal_club_id IS NULL AND word_custom LIKE CONCAT( '%', ?, '%' ) AND lang_id=?", $searchTerm, \IPS\Member::loggedIn()->language()->id ),
array( "cal_club_id IS NULL AND word_custom LIKE CONCAT( '%', ?, '%' ) AND lang_id=?", $searchTerm, Member::loggedIn()->language()->id ),
NULL,
NULL
)->join(
@@ -64,9 +73,9 @@ class _Calendars
/* Format results */
foreach ( $calendars as $calendar )
{
$calendar = \IPS\calendar\Calendar::constructFromData( $calendar );
$calendar = Calendar::constructFromData( $calendar );
$results[] = \IPS\Theme::i()->getTemplate( 'livesearch', 'calendar', 'admin' )->calendar( $calendar );
$results[] = Theme::i()->getTemplate( 'livesearch', 'calendar', 'admin' )->calendar( $calendar );
}
}
@@ -78,8 +87,8 @@ class _Calendars
*
* @return bool
*/
public function isDefault()
public function isDefault(): bool
{
return \IPS\Dispatcher::i()->application->directory == 'calendar';
return Dispatcher::i()->application->directory == 'calendar';
}
}
@@ -0,0 +1,159 @@
<?php
/**
* @brief Member Filter Extension
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Events
* @since 30 Jan 2024
*/
namespace IPS\calendar\extensions\core\MemberFilter;
use IPS\calendar\Event;
use IPS\Db;
use IPS\Db\Select;
use IPS\Extensions\MemberFilterAbstract;
use IPS\Helpers\Form\Item;
use IPS\Helpers\Form\YesNo;
use IPS\Member;
use LogicException;
use function defined;
use function in_array;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Member Filter Extension
*/
class Rsvp extends MemberFilterAbstract
{
/**
* Determine if the filter is available in a given area
*
* @param string $area Area to check (bulkmail, group_promotions, automatic_moderation, passwordreset)
* @return bool
*/
public function availableIn( string $area ): bool
{
return in_array( $area, array( 'bulkmail', 'group_promotions' ) );
}
/**
* Get Setting Field
*
* @param array $criteria Value returned from the save() method
* @return array Array of form elements
*/
public function getSettingField( array $criteria ): array
{
return array(
new YesNo( 'mf_rsvp', $criteria['rsvp'] ?? null, false, array( 'togglesOn' => array( 'mf_rsvp_events' ) ) ),
new Item( 'mf_rsvp_events', $criteria['rsvp_events'] ?? -1, false, array( 'class' => Event::class, 'maxItems' => null ), null, null, null, 'mf_rsvp_events' )
);
}
/**
* Save the filter data
*
* @param array $post Form values
* @return array|bool False, or an array of data to use later when filtering the members
* @throws LogicException
*/
public function save( array $post ) : array|bool
{
if( isset( $post['mf_rsvp'] ) and $post['mf_rsvp'] )
{
$events = [];
if( is_array( $post['mf_rsvp_events'] ) )
{
foreach( $post['mf_rsvp_events'] as $event )
{
$events[] = $event->id;
}
}
return [
'rsvp' => true,
'rsvp_events' => count( $events ) ? $events : -1
];
}
return FALSE;
}
/**
* Get where clause to add to the member retrieval database query
*
* @param array $data The array returned from the save() method
* @return array|NULL Where clause - must be a single array( "clause" )
*/
public function getQueryWhereClause( array $data ): ?array
{
if( isset( $data['rsvp'] ) and $data['rsvp'] )
{
$where = [
[ 'rsvp_response<>?', Event::RSVP_NO ]
];
if( isset( $data['events'] ) and $data['events'] != -1 )
{
$where[] = [ Db::i()->in( 'rsvp_event_id', $data['events'] ) ];
}
return $where;
}
return NULL;
}
/**
* Callback for member retrieval database query
* Can be used to set joins
*
* @param array $data The array returned from the save() method
* @param Select $query The query
* @return void
*/
public function queryCallback( array $data, Select $query ) : void
{
if( isset( $data['rsvp'] ) and $data['rsvp'] )
{
$query->join( 'calendar_event_rsvp', "core_members.member_id=calendar_event_rsvp.rsvp_member_id" );
}
}
/**
* Determine if a member matches specified filters
*
* @note This is only necessary if availableIn() includes group_promotions
* @param Member $member Member object to check
* @param array $filters Previously defined filters
* @param object|NULL $object Calling class
* @return bool
*/
public function matches( Member $member, array $filters, ?object $object=NULL ): bool
{
if( !isset( $filters['rsvp'] ) or !$filters['rsvp'] )
{
return true;
}
$where = [
[ 'rsvp_member_id=?', $member->member_id ]
];
if( isset( $filters['events'] ) and $filters['events'] !== -1 )
{
$where[] = [ Db::i()->in( 'rsvp_event_id', $filters['events'] ) ];
}
return (bool) Db::i()->select( 'count(rsvp_id)', 'calendar_event_rsvp', $where )->first();
}
}
@@ -1,54 +0,0 @@
<?php
/**
* @brief Member Sync
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Calendar
* @since 7 Jan 2014
*/
namespace IPS\calendar\extensions\core\MemberSync;
/* 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;
}
/**
* Member Sync
*/
class _Calendar
{
/**
* Member is merged with another member
*
* @param \IPS\Member $member Member being kept
* @param \IPS\Member $member2 Member being removed
* @return void
*/
public function onMerge( $member, $member2 )
{
\IPS\Db::i()->update( 'calendar_event_rsvp', array( 'rsvp_member_id' => $member->member_id ), array( 'rsvp_member_id=?', $member2->member_id ), array(), NULL, \IPS\Db::IGNORE );
\IPS\Db::i()->update( 'calendar_import_feeds', array( 'feed_member_id' => $member->member_id ), array( 'feed_member_id=?', $member2->member_id ) );
\IPS\Db::i()->update( 'calendar_events', array( 'event_approved_by' => $member->member_id ), array( 'event_approved_by=?', $member2->member_id ) );
\IPS\Db::i()->update( 'calendar_event_reminders', array( 'reminder_member_id' => $member->member_id ), array( 'reminder_member_id=?', $member2->member_id ) );
}
/**
* Member is deleted
*
* @param $member \IPS\Member The member
* @return void
*/
public function onDelete( $member )
{
\IPS\Db::i()->delete( 'calendar_event_rsvp', array( 'rsvp_member_id=?', $member->member_id ) );
\IPS\Db::i()->update( 'calendar_import_feeds', array( 'feed_member_id' => 0 ), array( 'feed_member_id=?', $member->member_id ) );
\IPS\Db::i()->update( 'calendar_events', array( 'event_approved_by' => 0 ), array( 'event_approved_by=?', $member->member_id ) );
\IPS\Db::i()->delete( 'calendar_event_reminders', array( 'reminder_member_id=?', $member->member_id ) );
}
}
Whitespace-only changes.
@@ -11,24 +11,33 @@
namespace IPS\calendar\extensions\core\Notifications;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\calendar\Event;
use IPS\Extensions\NotificationsAbstract;
use IPS\Lang;
use IPS\Member;
use IPS\Notification\Inline;
use OutOfRangeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Notification Options
*/
class _Events
class Events extends NotificationsAbstract
{
/**
* Get fields for configuration
*
* @param \IPS\Member|null $member The member (to take out any notification types a given member will never see) or NULL if this is for the ACP
* @param Member|null $member The member (to take out any notification types a given member will never see) or NULL if this is for the ACP
* @return array
*/
public static function configurationOptions( \IPS\Member $member = NULL ): array
public static function configurationOptions( Member $member = NULL ): array
{
return array(
'event_reminder' => array(
@@ -46,7 +55,7 @@ class _Events
/**
* Parse notification: new_content
*
* @param \IPS\Notification\Inline $notification The notification
* @param Inline $notification The notification
* @param bool $htmlEscape TRUE to escape HTML in title
* @return array
* @code
@@ -60,22 +69,22 @@ class _Events
);
* @endcode
*/
public function parse_new_content( $notification, $htmlEscape=TRUE )
public function parse_new_content( Inline $notification, bool $htmlEscape=TRUE ): array
{
$item = $notification->item;
if ( !$item )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
$name = ( $item->isAnonymous() ) ? \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : $item->author()->name;
$name = ( $item->isAnonymous() ) ? Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : $item->author()->name;
return array(
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_content', FALSE, array(
'title' => Member::loggedIn()->language()->addToStack( 'notification__new_content', FALSE, array(
( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array(
$name,
mb_strtolower( $item->indefiniteArticle() ),
$item->container()->getTitleForLanguage( \IPS\Member::loggedIn()->language(), $htmlEscape ? array( 'escape' => TRUE ) : array() ),
$item->container()->getTitleForLanguage( Member::loggedIn()->language(), $htmlEscape ? array( 'escape' => TRUE ) : array() ),
$item->mapped('title')
)
) ),
@@ -89,31 +98,31 @@ class _Events
/**
* Parse notification: Event reminder
*
* @param \IPS\Notification\Inline $notification The notification
* @param bool $htmlEscape TRUE to escape HTML in title
* @param Inline $notification The notification
* @param bool $htmlEscape TRUE to escape HTML in title
* @return array
* @code
return array(
'title' => "Mark has replied to A Topic", // The notification title
'url' => new \IPS\Http\Url( ... ), // The URL the notification should link to
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
// explains what the notification is about - just include any appropriate content.
// For example, if the notification is about a post, set this as the body of the post.
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
);
* return array(
* 'title' => "Mark has replied to A Topic", // The notification title
* 'url' => new \IPS\Http\Url( ... ), // The URL the notification should link to
* 'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
* // explains what the notification is about - just include any appropriate content.
* // For example, if the notification is about a post, set this as the body of the post.
* 'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
* );
* @endcode
*/
public function parse_event_reminder( $notification, $htmlEscape=TRUE )
public function parse_event_reminder( Inline $notification, bool $htmlEscape=TRUE ): array
{
$item = $notification->item;
if ( !$item )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
return array(
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__event_reminder', FALSE, array(
( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array( \IPS\Member::loggedIn()->language()->addToStack( "days_to_go", FALSE, array( 'pluralize' => array( $notification->extra['daysToGo'] ) ) ), $item->mapped('title') )
'title' => Member::loggedIn()->language()->addToStack( 'notification__event_reminder', FALSE, array(
( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array( Member::loggedIn()->language()->addToStack( "days_to_go", FALSE, array( 'pluralize' => array( $notification->extra['daysToGo'] ) ) ), $item->mapped('title') )
) ),
'url' => $notification->item->url(),
'content' => $notification->item->content(),
@@ -125,11 +134,12 @@ class _Events
/**
* Parse notification for mobile: event_reminder
*
* @param \IPS\Lang $language The language that the notification should be in
* @param \IPS\Member $member The member that started following the recipient
* @param Lang $language The language that the notification should be in
* @param Event $event The event the notification refers to
* @param int $daysToGo
* @return array
*/
public static function parse_mobile_event_reminder( \IPS\Lang $language, \IPS\calendar\Event $event, $daysToGo )
public static function parse_mobile_event_reminder( Lang $language, Event $event, int $daysToGo ): array
{
return array(
'title' => $language->addToStack( 'notification__event_reminder_title', FALSE, array(
@@ -12,32 +12,37 @@
namespace IPS\calendar\extensions\core\Permissions;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\calendar\Calendar;
use IPS\Extensions\PermissionsAbstract;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Permissions
*/
class _Permissions
class Permissions extends PermissionsAbstract
{
/**
* Get node classes
*
* @return array
*/
public function getNodeClasses()
public function getNodeClasses(): array
{
return array(
'IPS\calendar\Calendar' => function( $current, $group )
{
$rows = array();
foreach( \IPS\calendar\Calendar::roots( NULL ) AS $calendar )
foreach( Calendar::roots( NULL ) AS $calendar )
{
\IPS\calendar\Calendar::populatePermissionMatrix( $rows, $calendar, $group, $current );
Calendar::populatePermissionMatrix( $rows, $calendar, $group, $current );
}
return $rows;
@@ -0,0 +1,91 @@
<?php
/**
* @brief SearchContent extension: Events
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Events
* @since 10 Jul 2023
*/
namespace IPS\calendar\extensions\core\SearchContent;
/* To prevent PHP errors (extending class does not exist) revealing path */
use Exception;
use IPS\calendar\Date;
use IPS\calendar\Event;
use IPS\calendar\Event\Comment;
use IPS\calendar\Event\Review;
use IPS\calendar\Icalendar\ICSParser;
use IPS\Content\Search\SearchContentAbstract;
use IPS\Theme;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief SearchContent extension: Events
*/
class Events extends SearchContentAbstract
{
/**
* Return all searchable classes in your application,
* including comments and/or reviews
*
* @return array
*/
public static function supportedClasses() : array
{
return array(
Event::class,
Comment::class,
Review::class
);
}
/**
* Get snippet 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 container. 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 string $view 'expanded' or 'condensed'
* @return callable
* @throws Exception
*/
public static function searchResultSnippet( array $indexData, array $authorData, array $itemData, array|null $containerData, array $reputationData, int|null $reviewRating, string $view ): string
{
$startDate = Date::parseTime( $itemData['event_start_date'], !$itemData['event_all_day']);
$endDate = $itemData['event_end_date'] ? Date::parseTime( $itemData['event_end_date'], !$itemData['event_all_day']) : NULL;
$nextOccurance = $startDate;
if ( $itemData['event_recurring'] )
{
$occurances = Event::_findOccurances( $startDate, $endDate, $startDate->adjust( "-1 month" ), $startDate->adjust( "+2 years" ), ICSParser::parseRrule( $itemData['event_recurring'] ), NULL, $itemData['event_all_day'] );
foreach( $occurances as $occurrence )
{
if ( $occurrence['startDate'] AND $occurrence['startDate']->mysqlDatetime( FALSE ) >= $startDate->mysqlDatetime( FALSE ) )
{
$nextOccurance = $occurrence['startDate'];
break;
}
}
}
if( $indexData['index_class'] == Event::class )
{
return Theme::i()->getTemplate( 'global', 'calendar', 'front' )->searchResultEventSnippet( $indexData, $itemData, $nextOccurance, $startDate, $endDate, $itemData['event_all_day'], $view == 'condensed' );
}
else
{
return Theme::i()->getTemplate( 'global', 'calendar', 'front' )->searchResultCommentSnippet( $indexData, $nextOccurance, $startDate, $endDate, $itemData['event_all_day'], $reviewRating, $view == 'condensed' );
}
}
}
@@ -11,31 +11,38 @@
namespace IPS\calendar\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\core\Statistics\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0') . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _Rsvp extends \IPS\core\Statistics\Chart
class Rsvp extends Chart
{
/**
* @brief Controller
*/
public $controller = 'calendar_stats_rsvp';
public ?string $controller = 'calendar_stats_rsvp';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @param Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): \IPS\Helpers\Chart
{
$chart = new \IPS\Helpers\Chart\Database( $url, 'calendar_event_rsvp', 'rsvp_date', '', array(
$chart = new Database( $url, 'calendar_event_rsvp', 'rsvp_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
@@ -48,10 +55,10 @@ class _Rsvp extends \IPS\core\Statistics\Chart
foreach( array( 0,1,2 ) as $response )
{
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('calendar_stats_rsvp_response_' . $response ), 'number', 'COUNT(*)', FALSE, $response );
$chart->addSeries( Member::loggedIn()->language()->addToStack('calendar_stats_rsvp_response_' . $response ), 'number', 'COUNT(*)', FALSE, $response );
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('calendar_stats_rsvp_title');
$chart->title = Member::loggedIn()->language()->addToStack('calendar_stats_rsvp_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
return $chart;
@@ -12,26 +12,37 @@
namespace IPS\calendar\extensions\core\StreamItems;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\calendar\Event;
use IPS\Content\Search\Result\Custom;
use IPS\DateTime;
use IPS\Db;
use IPS\Extensions\StreamItemsAbstract;
use IPS\Member;
use IPS\Theme;
use OutOfRangeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Activity stream items extension: StreamItems
*/
class _StreamItems
class StreamItems extends StreamItemsAbstract
{
/**
* Is there content to display?
*
* @param \IPS\Member|NULL $author The author to limit extra items to
* @param Timestamp|NULL $lastTime If provided, only items since this date are included. If NULL, it works out which to include based on what results are being shown
* @param Timestamp|NULL $firstTime If provided, only items before this date are included. If NULL, it works out which to include based on what results are being shown
* @param Member|null $author The author to limit extra items to
* @param int|null $lastTime If provided, only items since this date are included. If NULL, it works out which to include based on what results are being shown
* @param int|null $firstTime If provided, only items before this date are included. If NULL, it works out which to include based on what results are being shown
* @return array Array of \IPS\Content\Search\Result\Custom objects
*/
public function extraItems( $author=NULL, $lastTime=NULL, $firstTime=NULL )
public function extraItems( Member $author=NULL, int $lastTime=NULL, int $firstTime=NULL ): array
{
$rsvps = array();
@@ -45,19 +56,19 @@ class _StreamItems
{
$where[] = array( 'calendar_event_rsvp.rsvp_member_id=?', $author->member_id );
}
foreach ( \IPS\Db::i()->select( '*', 'calendar_event_rsvp', $where, 'rsvp_date DESC', 10 ) as $rsvp )
foreach ( Db::i()->select( '*', 'calendar_event_rsvp', $where, 'rsvp_date DESC', 10 ) as $rsvp )
{
try
{
$event = \IPS\calendar\Event::load( $rsvp[ 'rsvp_event_id' ] );
$event = Event::load( $rsvp[ 'rsvp_event_id' ] );
if( $event->canView() )
{
$member = \IPS\Member::load( $rsvp['rsvp_member_id'] );
$member = Member::load( $rsvp['rsvp_member_id'] );
$title = htmlspecialchars( $event->title, ENT_DISALLOWED, 'UTF-8', FALSE );
$rsvps[] = new \IPS\Content\Search\Result\Custom( \IPS\DateTime::ts( $rsvp[ 'rsvp_date' ] ), \IPS\Member::loggedIn()->language()->addToStack( 'calendar_activity_stream_rsvp', FALSE, array( 'htmlsprintf' => array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( $member ), $event->url(), $title ) ) ) );
$rsvps[] = new Custom( DateTime::ts( $rsvp[ 'rsvp_date' ] ), Member::loggedIn()->language()->addToStack( 'calendar_activity_stream_rsvp', FALSE, array( 'htmlsprintf' => array( Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( $member ), $event->url(), $title ) ) ) );
}
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
/* Event doesn't exist */
}