...
 
Commits (12)
......@@ -141,6 +141,7 @@ class blog implements Interfaces\Api
!$blog ||
Helpers\Flags::shouldFail($blog) ||
!Core\Security\ACL::_()->read($blog)
|| ($blog->getTimeCreated() > time() && !$blog->canEdit())
) {
break;
}
......@@ -245,13 +246,6 @@ class blog implements Interfaces\Api
}
}
if (!$blog->isPublished()) {
$blog->setAccessId(Access::UNLISTED);
$blog->setDraftAccessId($_POST['access_id']);
} elseif ($blog->getTimePublished() == '') {
$blog->setTimePublished(time());
}
$blog->setLastSave(time());
if (isset($_POST['wire_threshold'])) {
......@@ -298,6 +292,31 @@ class blog implements Interfaces\Api
}
}
if (isset($_POST['time_created'])) {
try {
$timeCreatedDelegate = new Core\Blogs\Delegates\TimeCreatedDelegate();
if ($editing) {
$timeCreatedDelegate->onUpdate($blog, $_POST['time_created'], time());
} else {
$timeCreatedDelegate->onAdd($blog, $_POST['time_created'], time());
}
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
if (!$blog->isPublished()) {
$blog->setAccessId(Access::UNLISTED);
$blog->setDraftAccessId($_POST['access_id']);
} elseif ($blog->getTimePublished() == '') {
$blog->setTimePublished($blog->getTimeCreated() ?: time());
}
if (!$blog->canEdit()) {
return Factory::response([
'status' => 'error',
......@@ -326,14 +345,20 @@ class blog implements Interfaces\Api
}
if ($saved && is_uploaded_file($_FILES['file']['tmp_name'])) {
/** @var Core\Media\Imagick\Manager $manager */
$manager = Core\Di\Di::_()->get('Media\Imagick\Manager');
$manager->setImage($_FILES['file']['tmp_name'])
->resize(2000, 1000);
try {
$manager->setImage($_FILES['file']['tmp_name'])
->resize(2000, 1000);
$header->write($blog, $manager->getJpeg(), isset($_POST['header_top']) ? (int) $_POST['header_top'] : 0);
$header->write($blog, $manager->getJpeg(), isset($_POST['header_top']) ? (int)$_POST['header_top'] : 0);
} catch (\ImagickException $e) {
return Factory::response([
'status' => 'error',
'message' => 'Invalid image file',
]);
}
}
if ($saved) {
......
......@@ -13,6 +13,7 @@ use Minds\Helpers;
use Minds\Entities;
use Minds\Interfaces;
use Minds\Api\Factory;
use Minds\Core\Features\Manager as FeaturesManager;
class thumbnails implements Interfaces\Api, Interfaces\ApiIgnorePam
{
......@@ -28,6 +29,17 @@ class thumbnails implements Interfaces\Api, Interfaces\ApiIgnorePam
exit;
}
$featuresManager = new FeaturesManager();
if ($featuresManager->has('cdn-jwt')) {
error_log("{$_SERVER['REQUEST_URI']} was hit, and should not have been");
return Factory::response([
'status' => 'error',
'message' => 'This endpoint has been deprecated. Please use fs/v1/thumbnail',
]);
}
$guid = $pages[0];
Core\Security\ACL::$ignore = true;
......
......@@ -516,6 +516,18 @@ class newsfeed implements Interfaces\Api
$attachmentPaywallDelegate = new Core\Feeds\Activity\Delegates\AttachmentPaywallDelegate();
$attachmentPaywallDelegate->onUpdate($activity);
if (isset($_POST['time_created']) && ($_POST['time_created'] != $activity->getTimeCreated())) {
try {
$timeCreatedDelegate = new Core\Feeds\Activity\Delegates\TimeCreatedDelegate();
$timeCreatedDelegate->onUpdate($activity, $_POST['time_created'], time());
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
$save->setEntity($activity)
->save();
......@@ -529,6 +541,19 @@ class newsfeed implements Interfaces\Api
$activity->setMature(isset($_POST['mature']) && !!$_POST['mature']);
$user = Core\Session::getLoggedInUser();
if (isset($_POST['time_created'])) {
try {
$timeCreatedDelegate = new Core\Feeds\Activity\Delegates\TimeCreatedDelegate();
$timeCreatedDelegate->onAdd($activity, $_POST['time_created'], time());
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
if ($user->isMature()) {
$activity->setMature(true);
}
......@@ -599,6 +624,8 @@ class newsfeed implements Interfaces\Api
$attachment->setNsfw($activity->getNsfw());
$attachment->set('time_created', $activity->getTimeCreated());
$save->setEntity($attachment)->save();
switch ($attachment->subtype) {
......
......@@ -53,6 +53,7 @@ class views implements Interfaces\Api
$viewsManager->record(
(new Core\Analytics\Views\View())
->setEntityUrn($boost->getEntity()->getUrn())
->setOwnerGuid((string) $boost->getEntity()->getOwnerGuid())
->setClientMeta($_POST['client_meta'] ?? [])
);
} catch (\Exception $e) {
......@@ -105,6 +106,7 @@ class views implements Interfaces\Api
$viewsManager->record(
(new Core\Analytics\Views\View())
->setEntityUrn($activity->getUrn())
->setOwnerGuid((string) $activity->getOwnerGuid())
->setClientMeta($_POST['client_meta'] ?? [])
);
} catch (\Exception $e) {
......
<?php
namespace Minds\Controllers\api\v2\feeds;
use Minds\Api\Exportable;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities\Factory as EntitiesFactory;
use Minds\Entities\User;
use Minds\Interfaces;
class scheduled implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws \Exception
*/
public function get($pages)
{
/** @var User $currentUser */
$currentUser = Core\Session::getLoggedinUser();
//
$container_guid = $pages[0] ?? null;
if (!$container_guid) {
return Factory::response([
'status' => 'error',
'message' => 'Invalid container',
]);
}
$container = EntitiesFactory::build($container_guid);
if (!$container || !Core\Security\ACL::_()->read($container, $currentUser)) {
return Factory::response([
'status' => 'error',
'message' => 'Forbidden',
]);
}
$type = '';
switch ($pages[1]) {
case 'activities':
$type = 'activity';
break;
case 'images':
$type = 'object:image';
break;
case 'videos':
$type = 'object:video';
break;
case 'blogs':
$type = 'object:blog';
break;
case 'count':
$type = 'activity';
/** @var Core\Feeds\Scheduled\Manager $manager */
$manager = new Core\Feeds\Scheduled\Manager;
return Factory::response([
'status' => 'success',
'count' => $manager->getScheduledCount(['container_guid' => $container_guid, 'type' => $type])
]);
default:
return Factory::response([
'status' => 'error',
'message' => 'Invalid type',
]);
}
$hardLimit = 5000;
$offset = 0;
if (isset($_GET['offset'])) {
$offset = intval($_GET['offset']);
}
$limit = 12;
if (isset($_GET['limit'])) {
$limit = abs(intval($_GET['limit']));
}
if (($offset + $limit) > $hardLimit) {
$limit = $hardLimit - $offset;
}
if ($limit <= 0) {
return Factory::response([
'status' => 'success',
'entities' => [],
'load-next' => $hardLimit,
'overflow' => true,
]);
}
//
$sync = (bool) ($_GET['sync'] ?? false);
$fromTimestamp = $_GET['from_timestamp'] ?? 0;
$asActivities = (bool) ($_GET['as_activities'] ?? true);
$forcePublic = (bool) ($_GET['force_public'] ?? false);
$query = null;
if (isset($_GET['query'])) {
$query = $_GET['query'];
}
$custom_type = isset($_GET['custom_type']) && $_GET['custom_type'] ? [$_GET['custom_type']] : null;
/** @var Core\Feeds\Top\Manager $manager */
$manager = Di::_()->get('Feeds\Top\Manager');
/** @var Core\Feeds\Top\Entities $entities */
$entities = new Core\Feeds\Top\Entities();
$entities->setActor($currentUser);
$isOwner = false;
if ($currentUser) {
$entities->setActor($currentUser);
$isOwner = $currentUser->guid == $container_guid;
}
$opts = [
'cache_key' => $currentUser ? $currentUser->guid : null,
'container_guid' => $container_guid,
'access_id' => $isOwner && !$forcePublic ? [0, 1, 2, $container_guid] : [2, $container_guid],
'custom_type' => $custom_type,
'limit' => $limit,
'type' => $type,
'algorithm' => 'latest',
'period' => '1y',
'sync' => $sync,
'from_timestamp' => $fromTimestamp,
'query' => $query,
'single_owner_threshold' => 0,
'pinned_guids' => $type === 'activity' ? array_reverse($container->getPinnedPosts()) : null,
'time_created_upper' => false,
];
if (isset($_GET['nsfw'])) {
$nsfw = $_GET['nsfw'] ?? '';
$opts['nsfw'] = explode(',', $nsfw);
}
try {
$result = $manager->getList($opts);
if (!$sync) {
// Remove all unlisted content, if ES document is not in sync, it'll
// also remove pending activities
$result = $result->filter([$entities, 'filter']);
if ($asActivities) {
// Cast to ephemeral Activity entities, if another type
$result = $result->map([$entities, 'cast']);
}
}
return Factory::response([
'status' => 'success',
'entities' => Exportable::_($result),
'load-next' => $result->getPagingToken(),
]);
} catch (\Exception $e) {
error_log($e);
return Factory::response(['status' => 'error', 'message' => $e->getMessage()]);
}
}
public function post($pages)
{
return Factory::response([]);
}
public function put($pages)
{
return Factory::response([]);
}
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -8,6 +8,7 @@ use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities;
use Minds\Interfaces;
use Minds\Core\Features\Manager as FeaturesManager;
class thumbnail extends Core\page implements Interfaces\page
{
......@@ -17,6 +18,16 @@ class thumbnail extends Core\page implements Interfaces\page
exit;
}
$featuresManager = new FeaturesManager;
if ($featuresManager->has('cdn-jwt')) {
$signedUri = new Core\Security\SignedUri();
$uri = (string) \Zend\Diactoros\ServerRequestFactory::fromGlobals()->getUri();
if (!$signedUri->confirm($uri)) {
exit;
}
}
/** @var Core\Media\Thumbnails $mediaThumbnails */
$mediaThumbnails = Di::_()->get('Media\Thumbnails');
......
......@@ -55,6 +55,7 @@ class ElasticRepository
'uuid' => $view->getUuid(),
'@timestamp' => $view->getTimestamp() * 1000,
'entity_urn' => $view->getEntityUrn(),
'owner_guid' => $view->getOwnerGuid(),
'page_token' => $view->getPageToken(),
'campaign' => $view->getCampaign(),
'delta' => (int) $view->getDelta(),
......
......@@ -104,6 +104,7 @@ class Repository
->setDay((int) $row['day'] ?: null)
->setUuid($row['uuid']->uuid() ?: null)
->setEntityUrn($row['entity_urn'])
->setOwnerGuid($row['owner_guid'])
->setPageToken($row['page_token'])
->setPosition((int) $row['position'])
->setSource($row['platform'])
......@@ -135,13 +136,14 @@ class Repository
$timestamp = $view->getTimestamp() ?: time();
$date = new DateTime("@{$timestamp}", new DateTimeZone('utc'));
$cql = "INSERT INTO views (year, month, day, uuid, entity_urn, page_token, position, platform, source, medium, campaign, delta) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$cql = "INSERT INTO views (year, month, day, uuid, entity_urn, owner_guid, page_token, position, platform, source, medium, campaign, delta) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$values = [
(int) ($view->getYear() ?? $date->format('Y')),
new Tinyint((int) ($view->getMonth() ?? $date->format('m'))),
new Tinyint((int) ($view->getDay() ?? $date->format('d'))),
new Timeuuid($view->getUuid() ?? $timestamp * 1000),
$view->getEntityUrn() ?: '',
(string) ($view->getOwnerGuid() ?? ''),
$view->getPageToken() ?: '',
(int) ($view->getPosition() ?? -1),
$view->getPlatform() ?: '',
......
......@@ -21,6 +21,8 @@ use Minds\Traits\MagicAttributes;
* @method string getUuid()
* @method View setEntityUrn(string $entityUrn)
* @method string getEntityUrn()
* @method View setOwnerGuid(string $ownerGuid)
* @method string getOwnerGuid()
* @method View setPageToken(string $pageToken)
* @method string getPageToken()
* @method View setPosition(int $position)
......@@ -57,6 +59,9 @@ class View
/** @var string */
protected $entityUrn;
/** @var string */
protected $ownerGuid;
/** @var string */
protected $pageToken;
......
......@@ -95,6 +95,8 @@ use Minds\Traits\MagicAttributes;
* @method int getTimeModerated()
* @method Blog setAllowComments(bool $allowComments)
* @method bool getAllowComments()
* @method int getTimeSent()
* @method Blog setTimeSent(int $time_sent)
*/
class Blog extends RepositoryEntity
{
......@@ -240,6 +242,9 @@ class Blog extends RepositoryEntity
/** @var bool */
protected $allowComments = true;
/** @var int */
protected $timeSent;
/**
* Blog constructor.
* @param null $eventsDispatcher
......@@ -582,6 +587,7 @@ class Blog extends RepositoryEntity
'nsfw',
'nsfw_lock',
'allow_comments',
'time_sent',
function ($export) {
return $this->_extendExport($export);
}
......@@ -609,6 +615,7 @@ class Blog extends RepositoryEntity
$output['nsfw'] = $this->getNsfw();
$output['nsfw_lock'] = $this->getNsfwLock();
$output['allow_comments'] = $this->getAllowComments();
$output['time_sent'] = $this->getTimeSent();
$output['header_bg'] = $export['has_header_bg'];
if (!$this->isEphemeral()) {
......
......@@ -41,7 +41,14 @@ class CreateActivity
{
$activities = $this->db->getRow("activity:entitylink:{$blog->getGuid()}");
if (!empty($activities)) {
return false;
foreach ($activities as $guid) {
$activity = new Activity($guid);
$activity->setTimeCreated($blog->getTimeCreated());
$this->saveAction
->setEntity($activity)
->save();
}
return true;
}
$owner = $blog->getOwnerEntity();
......@@ -60,6 +67,7 @@ class CreateActivity
$activity->container_guid = $owner->guid;
$activity->owner_guid = $owner->guid;
$activity->ownerObj = $owner->export();
$activity->setTimeCreated($blog->getTimeCreated());
$this->saveAction
->setEntity($activity)
......
<?php
/**
* TimeCreatedDelegate
* @author juanmsolaro
*/
namespace Minds\Core\Blogs\Delegates;
use Minds\Core\Feeds\Scheduled\EntityTimeCreated;
class TimeCreatedDelegate
{
/** @var Core\Feeds\Scheduled\EntityTimeCreated $entityTimeCreated */
protected $entityTimeCreated;
/**
* TimeCreatedDelegate constructor.
* @param Save $save
*/
public function __construct()
{
$this->entityTimeCreated = new EntityTimeCreated();
}
/**
* Validates time_created date and sets it to activity
* @param $entity
* @param string $time_created
* @return bool
*/
public function onAdd($entity, $time_created, $time_sent)
{
$this->entityTimeCreated->validate($entity, $time_created, $time_sent);
return true;
}
/**
* Validates time_created date and set it to activity
* @param $entity
* @param string $time_created
* @return bool
*/
public function onUpdate($entity, $time_created, $time_sent)
{
$this->entityTimeCreated->validate($entity, $time_created, $time_sent);
return true;
}
}
......@@ -52,6 +52,7 @@ class Entity
'moderatorGuid' => 'moderator_guid',
'timeModerated' => 'time_moderated',
'allowComments' => 'allow_comments',
'timeSent' => 'time_sent'
];
public static $jsonEncodedFields = [
......
......@@ -114,7 +114,7 @@ class Manager
}
$blog
->setTimeCreated(time())
->setTimeCreated($blog->getTimeCreated() ?: time())
->setTimeUpdated(time())
->setLastUpdated(time())
->setLastSave(time());
......
......@@ -9,6 +9,7 @@ use Minds\Entities\RepositoryEntity;
use Minds\Entities\User;
use Minds\Helpers\Flags;
use Minds\Helpers\Unknown;
use Minds\Core\Di\Di;
/**
* Comment Entity
......@@ -284,6 +285,21 @@ class Comment extends RepositoryEntity
return "{$this->getParentGuidL1()}:{$this->getParentGuidL2()}:{$this->getGuid()}";
}
/**
* Return an array of thumbnails
* @return array
*/
public function getThumbnails(): array
{
$thumbnails = [];
$mediaManager = Di::_()->get('Media\Image\Manager');
$sizes = [ 'xlarge', 'large' ];
foreach ($sizes as $size) {
$thumbnails[$size] = $mediaManager->getPublicAssetUri($this, $size);
}
return $thumbnails;
}
/**
* Return the urn for the comment
* @return string
......@@ -384,6 +400,8 @@ class Comment extends RepositoryEntity
$output['thumbs:down:count'] = count($this->getVotesDown());
}
$output['thumbnails'] = $this->getThumbnails();
$output['can_reply'] = (bool) !$this->getParentGuidL2();
//$output['parent_guid'] = (string) $this->entityGuid;
......
......@@ -5,21 +5,40 @@
namespace Minds\Core\Data\PubSub\Redis;
use Minds\Core\Config;
use \Redis as RedisServer;
class Client
{
/**
* @var \Redis
*/
private $redis;
/**
* @var string
*/
private $host;
public function __construct($redis = null)
{
if (class_exists('\Redis')) {
$this->redis = $redis ?: new RedisServer();
$this->redis = $redis ?: new \Redis();
$config = Config::_()->get('redis');
try {
$this->redis->connect($config['pubsub'] ?: $config['master'] ?: '127.0.0.1');
} catch (\Exception $e) {
$this->host = $config['pubsub'] ?: $config['master'] ?: '127.0.0.1';
$this->connect();
}
}
/**
* @throws \Exception
*/
public function connect(): void
{
if (!$this->redis instanceof \Redis) {
$this->redis = new \Redis();
}
if (!$this->redis->isConnected()) {
if (!@$this->redis->connect($this->host)) {
throw new \Exception("Unable to connect to Redis: " . $this->redis->getLastError());
}
}
}
......@@ -34,11 +53,17 @@ class Client
}
}
/**
* @param $channel
* @param string $data
* @return bool|int
*/
public function publish($channel, $data = '')
{
if (!$this->redis) {
if (!$this->redis->isConnected()) {
return false;
}
return $this->redis->publish($channel, $data);
}
}
<?php
/**
* ResolverDelegate.
*
* @author juanmsolaro
*/
namespace Minds\Core\Entities\Delegates;
use Minds\Core\Security\ACL;
use Minds\Entities\User;
class FilterEntitiesDelegate
{
/** @var ACL */
protected $acl;
/** @var User */
protected $user;
/** @var int */
protected $time;
public function __construct($user, $time, $acl = null)
{
$this->acl = $acl ?: ACL::_();
$this->user = $user;
$this->time = $time;
}
/**
* Filter entities by read rights and write rights for scheduled activities, images, videos or blogs
* @param array $entities
* @return array
*/
public function filter($entities)
{
return array_values(array_filter($entities, function ($entity) {
$filterByScheduled = false;
if ($this->shouldFilterScheduled($entity->getType())) {
$filterByScheduled = $entity->getTimeCreated() > $this->time
&& !ACL::_()->write($entity, $this->user);
}
return !$filterByScheduled && $this->acl->read($entity, $this->user);
}));
}
private function shouldFilterScheduled($type)
{
return $type == 'activity'
|| $type == 'blog'
|| $type == 'video'
|| $type == 'image';
}
}
......@@ -12,6 +12,7 @@ use Minds\Core\Entities\Delegates\BoostGuidResolverDelegate;
use Minds\Core\Entities\Delegates\CommentGuidResolverDelegate;
use Minds\Core\Entities\Delegates\EntityGuidResolverDelegate;
use Minds\Core\Entities\Delegates\ResolverDelegate;
use Minds\Core\Entities\Delegates\FilterEntitiesDelegate;
use Minds\Core\Security\ACL;
use Minds\Entities\User;
......@@ -128,11 +129,8 @@ class Resolver
});
// Filter out forbidden entities
$sorted = array_filter($sorted, function ($entity) {
return $this->acl->read($entity, $this->user);
//&& !Flags::shouldFail($entity);
});
$filterDelegate = new FilterEntitiesDelegate($this->user, time(), $this->acl);
$sorted = $filterDelegate->filter($sorted);
//
......
<?php
/**
* TimeCreatedDelegate
* @author juanmsolaro
*/
namespace Minds\Core\Feeds\Activity\Delegates;
use Minds\Core\Feeds\Scheduled\EntityTimeCreated;
class TimeCreatedDelegate
{
/** @var Core\Feeds\Scheduled\EntityTimeCreated $entityTimeCreated */
protected $entityTimeCreated;
/**
* TimeCreatedDelegate constructor.
* @param Save $save
*/
public function __construct()
{
$this->entityTimeCreated = new EntityTimeCreated();
}
/**
* Validates time_created date and set it to activity
* @param $entity
* @param string $time_created
* @return bool
*/
public function onAdd($entity, $time_created, $time_sent)
{
$this->entityTimeCreated->validate($entity, $time_created, $time_sent);
return true;
}
/**
* Validates time_created date and set it to activity
* @param $entity
* @param string $time_created
* @return bool
*/
public function onUpdate($entity, $time_created, $time_sent)
{
$this->entityTimeCreated->validate($entity, $time_created, $time_sent);
return true;
}
}
<?php
namespace Minds\Core\Feeds\Scheduled;
class EntityTimeCreated
{
/**
* EntityTimeCreated constructor.
*
*/
public function __construct()
{
}
public function validate($entity, $time_created, $time_sent)
{
if ($time_created > strtotime('+3 Months')) {
throw new \InvalidParameterException();
}
if ($time_created < strtotime('+5 Minutes')) {
$time_created = $time_sent;
}
$entity->setTimeCreated($time_created);
$entity->setTimeSent($time_sent);
}
}
<?php
namespace Minds\Core\Feeds\Scheduled;
class Manager
{
/** @var Repository */
protected $repository;
public function __construct(
$repository = null
) {
$this->repository = $repository ?: new Repository;
}
/**
* @param array $opts
* @return int
*/
public function getScheduledCount(array $opts = [])
{
return $this->repository->getScheduledCount($opts) ;
}
}
<?php
namespace Minds\Core\Feeds\Scheduled;
use Minds\Core\Data\ElasticSearch\Client as ElasticsearchClient;
use Minds\Core\Data\ElasticSearch\Prepared;
use Minds\Core\Di\Di;
use Minds\Helpers\Text;
class Repository
{
/** @var ElasticsearchClient */
protected $client;
protected $index;
public function __construct($client = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$config = $config ?: Di::_()->get('Config');
$this->index = $config->get('elasticsearch')['index'];
}
public function getScheduledCount(array $opts = [])
{
$opts = array_merge([
'container_guid' => null,
'type' => null,
], $opts);
if (!$opts['type']) {
throw new \Exception('Type must be provided');
}
if (!$opts['container_guid']) {
throw new \Exception('Container Guid must be provided');
}
$containerGuids = Text::buildArray($opts['container_guid']);
$query = [
'index' => $this->index,
'type' => $opts['type'],
'body' => [
'query' => [
'bool' => [
'must' => [
[
'range' => [
'@timestamp' => [
'gt' => time() * 1000,
]
]
],
[
'terms' => [
'container_guid' => $containerGuids,
],
]
]
]
]
]
];
$prepared = new Prepared\Count();
$prepared->query($query);
$result = $this->client->request($prepared);
return $result['count'] ?? 0;
}
}
......@@ -53,6 +53,7 @@ class Repository
'exclude_moderated' => false,
'moderation_reservations' => null,
'pinned_guids' => null,
'time_created_upper' => time(),
'exclude' => null,
], $opts);
......@@ -257,6 +258,20 @@ class Repository
];
}
// Filter by time created to cut out scheduled feeds
$time_created_upper = $opts['time_created_upper'] ? 'lte' : 'gt';
if (!isset($body['query']['function_score']['query']['bool']['must'])) {
$body['query']['function_score']['query']['bool']['must'] = [];
}
$body['query']['function_score']['query']['bool']['must'][] = [
'range' => [
'@timestamp' => [
$time_created_upper => ((int) ($opts['time_created_upper'] ?: time())) * 1000,
],
],
];
//
if ($opts['query']) {
$words = explode(' ', $opts['query']);
......
<?php
/**
* Image Manager
*/
namespace Minds\Core\Media\Image;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Entities\Entity;
use Minds\Entities\Activity;
use Minds\Entities\Image;
use Minds\Entities\Video;
use Minds\Core\Comments\Comment;
use Minds\Core\Security\SignedUri;
use Lcobucci\JWT;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Zend\Diactoros\Uri;
class Manager
{
/** @var Config $config */
private $config;
/** @var SignedUri $signedUri */
private $signedUri;
public function __construct($config = null, $signedUri = null)
{
$this->config = $config ?? Di::_()->get('Config');
$this->signedUri = $signedUri ?? new SignedUri;
}
/**
* Return a public asset uri for entity type
* @param Entity $entity
* @param string $size
* @return string
*/
public function getPublicAssetUri($entity, $size = 'xlarge'): string
{
$uri = null;
$asset_guid = null;
switch (get_class($entity)) {
case Activity::class:
switch ($entity->get('custom_type')) {
case "batch":
$asset_guid = $entity->get('entity_guid');
break;
default:
$asset_guid = $entity->get('entity_guid');
}
break;
case Image::class:
$asset_guid = $entity->getGuid();
break;
case Video::class:
$asset_guid = $entity->getGuid();
break;
case Comment::class:
$asset_guid = $entity->getAttachments()['attachment_guid'];
break;
}
$uri = $this->config->get('cdn_url') . 'fs/v1/thumbnail/' . $asset_guid . '/' . $size;
$uri = $this->signUri($uri);
return $uri;
}
/**
* Sign a uri and return the uri with the signature attached
* @param string $uri
* @return string
*/
private function signUri($uri, $pub = ""): string
{
$now = new \DateTime();
$expires = $now->modify('midnight + 30 days')->getTimestamp();
return $this->signedUri->sign($uri, $expires);
}
/**
* Config signed uri
* @param string $uri
* @return string
*/
public function confirmSignedUri($uri): bool
{
return $this->signedUri->confirm($uri);
}
}
......@@ -12,6 +12,12 @@ class MediaProvider extends Provider
{
public function register()
{
$this->di->bind('Media\Image\Manager', function ($di) {
return new Image\Manager();
}, ['useFactory' => true]);
$this->di->bind('Media\Video\Manager', function ($di) {
return new Video\Manager();
}, ['useFactory' => true]);
$this->di->bind('Media\Albums', function ($di) {
return new Albums(new Core\Data\Call('entities_by_time'));
}, ['useFactory' => true]);
......
<?php
/**
* Video Manager
*/
namespace Minds\Core\Media\Video;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Entities\Entity;
use Minds\Entities\Activity;
use Minds\Entities\Image;
use Minds\Entities\Video;
use Minds\Core\Comments\Comment;
use Aws\S3\S3Client;
class Manager
{
/** @var Config $config */
private $config;
/** @var S3Client $s3 */
private $s3;
public function __construct($config = null, $s3 = null)
{
$this->config = $config ?? Di::_()->get('Config');
// AWS
$awsConfig = $this->config->get('aws');
$opts = [
'region' => $awsConfig['region'],
];
if (!isset($awsConfig['useRoles']) || !$awsConfig['useRoles']) {
$opts['credentials'] = [
'key' => $awsConfig['key'],
'secret' => $awsConfig['secret'],
];
}
$this->s3 = $s3 ?: new S3Client(array_merge(['version' => '2006-03-01'], $opts));
}
/**
* Return a public asset uri for entity type
* @param Entity $entity
* @param string $size
* @return string
*/
public function getPublicAssetUri($entity, $size = '360.mp4'): string
{
$cmd = null;
switch (get_class($entity)) {
case Activity::class:
// To do
break;
case Video::class:
$cmd = $this->s3->getCommand('GetObject', [
'Bucket' => 'cinemr', // TODO: don't hard code
'Key' => $this->config->get('transcoder')['dir'] . "/" . $entity->get('cinemr_guid') . "/" . $size,
]);
break;
}
if (!$cmd) {
return null;
}
return (string) $this->s3->createPresignedRequest($cmd, '+20 minutes')->getUri();
}
}
......@@ -1445,3 +1445,5 @@ CREATE TABLE minds.email_campaign_logs (
email_campaign_id text,
PRIMARY KEY (receiver_guid, time_sent)
) WITH CLUSTERING ORDER BY (time_sent desc);
ALTER TABLE minds.views ADD owner_guid text;
\ No newline at end of file
<?php
namespace Minds\Core\Security;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Lcobucci\JWT;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Zend\Diactoros\Uri;
class SignedUri
{
/** @Var JWT\Builder $jwtBuilder */
private $jwtBuilder;
/** @var JWT\Parser $jwtParser */
private $jwtParser;
/** @var Config $config */
private $config;
public function __construct($jwtBuilder = null, $jwtParser = null, $config = null)
{
$this->jwtBuilder = $jwtBuilder ?? new JWT\Builder;
$this->jwtParser = $jwtParser ?? new JWT\Parser();
$this->config = $config ?? Di::_()->get('Config');
}
/**
* Sign the uri
* @param string $uri
* @param int $ttl - defaults to 1 day
* @return string
*/
public function sign($uri, $expires = 86400): string
{
$uri = new Uri($uri);
$expires = (new \DateTime())->modify('midnight first day of next month')->modify('+1 month')->getTimestamp();
$token = (new $this->jwtBuilder)
//->setId((string) $uri)
->setExpiration($expires)
->set('uri', (string) $uri)
->set('user_guid', Session::isLoggedIn() ? (string) Session::getLoggedInUser()->getGuid() : null)
->sign(new Sha256, $this->config->get('sessions')['private_key'])
->getToken();
$signedUri = $uri->withQuery("jwtsig=$token");
return (string) $signedUri;
}
/**
* Confirm signed uri
* @param string $uri
* @return string
*/
public function confirm($uri): bool
{
$providedUri = new Uri($uri);
parse_str($providedUri->getQuery(), $queryParams);
$providedSig = $queryParams['jwtsig'];
$token = $this->jwtParser->parse($providedSig);
if (!$token->verify(new Sha256, $this->config->get('sessions')['private_key'])) {
return false;
}
return ((string) $token->getClaim('uri') === (string) $providedUri->withQuery(''));
}
}
......@@ -4,15 +4,22 @@
*/
namespace Minds\Core\Sockets;
use Minds\Core\Data\PubSub;
use Minds\Core\Di\Di;
use Minds\Core\Config;
use Minds\Entities\User;
class Events
{
/**
* @var PubSub\Redis\Client
*/
private $redis;
/**
* @var MsgPack
*/
private $msgpack;
private $prefix = 'socket.io';
private $prefix = 'socket.io#';
private $rooms = [];
private $flags = [];
......@@ -25,7 +32,6 @@ class Events
{
$this->redis = $redis ?: Di::_()->get('PubSub\Redis');
$this->msgpack = $msgpack ?: new MsgPack();
$this->prefix = (isset($config['socket-prefix']) ? $config['socket-prefix'] : 'socket.io') . '#';
}
public function emit(/*$event, ...$data*/)
......@@ -82,7 +88,7 @@ class Events
$packed = str_replace(pack('c', 0xdb), pack('c', 0xd9), $packed);
}
// Publish
// Publish - TODO: Log + track possible Redis failures if false is returned
$this->redis->publish($this->prefix . $packet['nsp'] . '#', $packed);
// Reset
......
......@@ -67,7 +67,7 @@ class S3 implements ServiceInterface
$mimeType = $finfo->buffer($data);
$write = $this->s3->putObject([
'ACL' => 'public-read',
// 'ACL' => 'public-read',
'Bucket' => Config::_()->aws['bucket'],
'Key' => $this->filepath,
'ContentType' => $mimeType,
......@@ -121,6 +121,20 @@ class S3 implements ServiceInterface
}
}
/**
* Return a signed url
* @return string
*/
public function getSignedUrl(): string
{
$cmd = $this->s3->getCommand('GetObject', [
'Bucket' => Config::_()->aws['bucket'],
'Key' => $this->filepath,
]);
$request = $this->s3->createPresignedRequest($cmd, '+20 minutes');
return (string) $request->getUri();
}
public function seek($offset = 0)
{
//not supported
......
......@@ -3,6 +3,7 @@ namespace Minds\Entities;
use Minds\Helpers;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Queue;
use Minds\Core\Analytics;
......@@ -39,6 +40,7 @@ class Activity extends Entity
'pending' => false,
'rating' => 2, //open by default
'ephemeral' => false,
'time_sent' => null,
// 'node' => elgg_get_site_url()
]);
}
......@@ -218,6 +220,7 @@ class Activity extends Entity
'ephemeral',
'hide_impressions',
'pinned',
'time_sent',
]);
}
......@@ -274,11 +277,14 @@ class Activity extends Entity
$export['rating'] = $this->getRating();
$export['ephemeral'] = $this->getEphemeral();
$export['ownerObj'] = $this->getOwnerObj();
$export['time_sent'] = $this->getTimeSent();
if ($this->hide_impressions) {
$export['hide_impressions'] = $this->hide_impressions;
}
$export['thumbnails'] = $this->getThumbnails();
switch ($this->custom_type) {
case 'video':
if ($this->custom_data['guid']) {
......@@ -289,7 +295,11 @@ class Activity extends Entity
// fix old images src
if (is_array($export['custom_data']) && strpos($export['custom_data'][0]['src'], '/wall/attachment') !== false) {
$export['custom_data'][0]['src'] = Core\Config::_()->cdn_url . 'fs/v1/thumbnail/' . $this->entity_guid;
$this->custom_data[0]['src'] = $export['custom_data'][0]['src'];
}
// go directly to cdn
$mediaManager = Di::_()->get('Media\Image\Manager');
$export['custom_data'][0]['src'] = $export['thumbnails']['xlarge'];
break;
}
......@@ -720,6 +730,27 @@ class Activity extends Entity
return $this->ownerObj;
}
/**
* Return thumbnails array to be used with export
* @return array
*/
public function getThumbnails(): array
{
$thumbnails = [];
switch ($this->custom_type) {
case 'video':
break;
case 'batch':
$mediaManager = Di::_()->get('Media\Image\Manager');
$sizes = [ 'xlarge', 'large' ];
foreach ($sizes as $size) {
$thumbnails[$size] = $mediaManager->getPublicAssetUri($this, $size);
}
break;
}
return $thumbnails;
}
/**
* Return a preferred urn
* @return string
......@@ -728,4 +759,23 @@ class Activity extends Entity
{
return "urn:activity:{$this->getGuid()}";
}
/**
* Return time_sent
* @return int
*/
public function getTimeSent()
{
return $this->time_sent;
}
/**
* Set time_sent
* @return Activity
*/
public function setTimeSent($time_sent)
{
$this->time_sent = $time_sent;
return $this;
}
}
......@@ -5,6 +5,7 @@
namespace Minds\Entities;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Helpers;
class Image extends File
......@@ -19,6 +20,7 @@ class Image extends File
$this->attributes['rating'] = 2;
$this->attributes['width'] = 0;
$this->attributes['height'] = 0;
$this->attributes['time_sent'] = null;
}
public function getUrl()
......@@ -33,17 +35,18 @@ class Image extends File
$size = '';
}
if (isset($CONFIG->cdn_url) && !$this->getFlag('paywall') && !$this->getWireThreshold()) {
$base_url = $CONFIG->cdn_url;
} else {
$base_url = \elgg_get_site_url();
}
// if (isset($CONFIG->cdn_url) && !$this->getFlag('paywall') && !$this->getWireThreshold()) {
// $base_url = $CONFIG->cdn_url;
// } else {
// $base_url = \elgg_get_site_url();
// }
if ($this->access_id != 2) {
$base_url = \elgg_get_site_url();
}
// if ($this->access_id != 2) {
// $base_url = \elgg_get_site_url();
// }
$mediaManager = Di::_()->get('Media\Image\Manager');
return $base_url. 'api/v1/media/thumbnails/' . $this->guid . '/'.$size;
return $mediaManager->getPublicAssetUri($this, $size);
}
protected function getIndexKeys($ia = false)
......@@ -191,6 +194,7 @@ class Image extends File
'width',
'height',
'gif',
'time_sent',
]);
}
......@@ -211,7 +215,8 @@ class Image extends File
public function export()
{
$export = parent::export();
$export['thumbnail_src'] = $this->getIconUrl();
$export['thumbnail_src'] = $this->getIconUrl('xlarge');
$export['thumbnail'] = $export['thumbnail_src'];
$export['thumbs:up:count'] = Helpers\Counters::get($this->guid, 'thumbs:up');
$export['thumbs:down:count'] = Helpers\Counters::get($this->guid, 'thumbs:down');
$export['description'] = $this->description; //videos need to be able to export html.. sanitize soon!
......@@ -221,6 +226,7 @@ class Image extends File
$export['height'] = $this->height ?: 0;
$export['gif'] = (bool) $this->gif;
$export['urn'] = $this->getUrn();
$export['time_sent'] = $this->getTimeSent();
if (!Helpers\Flags::shouldDiscloseStatus($this) && isset($export['flags']['spam'])) {
unset($export['flags']['spam']);
......@@ -264,6 +270,7 @@ class Image extends File
'access_id' => null,
'container_guid' => null,
'rating' => 2, //open by default
'time_sent' => time(),
], $data);
$allowed = [
......@@ -277,6 +284,7 @@ class Image extends File
'mature',
'boost_rejection_reason',
'rating',
'time_sent',
];
foreach ($allowed as $field) {
......@@ -358,4 +366,23 @@ class Image extends File
{
return "urn:image:{$this->guid}";
}
/**
* Return time_sent
* @return int
*/
public function getTimeSent()
{
return $this->time_sent;
}
/**
* Set time_sent
* @return Image
*/
public function setTimeSent($time_sent)
{
$this->time_sent = $time_sent;
return $this;
}
}
......@@ -8,6 +8,7 @@ namespace Minds\Entities;
use Minds\Core;
use Minds\Core\Media\Services\Factory as ServiceFactory;
use Minds\Core\Di\Di;
use cinemr;
use Minds\Helpers;
......@@ -23,6 +24,7 @@ class Video extends Object
$this->attributes['subtype'] = "video";
$this->attributes['boost_rejection_reason'] = -1;
$this->attributes['rating'] = 2;
$this->attributes['time_sent'] = null;
}
......@@ -38,8 +40,8 @@ class Video extends Object
*/
public function getSourceUrl($transcode = '720.mp4')
{
$url = Core\Config::_()->cinemr_url . $this->cinemr_guid . '/' . $transcode;
return $url;
$mediaManager = Di::_()->get('Media\Video\Manager');
return $mediaManager->getPublicAssetUri($this, $transcode);
}
/**
......@@ -61,13 +63,16 @@ class Video extends Object
public function getIconUrl($size = "medium")
{
$domain = elgg_get_site_url();
global $CONFIG;
if (isset($CONFIG->cdn_url) && !$this->getFlag('paywall') && !$this->getWireThreshold()) {
$domain = $CONFIG->cdn_url;
}
// $domain = elgg_get_site_url();
// global $CONFIG;
// if (isset($CONFIG->cdn_url) && !$this->getFlag('paywall') && !$this->getWireThreshold()) {
// $domain = $CONFIG->cdn_url;
// }
// return $domain . 'api/v1/media/thumbnails/' . $this->guid . '/' . $this->time_updated;
return $domain . 'api/v1/media/thumbnails/' . $this->guid . '/' . $this->time_updated;
$mediaManager = Di::_()->get('Media\Image\Manager');
return $mediaManager->getPublicAssetUri($this, 'medium');
}
public function getURL()
......@@ -112,7 +117,8 @@ class Video extends Object
'license',
'monetized',
'mature',
'boost_rejection_reason'
'boost_rejection_reason',
'time_sent',
]);
}
......@@ -143,6 +149,7 @@ class Video extends Object
$export['thumbs:down:count'] = Helpers\Counters::get($this->guid, 'thumbs:down');
$export['description'] = (new Core\Security\XSS())->clean($this->description); //videos need to be able to export html.. sanitize soon!
$export['rating'] = $this->getRating();
$export['time_sent'] = $this->getTimeSent();
if (!Helpers\Flags::shouldDiscloseStatus($this) && isset($export['flags']['spam'])) {
unset($export['flags']['spam']);
......@@ -185,6 +192,7 @@ class Video extends Object
'access_id' => null,
'container_guid' => null,
'rating' => 2, //open by default
'time_sent' => time(),
], $data);
$allowed = [
......@@ -197,6 +205,7 @@ class Video extends Object
'mature',
'boost_rejection_reason',
'rating',
'time_sent'
];
foreach ($allowed as $field) {
......@@ -264,4 +273,23 @@ class Video extends Object
{
return "urn:video:{$this->getGuid()}";
}
/**
* Return time_sent
* @return int
*/
public function getTimeSent()
{
return $this->time_sent;
}
/**
* Set time_sent
* @return Image
*/
public function setTimeSent($time_sent)
{
$this->time_sent = $time_sent;
return $this;
}
}
......@@ -61,6 +61,10 @@ class RepositorySpec extends ObjectBehavior
->shouldBeCalled()
->willReturn('urn:test:123123');
$view->getOwnerGuid()
->shouldBeCalled()
->wilLReturn('789');
$view->getPageToken()
->shouldBeCalled()
->willReturn('1234-qwe-qwe-1234');
......@@ -98,13 +102,14 @@ class RepositorySpec extends ObjectBehavior
$statement['values'][2]->toInt() === 29 &&
$statement['values'][3]->uuid() === 'abc-123-456-def' &&
$statement['values'][4] === 'urn:test:123123' &&
$statement['values'][5] === '1234-qwe-qwe-1234' &&
$statement['values'][6] === 5 &&
$statement['values'][7] === 'php' &&
$statement['values'][8] === 'phpspec' &&
$statement['values'][9] === 'test' &&
$statement['values'][10] === 'urn:phpspec:234234' &&
$statement['values'][11] === 100;
$statement['values'][5] === '789' &&
$statement['values'][6] === '1234-qwe-qwe-1234' &&
$statement['values'][7] === 5 &&
$statement['values'][8] === 'php' &&
$statement['values'][9] === 'phpspec' &&
$statement['values'][10] === 'test' &&
$statement['values'][11] === 'urn:phpspec:234234' &&
$statement['values'][12] === 100;
}), true)
->shouldBeCalled()
->willReturn(true);
......@@ -143,6 +148,10 @@ class RepositorySpec extends ObjectBehavior
->shouldBeCalled()
->willReturn('urn:test:123123');
$view->getOwnerGuid()
->shouldBeCalled()
->willReturn(789);
$view->getPageToken()
->shouldBeCalled()
->willReturn('1234-qwe-qwe-1234');
......@@ -180,13 +189,14 @@ class RepositorySpec extends ObjectBehavior
$statement['values'][2]->toInt() === 29 &&
$statement['values'][3]->time() === $now * 1000 &&
$statement['values'][4] === 'urn:test:123123' &&
$statement['values'][5] === '1234-qwe-qwe-1234' &&
$statement['values'][6] === 5 &&
$statement['values'][7] === 'php' &&
$statement['values'][8] === 'phpspec' &&
$statement['values'][9] === 'test' &&
$statement['values'][10] === 'urn:phpspec:234234' &&
$statement['values'][11] === 100;
$statement['values'][5] === '789' &&
$statement['values'][6] === '1234-qwe-qwe-1234' &&
$statement['values'][7] === 5 &&
$statement['values'][8] === 'php' &&
$statement['values'][9] === 'phpspec' &&
$statement['values'][10] === 'test' &&
$statement['values'][11] === 'urn:phpspec:234234' &&
$statement['values'][12] === 100;
}), true)
->shouldBeCalled()
->willReturn(true);
......
......@@ -80,6 +80,10 @@ class CreateActivitySpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(1000);
$blog->getTimeCreated()
->shouldBeCalled()
->willReturn(9999);
$this->db->getRow("activity:entitylink:9999")
->shouldBeCalled()
->willReturn([]);
......@@ -97,19 +101,31 @@ class CreateActivitySpec extends ObjectBehavior
->shouldReturn(true);
}
public function it_should_not_save_when_previous_activity(
public function it_should_save_when_previous_activity(
Blog $blog
) {
$blog->getGuid()
->shouldBeCalled()
->willReturn(9999);
$blog->getTimeCreated()
->shouldBeCalled()
->willReturn(9999);
$this->db->getRow("activity:entitylink:9999")
->shouldBeCalled()
->willReturn(['activity1']);
$this->saveAction->setEntity(Argument::type(Activity::class))
->shouldBeCalled()
->willReturn($this->saveAction);
$this->saveAction->save()
->shouldBeCalled()
->willReturn(true);
$this
->save($blog)
->shouldReturn(false);
->shouldReturn(true);
}
}
......@@ -153,6 +153,10 @@ class ManagerSpec extends ObjectBehavior
$blog->getSubtype()
->willReturn('blog');
$blog->getTimeCreated()
->shouldBeCalled()
->willReturn(9999);
$blog->setTimeCreated(Argument::type('int'))
->shouldBeCalled()
->willReturn($blog);
......
<?php
namespace Spec\Minds\Core\Feeds\Scheduled;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Feeds\Scheduled\Manager;
use Minds\Core\Feeds\Scheduled\Repository;
use Minds\Core\Search\Search;
use PhpSpec\ObjectBehavior;
class ManagerSpec extends ObjectBehavior
{
/** @var Repository */
protected $repository;
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Search */
protected $search;
public function let(
Repository $repository
) {
$this->repository = $repository;
$this->beConstructedWith($repository);
}
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_get_scheduled_count()
{
$argument = ['container_guid' => 9999, 'type' => 'activity'];
$this->repository->getScheduledCount($argument)
->shouldBeCalled()
->willReturn(1);
$this->getScheduledCount($argument);
}
}
<?php
namespace Spec\Minds\Core\Feeds\Scheduled;
use Minds\Core\Config;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Data\ElasticSearch\Prepared\Count;
use Minds\Core\Feeds\Scheduled\Repository;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
{
/** @var Client */
protected $client;
/** @var Config */
protected $config;
public function let(Client $client, Config $config)
{
$this->client = $client;
$this->config = $config;
$config->get('elasticsearch')
->shouldBeCalled()
->willReturn(['index' => 'minds']);
$this->beConstructedWith($client, $config);
}
public function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
}
public function it_should_count_scheduled_activities()
{
$opts = ['container_guid' => 9999, 'type' => 'activity'];
$this->client->request(Argument::type(Count::class))
->shouldBeCalled()
->willReturn([
"count" => 1,
"_shards" => [
"total" => 5,
"successful" => 5,
"skipped" => 0,
"failed" => 0
]
]);
$this->getScheduledCount($opts);
}
}
<?php
namespace Spec\Minds\Core\Media\Image;
use Minds\Core\Media\Image\Manager;
use Minds\Core\Config;
use Minds\Core\Security\SignedUri;
use Minds\Entities\Activity;
use Minds\Entities\Image;
use Minds\Entities\Video;
use Minds\Core\Comments\Comment;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $config;
private $signedUri;
public function let(Config $config, SignedUri $signedUri)
{
$this->beConstructedWith($config, $signedUri);
$this->config = $config;
$this->signedUri = $signedUri;
}
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_return_public_asset_uri()
{
$activity = new Activity();
$activity->set('entity_guid', 123);
$this->config->get('cdn_url')
->willReturn('https://minds.dev/');
$uri = 'https://minds.dev/fs/v1/thumbnail/123/xlarge';
$this->signedUri->sign($uri, Argument::any())
->willReturn('signed url will be here');
$this->getPublicAssetUri($activity)
->shouldBe('signed url will be here');
}
public function it_should_return_public_asset_uri_for_image()
{
$entity = new Image();
$entity->set('guid', 123);
$this->config->get('cdn_url')
->willReturn('https://minds.dev/');
$uri = 'https://minds.dev/fs/v1/thumbnail/123/xlarge';
$this->signedUri->sign($uri, Argument::any())
->willReturn('signed url will be here');
$this->getPublicAssetUri($entity)
->shouldBe('signed url will be here');
}
public function it_should_return_public_asset_uri_for_video()
{
$entity = new Video();
$entity->set('guid', 123);
$this->config->get('cdn_url')
->willReturn('https://minds.dev/');
$uri = 'https://minds.dev/fs/v1/thumbnail/123/xlarge';
$this->signedUri->sign($uri, Argument::any())
->willReturn('signed url will be here');
$this->getPublicAssetUri($entity)
->shouldBe('signed url will be here');
}
public function it_should_return_public_asset_uri_for_comment()
{
$entity = new Comment();
$entity->setAttachment('attachment_guid', '123');
$this->config->get('cdn_url')
->willReturn('https://minds.dev/');
$uri = 'https://minds.dev/fs/v1/thumbnail/123/xlarge';
$this->signedUri->sign($uri, Argument::any())
->willReturn('signed url will be here');
$this->getPublicAssetUri($entity)
->shouldBe('signed url will be here');
}
}
<?php
namespace Spec\Minds\Core\Media\Video;
use Minds\Core\Config;
use Aws\S3\S3Client;
use Minds\Core\Media\Video\Manager;
use Minds\Entities\Video;
use Psr\Http\Message\RequestInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $config;
private $s3;
public function let(Config $config, S3Client $s3)
{
$this->beConstructedWith($config, $s3);
$this->config = $config;
$this->s3 = $s3;
}
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_get_a_720p_video(RequestInterface $request, \Aws\CommandInterface $cmd)
{
$this->config->get('transcoder')
->willReturn([
'dir' => 'dir',
]);
$this->config->get('aws')
->willReturn([
'region' => 'us-east-1',
'useRoles' => true,
]);
$this->s3->getCommand('GetObject', [
'Bucket' => 'cinemr',
'Key' => 'dir/123/720.mp4'
])
->shouldBeCalled()
->willReturn($cmd);
$request->getUri()
->willReturn('s3-signed-url-here');
$this->s3->createPresignedRequest(Argument::any(), Argument::any())
->willReturn($request);
$video = new Video();
$video->set('cinemr_guid', 123);
$this->getPublicAssetUri($video, '720.mp4')
->shouldBe('s3-signed-url-here');
}
}
<?php
namespace Spec\Minds\Core\Security;
use Minds\Core\Security\SignedUri;
use Minds\Entities\User;
use Minds\Core\Config;
use Lcobucci\JWT;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class SignedUriSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(SignedUri::class);
}
public function it_sign_a_uri(Config $config)
{
$user = new User();
$user->set('guid', 123);
$user->set('username', 'phpspec');
\Minds\Core\Session::setUser($user);
$this->beConstructedWith(null, null, $config);
$config->get('sessions')
->willReturn([
'private_key' => 'priv-key'
]);
$this->sign("https://minds-dev/foo")
->shouldContain("https://minds-dev/foo?jwtsig=");
}
public function it_should_verify_a_uri_was_signed(Config $config)
{
$this->beConstructedWith(null, null, $config);
$config->get('sessions')
->willReturn([
'private_key' => 'priv-key'
]);
$uri = "https://minds-dev/foo?jwtsig=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NzI1NjY0MDAsInVyaSI6Imh0dHBzOlwvXC9taW5kcy1kZXZcL2ZvbyIsInVzZXJfZ3VpZCI6IjEyMyJ9.jqOq0k-E4h1I0PHnc_WkmWqXonRU4yWq_ymoOYoaDvc";
$this->confirm($uri)
->shouldBe(true);
}
public function it_should_not_very_a_wrongly_signed_uri(Config $config)
{
$this->beConstructedWith(null, null, $config);
$config->get('sessions')
->willReturn([
'private_key' => 'priv-key'
]);
$uri = "https://minds-dev/bar?jwtsig=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NzI1NjY0MDAsInVyaSI6Imh0dHBzOlwvXC9taW5kcy1kZXZcL2ZvbyIsInVzZXJfZ3VpZCI6IjEyMyJ9.jqOq0k-E4h1I0PHnc_WkmWqXonRU4yWq_ymoOYoaDvc";
$this->confirm($uri)
->shouldBe(false);
}
}
......@@ -3,6 +3,7 @@
"description": "The core minds social engine",
"require": {
"php": "~7.1",
"ext-redis": "~4.3",
"aws/aws-sdk-php": "^3.38",
"braintree/braintree_php": "3.5.0",
"datastax/php-driver": "1.2.2",
......