diff --git a/Credits.txt b/Credits.txt index a17b8690..4e2d146b 100644 --- a/Credits.txt +++ b/Credits.txt @@ -29,7 +29,7 @@ Clipboard CKEditor Description: Provides the editor functionality for submitting content. Location: In development: applications/core/dev/ckeditor (custom build with additional plugins) - Included version: 4.19.1 + Included version: 4.20.1 Website: http://ckeditor.com License: http://www.gnu.org/copyleft/lesser.html @@ -110,7 +110,7 @@ Howler HTMLPurifier Description: Validates and cleans submitted HTML for content Location: system/3rd_party/HTMLPurifier - Included version: 4.14.0 + Included version: 4.15.0 Website: http://htmlpurifier.org License: http://www.gnu.org/copyleft/lesser.html @@ -211,13 +211,6 @@ JShrink Website: https://github.com/tedious/JShrink License: https://github.com/tedious/JShrink/blob/master/LICENSE -jsTimezoneDetect - Description: Provides timezone detection - Location: dev/js/library/jstz - Included version: 1.0.6 - Website: https://bitbucket.org/pellepim/jstimezonedetect - License: http://www.opensource.org/licenses/mit-license.php - JwtFramework Description: Provides JWT signing & encryption support Location: system/3rd_party/JwtFramework @@ -336,7 +329,7 @@ success.mp3 Notification Sound by RCP Tones Twemoji Description: Emoji images Location: Served from CDN - Included version: 2.3 + Included version: 14.0.2 Website: http://twitter.github.io/twemoji/ License: https://github.com/twitter/twemoji/blob/gh-pages/LICENSE diff --git a/admin/install/eula.txt b/admin/install/eula.txt index 1600a87c..57e13bc0 100644 --- a/admin/install/eula.txt +++ b/admin/install/eula.txt @@ -1,5 +1,5 @@ =============================[NOTE]============================= - IPS 4.7.6 (107601) + IPS 4.7.8 (107643) =============================[NOTE]============================= Invision Community End User License Agreement diff --git a/applications/blog/api/GraphQL/Mutation.php b/applications/blog/api/GraphQL/Mutation.php new file mode 100644 index 00000000..6ec65b95 --- /dev/null +++ b/applications/blog/api/GraphQL/Mutation.php @@ -0,0 +1,41 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 22 Oct 2022 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\Types; + +/* 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; +} + +/** + * Blog mutationss GraphQL API + */ +abstract class _Mutation +{ + /** + * Get the supported query types in this app + * + * @return array + */ + public static function mutations(): array + { + $return = [ + 'createEntry' => new \IPS\blog\api\GraphQL\Mutations\CreateEntry(), + 'replyEntry' => new \IPS\blog\api\GraphQL\Mutations\ReplyEntry(), + ]; + return $return; + } +} diff --git a/applications/blog/api/GraphQL/Mutations/CreateEntry.php b/applications/blog/api/GraphQL/Mutations/CreateEntry.php new file mode 100644 index 00000000..124e6c0c --- /dev/null +++ b/applications/blog/api/GraphQL/Mutations/CreateEntry.php @@ -0,0 +1,95 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 22 oct 2022 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Mutations; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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 blog entry mutation for GraphQL API + */ +class _CreateEntry extends \IPS\Content\Api\GraphQL\ItemMutator +{ + /** + * Class + */ + protected $class = \IPS\blog\Entry::class; + + /* + * @brief Query description + */ + public static $description = "Create a new blog entry"; + + /* + * Mutation arguments + */ + public function args(): array + { + return [ + 'blog' => TypeRegistry::nonNull( TypeRegistry::id() ) + ]; + } + + /** + * Return the mutation return type + */ + public function type() + { + return \IPS\blog\api\GraphQL\TypeRegistry::entry(); + } + + /** + * Resolves this mutation + * + * @param mixed Value passed into this resolver + * @param array Arguments + * @param array Context values + * @return \IPS\blog\Blog + */ + public function resolve($val, $args, $context, $info) + { + /* Get blog */ + try + { + $blog = \IPS\blog\Blog::loadAndCheckPerms( $args['blogID'] ); + } + catch ( \OutOfRangeException $e ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_BLOG', '1B300/1_graphql', 400 ); + } + + /* Check permission */ + if ( !$blog->can( 'add', \IPS\Member::loggedIn() ) ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_PERMISSION', '1B300/A_graphql', 403 ); + } + + /* Check we have a title and a post */ + if ( !$args['title'] ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_TITLE', '1B300/4_graphql', 400 ); + } + if ( !$args['content'] ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_POST', '1B300/5_grapqhl', 400 ); + } + + + return $this->_create( $args, $blog, $args['postKey'] ?? NULL ); + } +} diff --git a/applications/blog/api/GraphQL/Mutations/ReplyEntry.php b/applications/blog/api/GraphQL/Mutations/ReplyEntry.php new file mode 100644 index 00000000..0c18fce1 --- /dev/null +++ b/applications/blog/api/GraphQL/Mutations/ReplyEntry.php @@ -0,0 +1,108 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 10 May 2017 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Mutations; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * Reply to entry mutation for GraphQL API + */ +class _ReplyEntry extends \IPS\Content\Api\GraphQL\CommentMutator +{ + /** + * Class + */ + protected $class = \IPS\blog\Entry\Comment::class; + + /* + * @brief Query description + */ + public static $description = "Create a new comment"; + + /* + * Mutation arguments + */ + public function args(): array + { + return [ + 'entryID' => TypeRegistry::nonNull( TypeRegistry::id() ), + 'content' => TypeRegistry::nonNull( TypeRegistry::string() ), + 'replyingTo' => TypeRegistry::id(), + 'postKey' => TypeRegistry::string() + ]; + } + + /** + * Return the mutation return type + */ + public function type() + { + return \IPS\blog\api\GraphQL\TypeRegistry::comment(); + } + + /** + * Resolves this mutation + * + * @param mixed Value passed into this resolver + * @param array Arguments + * @param array Context values + * @return \IPS\blog\Entry + */ + public function resolve($val, $args, $context, $info) + { + /* Get topic */ + try + { + $entry = \IPS\blog\Entry::loadAndCheckPerms( $args['entryID'] ); + } + catch ( \OutOfRangeException $e ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_TOPIC', '1F295/1_graphql', 403 ); + } + + /* Get author */ + if ( !$entry->canComment( \IPS\Member::loggedIn() ) ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_PERMISSION', '2F294/A_graphql', 403 ); + } + + /* Check we have a post */ + if ( !$args['content'] ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_POST', '1F295/3_graphql', 403 ); + } + + $originalPost = NULL; + + if( isset( $args['replyingTo'] ) ) + { + try + { + $originalPost = \IPS\blog\Entry\Comment::loadAndCheckPerms( $args['replyingTo'] ); + } + catch ( \OutOfRangeException $e ) + { + // Just ignore it + } + } + + /* Do it */ + return $this->_createComment( $args, $entry, $args['postKey'] ?? NULL, $originalPost ); + } +} diff --git a/applications/blog/api/GraphQL/Queries/Blog.php b/applications/blog/api/GraphQL/Queries/Blog.php new file mode 100644 index 00000000..2796f952 --- /dev/null +++ b/applications/blog/api/GraphQL/Queries/Blog.php @@ -0,0 +1,69 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 10 May 2017 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Queries; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * Blog query for GraphQL API + */ +class _Blog +{ + /* + * @brief Query description + */ + public static $description = "Returns a Blog"; + + /* + * Query arguments + */ + public function args(): array + { + return array( + 'id' => TypeRegistry::nonNull( TypeRegistry::id() ), + ); + } + + /** + * Return the query return type + */ + public function type() + { + return \IPS\blog\api\GraphQL\TypeRegistry::blog(); + } + + /** + * Resolves this query + * + * @param mixed Value passed into this resolver + * @param array Arguments + * @param array Context values + * @return \IPS\blog\Blog + */ + public function resolve($val, $args, $context, $info) + { + $blog = \IPS\blog\Blog::load( $args['id'] ); + + if( !$blog->can( 'view', $context['member'] ) ) + { + throw new \OutOfRangeException; + } + return $blog; + } +} diff --git a/applications/blog/api/GraphQL/Queries/Blogs.php b/applications/blog/api/GraphQL/Queries/Blogs.php new file mode 100644 index 00000000..beb39345 --- /dev/null +++ b/applications/blog/api/GraphQL/Queries/Blogs.php @@ -0,0 +1,64 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 22 Oct 2022 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Queries; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * Blogs query for GraphQL API + */ +class _Blogs +{ + /* + * @brief Query description + */ + public static $description = "Returns a list of blogs"; + + /* + * Query arguments + */ + public function args(): array + { + return array( + 'id' => TypeRegistry::id() + ); + } + + /** + * Return the query return type + */ + public function type() + { + return TypeRegistry::listOf( \IPS\blog\api\GraphQL\TypeRegistry::blog() ); + } + + /** + * Resolves this query + * + * @param mixed Value passed into this resolver + * @param array Arguments + * @param array Context values + * @return array + */ + public function resolve($val, $args, $context, $info) + { + \IPS\blog\Blog::loadIntoMemory('view', $context['member']); + return \IPS\blog\Blog::roots(); + } +} diff --git a/applications/blog/api/GraphQL/Queries/Entries.php b/applications/blog/api/GraphQL/Queries/Entries.php new file mode 100644 index 00000000..c25e697f --- /dev/null +++ b/applications/blog/api/GraphQL/Queries/Entries.php @@ -0,0 +1,143 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 10 May 2017 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Queries; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * Topics query for GraphQL API + */ +class _Entries +{ + /* + * @brief Query description + */ + public static $description = "Returns a list of blog entries"; + + /* + * Query arguments + */ + public function args(): array + { + return array( + 'blogs' => TypeRegistry::listOf( TypeRegistry::int() ), + 'offset' => [ + 'type' => TypeRegistry::int(), + 'defaultValue' => 0 + ], + 'limit' => [ + 'type' => TypeRegistry::int(), + 'defaultValue' => 25 + ], + 'orderBy' => [ + 'type' => TypeRegistry::eNum([ + 'name' => 'blog_order_by', + 'description' => 'Fields on which event can be sorted', + 'values' => \IPS\blog\api\GraphQL\Types\EntryType::getOrderByOptions() + ]), + 'defaultValue' => NULL // will use default sort option + ], + 'orderDir' => [ + 'type' => TypeRegistry::eNum([ + 'name' => 'entries_order_dir', + 'description' => 'Directions in which items can be sorted', + 'values' => [ 'ASC', 'DESC' ] + ]), + 'defaultValue' => 'DESC' + ], + 'honorPinned' => [ + 'type' => TypeRegistry::boolean(), + 'defaultValue' => true + ] + ); + } + + /** + * Return the query return type + */ + public function type() + { + return TypeRegistry::listOf( \IPS\blog\api\GraphQL\TypeRegistry::entry() ); + } + + /** + * Resolves this query + * + * @param mixed Value passed into this resolver + * @param array Arguments + * @param array Context values + * @return \IPS\blog\Entry + */ + public function resolve($val, $args, $context, $info) + { + \IPS\blog\Blog::loadIntoMemory('view', \IPS\Member::loggedIn() ); + + $blogIds = []; + + /* Are we filtering by blogs? */ + if( isset( $args['blogs'] ) && \count( $args['blogs'] ) ) + { + foreach( $args['blogs'] as $id ) + { + $blog = \IPS\blog\Blog::loadAndCheckPerms( $id ); + $blogIds[] = $blog->id; + } + + if( \count( $blogIds ) ) + { + $where['container'][] = array( \IPS\Db::i()->in( 'blog_blogs.blog_id', array_filter( $blogIds ) ) ); + } + } + + /* Get sorting */ + try + { + if( $args['orderBy'] === NULL ) + { + $orderBy = 'last_post'; + } + else + { + $orderBy = \IPS\blog\Entry::$databaseColumnMap[ $args['orderBy'] ]; + } + + if( $args['orderBy'] === 'last_comment' ) + { + $orderBy = \is_array( $orderBy ) ? array_pop( $orderBy ) : $orderBy; + } + } + catch (\Exception $e) + { + $orderBy = 'last_post'; + } + + $sortBy = \IPS\blog\Entry::$databaseTable . '.' . \IPS\blog\Entry::$databasePrefix . "{$orderBy} {$args['orderDir']}"; + $offset = max( $args['offset'], 0 ); + $limit = min( $args['limit'], 50 ); + + /* Figure out pinned status */ + if ( $args['honorPinned'] ) + { + $column = \IPS\blog\Entry::$databaseTable . '.' . \IPS\blog\Entry::$databasePrefix . \IPS\blog\Entry::$databaseColumnMap['pinned']; + $sortBy = "{$column} DESC, {$sortBy}"; + } + + return \IPS\blog\Entry::getItemsWithPermission( $where, $sortBy, array( $offset, $limit ), 'read' ); + } +} diff --git a/applications/blog/api/GraphQL/Queries/Entry.php b/applications/blog/api/GraphQL/Queries/Entry.php new file mode 100644 index 00000000..e8b3c14e --- /dev/null +++ b/applications/blog/api/GraphQL/Queries/Entry.php @@ -0,0 +1,77 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 22 Oct 2022 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Queries; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * Entry query for GraphQL API + */ +class _Entry +{ + /* + * @brief Query description + */ + public static $description = "Returns a Blog Entry"; + + /* + * Query arguments + */ + public function args(): array + { + return array( + 'id' => TypeRegistry::nonNull( TypeRegistry::id() ) + ); + } + + /** + * Return the query return type + */ + public function type() + { + return \IPS\blog\api\GraphQL\TypeRegistry::entry(); + } + + /** + * Resolves this query + * + * @param mixed Value passed into this resolver + * @param array Arguments + * @param array Context values + * @return \IPS\blog\Entry + */ + public function resolve($val, $args, $context, $info) + { + try + { + $entry = \IPS\blog\Entry::loadAndCheckPerms( $args['id'] ); + } + catch ( \OutOfRangeException $e ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_TOPIC', '2B300/A_graphql', 400 ); + } + + if( !$entry->can('read') ) + { + throw new \IPS\Api\GraphQL\SafeException( 'NO_PERMISSION', '2B300/9_graphql', 403 ); + } + + return $entry; + } +} diff --git a/applications/blog/api/GraphQL/Query.php b/applications/blog/api/GraphQL/Query.php new file mode 100644 index 00000000..6be90797 --- /dev/null +++ b/applications/blog/api/GraphQL/Query.php @@ -0,0 +1,42 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 22 Oct 2022 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\Types; + +/* 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; +} + +/** + * Blog queries for GraphQL API + */ +abstract class _Query +{ + /** + * Get the supported query types in this app + * + * @return array + */ + public static function queries(): array + { + return [ + 'blogs' => new \IPS\blog\api\GraphQL\Queries\Blogs(), + 'blog' => new \IPS\blog\api\GraphQL\Queries\Blog(), + 'entries' => new \IPS\blog\api\GraphQL\Queries\Entries(), + 'entry' => new \IPS\blog\api\GraphQL\Queries\Entry(), + ]; + } +} diff --git a/applications/blog/api/GraphQL/TypeRegistry.php b/applications/blog/api/GraphQL/TypeRegistry.php new file mode 100644 index 00000000..d05de774 --- /dev/null +++ b/applications/blog/api/GraphQL/TypeRegistry.php @@ -0,0 +1,76 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 22 Oct 2022 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\Types; +use IPS\blog\api\GraphQL\Types\_BlogType; + +/* 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; +} + + +class _TypeRegistry +{ + /** + * The blog type instance + * @var \IPS\blog\api\GraphQL\Types\BlogType + */ + protected static $blog; + + /** + * The blog entry type instance + * @var \IPS\blog\api\GraphQL\Types\EntryType + */ + protected static $entry; + + /** + * The entry comment instance + * @var \IPS\blog\api\GraphQL\Types\CommentType + */ + protected static $comment; + + /** + * Constructor + */ + public function __construct() + { + // Defined to suppress static warnings + } + + /** + * @return BlogType + */ + public static function blog(): \IPS\blog\api\GraphQL\Types\BlogType + { + return self::$blog ?: (self::$blog = new \IPS\blog\api\GraphQL\Types\BlogType()); + } + + /** + * @return EntryType + */ + public static function entry(): \IPS\blog\api\GraphQL\Types\EntryType + { + return self::$entry ?: (self::$entry = new \IPS\blog\api\GraphQL\Types\EntryType()); + } + + /** + * @return CommentType + */ + public static function comment(): \IPS\blog\api\GraphQL\Types\CommentType + { + return self::$comment ?: (self::$comment = new \IPS\blog\api\GraphQL\Types\CommentType()); + } +} \ No newline at end of file diff --git a/applications/blog/api/GraphQL/Types/BlogType.php b/applications/blog/api/GraphQL/Types/BlogType.php new file mode 100644 index 00000000..720f184d --- /dev/null +++ b/applications/blog/api/GraphQL/Types/BlogType.php @@ -0,0 +1,58 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 22 Oct 2022 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Types; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * BlogType for GraphQL API + */ +class _BlogType extends \IPS\Node\Api\GraphQL\NodeType +{ + /* + * @brief The item classname we use for this type + */ + protected static $nodeClass = \IPS\blog\Blog::class; + + /* + * @brief GraphQL type name + */ + protected static $typeName = 'blog_Blog'; + + /* + * @brief GraphQL type description + */ + protected static $typeDescription = 'A blog'; + + /* + * @brief Follow data passed in to FollowType resolver + */ + protected static $followData = array('app' => 'blog', 'area' => 'blog'); + + + /** + * Get the item type that goes with this node type + * + * @return ObjectType + */ + public static function getItemType() + { + return \IPS\blog\api\GraphQL\TypeRegistry::entry(); + } +} diff --git a/applications/blog/api/GraphQL/Types/CommentType.php b/applications/blog/api/GraphQL/Types/CommentType.php new file mode 100644 index 00000000..7a1b19d4 --- /dev/null +++ b/applications/blog/api/GraphQL/Types/CommentType.php @@ -0,0 +1,75 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 10 May 2017 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Types; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * PostType for GraphQL API + */ +class _CommentType extends \IPS\Content\Api\GraphQL\CommentType +{ + /* + * @brief The item classname we use for this type + */ + protected static $commentClass = \IPS\blog\Entry\Comment::class; + + /* + * @brief GraphQL type name + */ + protected static $typeName = 'blog_Comment'; + + /* + * @brief GraphQL type description + */ + protected static $typeDescription = 'A blog comment'; + + /** + * Get the item type that goes with this item type + * + * @return ObjectType + */ + public static function getItemType() + { + return \IPS\blog\api\GraphQL\TypeRegistry::comment(); + } + + /** + * Return the fields available in this type + * + * @return array + */ + public function fields() + { + $defaultFields = parent::fields(); + $postFields = array( + 'entry' => [ + 'type' => \IPS\blog\api\GraphQL\TypeRegistry::entry(), + 'resolve' => function ($comment) { + return $comment->item(); + } + ], + ); + + // Remove duplicated fields + unset( $defaultFields['item'] ); + + return array_merge( $defaultFields, $postFields ); + } +} diff --git a/applications/blog/api/GraphQL/Types/EntryType.php b/applications/blog/api/GraphQL/Types/EntryType.php new file mode 100644 index 00000000..556af162 --- /dev/null +++ b/applications/blog/api/GraphQL/Types/EntryType.php @@ -0,0 +1,58 @@ +Invision Power Services, Inc. + * @copyright (c) 2001 - 2016 Invision Power Services, Inc. + * @license http://www.invisionpower.com/legal/standards/ + * @package IPS Community Suite + * @since 10 May 2017 + * @version SVN_VERSION_NUMBER + */ + +namespace IPS\blog\api\GraphQL\Types; +use GraphQL\Type\Definition\ObjectType; +use IPS\Api\GraphQL\_TypeRegistry; +use IPS\Api\GraphQL\TypeRegistry; + +/* 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; +} + +/** + * Entry for GraphQL API + */ +class _EntryType extends \IPS\Content\Api\GraphQL\ItemType +{ + /* + * @brief The item classname we use for this type + */ + protected static $itemClass = \IPS\blog\Entry::class; + + /* + * @brief GraphQL type name + */ + protected static $typeName = 'blog_Entry'; + + /* + * @brief GraphQL type description + */ + protected static $typeDescription = 'A blog entry'; + + /* + * @brief Follow data passed in to FollowType resolver + */ + protected static $followData = array('app' => 'blogs', 'area' => 'entry'); + + /** + * Get the comment type that goes with this item type + * + * @return ObjectType + */ + protected static function getCommentType() + { + return \IPS\blog\api\GraphQL\TypeRegistry::comment(); + } +} diff --git a/applications/blog/api/blogs.php b/applications/blog/api/blogs.php index 162a2b51..1d67e9d7 100644 --- a/applications/blog/api/blogs.php +++ b/applications/blog/api/blogs.php @@ -34,6 +34,7 @@ class _blogs extends \IPS\Node\Api\NodeController * Get list of blogs * * @note For requests using an OAuth Access Token for a particular member, only blogs that are not disabled and not belonging to a particular club or social group will be included + * @apiparam string ids Comma-delimited list of blog ids * @apiparam string owners Comma-delimited list of member IDs - if provided, only blogs owned by those members are returned (can be used in conjection with groups or set to 0 to exclude member-created blogs) * @apiparam string groups Comma-delimited list of group IDs - if provided, only blogs owned by those groups are returned (can be used in conjection with members or set to 0 to exclude group blogs) * @apiparam int pinned If 1, only blogs which are pinned are returned, if 0 only not pinned @@ -46,8 +47,8 @@ class _blogs extends \IPS\Node\Api\NodeController public function GETindex() { /* Where clause */ - $where = array(); - + $where = $this->_globalWhere(); + /* Owners */ if ( isset( \IPS\Request::i()->owners ) and isset( \IPS\Request::i()->groups ) ) { diff --git a/applications/blog/api/categories.php b/applications/blog/api/categories.php index 06d8f18a..f55b27e1 100644 --- a/applications/blog/api/categories.php +++ b/applications/blog/api/categories.php @@ -33,6 +33,7 @@ class _categories extends \IPS\Node\Api\NodeController * GET /blog/categories * Get list of blog categories * + * @apiparam string ids Comma-delimited list of category ids * @apiparam string sortBy What to sort by. Can be 'count_entries' for number of entries, 'last_edate' for last entry date or do not specify for ID * @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc' * @apiparam int page Page number @@ -42,7 +43,7 @@ class _categories extends \IPS\Node\Api\NodeController public function GETindex() { /* Where clause */ - $where = array(); + $where = $this->_globalWhere(); /* Sort */ if ( isset( \IPS\Request::i()->sortBy ) and \in_array( \IPS\Request::i()->sortBy, array( 'position' ) ) ) diff --git a/applications/blog/api/entries.php b/applications/blog/api/entries.php index e72bcb1e..cb355409 100644 --- a/applications/blog/api/entries.php +++ b/applications/blog/api/entries.php @@ -33,6 +33,7 @@ class _entries extends \IPS\Content\Api\ItemController * Get list of entries * * @note For requests using an OAuth Access Token for a particular member, only entries that are published and in blogs that are not disabled and not belonging to a particular club or social group will be included + * @apiparam string ids Comma-delimited list of entry IDs * @apiparam string blogs Comma-delimited list of blog IDs * @apiparam string authors Comma-delimited list of member IDs - if provided, only entries started by those members are returned * @apiparam int locked If 1, only entries which are locked are returned, if 0 only unlocked diff --git a/applications/blog/api/entrycategories.php b/applications/blog/api/entrycategories.php index 246935a6..e0ca261d 100644 --- a/applications/blog/api/entrycategories.php +++ b/applications/blog/api/entrycategories.php @@ -45,6 +45,14 @@ class _entrycategories extends \IPS\Node\Api\NodeController /* Where clause */ $where = array(); + $class = $this->class; + + if ( isset( \IPS\Request::i()->ids ) ) + { + $idField = $class::$databaseTable . '.' . $class::$databasePrefix . '.' . $class::$databaseColumnId; + $where[] = array( \IPS\Db::i()->in( $idField, array_map( 'intval', explode( ',', \IPS\Request::i()->ids ) ) ) ); + } + /* Blog */ if ( isset( \IPS\Request::i()->blog ) ) { diff --git a/applications/blog/data/acpmenu.json b/applications/blog/data/acpmenu.json index a0f7fee7..1b195ed6 100644 --- a/applications/blog/data/acpmenu.json +++ b/applications/blog/data/acpmenu.json @@ -1,16 +1,16 @@ { "blogs": { - "settings": { - "tab": "community", - "controller": "settings", - "do": "", - "restriction": "settings_manage" - }, "blogs": { "tab": "community", "controller": "blogs", "do": "", "restriction": "blogs_manage" + }, + "settings": { + "tab": "community", + "controller": "settings", + "do": "", + "restriction": "settings_manage" } } } \ No newline at end of file diff --git a/applications/blog/data/javascript.xml b/applications/blog/data/javascript.xml index e914f054..e633ebe1 100644 --- a/applications/blog/data/javascript.xml +++ b/applications/blog/data/javascript.xml @@ -1,6 +1,6 @@ - - /** + /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * @@ -66,7 +66,7 @@ } }); }(jQuery, _)); - - {{#lang}}manage_cats_new_row{{/lang}}\ "); @@ -373,7 +373,6 @@ controllers underscore jquery mustache -jstz IntersectionObserver Debug.js app.js diff --git a/applications/blog/data/lang.xml b/applications/blog/data/lang.xml index aeea5371..1cb9fef3 100644 --- a/applications/blog/data/lang.xml +++ b/applications/blog/data/lang.xml @@ -1,6 +1,6 @@ - + Blogs Blogs a comment on a blog entry diff --git a/applications/blog/data/theme.xml b/applications/blog/data/theme.xml index c7e2334b..96cfd069 100644 --- a/applications/blog/data/theme.xml +++ b/applications/blog/data/theme.xml @@ -1270,7 +1270,7 @@
-
+