...
 
Commits (28)
......@@ -118,6 +118,10 @@ class Exportable implements \JsonSerializable
$exported['ownerObj']['guid'] = (string) $exported['ownerObj']['guid'];
}
if (isset($exported['urn']) && isset($_SERVER['HTTP_APP_VERSION'])) {
$exported['urn'] = "urn:entity:{$exported['guid']}";
}
foreach ($this->exceptions as $exception) {
$exported[$exception] = $item->{$exception};
}
......
<?php
namespace Minds\Common;
class Access
{
const UNLISTED = 0;
const LOGGED_IN = 1;
const PUBLIC = 2;
const UNKNOWN = 99;
const ACCESS_STRINGS = [
0 => 'Unlisted',
1 => 'LoggedIn',
3 => 'Public'
];
public static function idToString(int $id) : string
{
return self::ACCESS_STRINGS[$id] ?? 'Unknown';
}
}
......@@ -10,8 +10,8 @@ namespace Minds\Controllers\api\v1;
use Minds\Api\Exportable;
use Minds\Api\Factory;
use Minds\Common\Access;
use Minds\Core;
use Minds\Entities\Activity;
use Minds\Helpers;
use Minds\Interfaces;
......@@ -93,7 +93,7 @@ class blog implements Interfaces\Api
$export = [];
foreach ($blogs as $blog) {
if ($blog->getOwnerGuid() != Core\Session::getLoggedInUserGuid() && $blog->getAccessId() != 2) {
if ($blog->getOwnerGuid() != Core\Session::getLoggedInUserGuid() && $blog->getAccessId() != Access::PUBLIC) {
continue;
}
$export[] = $blog;
......@@ -157,13 +157,16 @@ class blog implements Interfaces\Api
$header = new Core\Blogs\Header();
$response = [];
$alreadyPublished = false;
$oldAccessId = Access::UNKNOWN;
$editing = isset($pages[0]) && (is_numeric($pages[0]) || Core\Luid::isValid($pages[0]));
if ($editing) {
$blog = $manager->get($pages[0]);
$originallyPublished = $blog->isPublished();
$alreadyPublished = $blog->isPublished();
$oldAccessId = $alreadyPublished ? $blog->getAccessId() : $blog->getDraftAccessId();
} else {
$blog = new Core\Blogs\Blog();
$blog
......@@ -218,7 +221,8 @@ class blog implements Interfaces\Api
}
if (isset($_POST['published'])) {
$blog->setPublished(!!$_POST['published']);
$published = is_string($_POST['published']) ? json_decode($_POST['published']) : $_POST['published'];
$blog->setPublished($published);
}
if (isset($_POST['monetized'])) {
......@@ -237,9 +241,8 @@ class blog implements Interfaces\Api
}
}
//draft
if (!$_POST['published'] || $_POST['published'] === 'false') {
$blog->setAccessId(0);
if (!$blog->isPublished()) {
$blog->setAccessId(Access::UNLISTED);
$blog->setDraftAccessId($_POST['access_id']);
} elseif ($blog->getTimePublished() == '') {
$blog->setTimePublished(time());
......@@ -282,7 +285,6 @@ class blog implements Interfaces\Api
}
}
if (isset($_POST['mature']) && $_POST['mature']) {
$user = Core\Session::getLoggedInUser();
......@@ -333,19 +335,10 @@ class blog implements Interfaces\Api
if ($saved) {
$createActivity = new Core\Blogs\Delegates\CreateActivity();
if (
!$editing &&
$blog->isPublished() &&
$blog->getAccessId() == 2
) {
$createActivity->save($blog);
} elseif (
$editing &&
!$originallyPublished &&
$blog->isPublished() &&
$blog->getAccessId() == 2
) {
$createActivity->save($blog);
if ($blog->isPublished() && $blog->getAccessId() == Access::PUBLIC) {
if (!$editing || ($editing && !$alreadyPublished) || ($editing && $oldAccessId == Access::UNLISTED)) {
$createActivity->save($blog);
}
}
$response['guid'] = (string) $blog->getGuid();
......
......@@ -52,6 +52,10 @@ class wire implements Interfaces\Api
return Factory::response(['status' => 'error', 'message' => 'You cannot send a wire to yourself!']);
}
if (Core\Security\ACL\Block::_()->isBlocked(Core\Session::getLoggedInUserGuid(), $user->guid)) {
return Factory::response(['status' => 'error', 'message' => 'You cannot send a wire to a user who has blocked you.']);
}
$amount = BigNumber::_($_POST['amount']);
$recurring = isset($_POST['recurring']) ? $_POST['recurring'] : false;
......
<?php
/**
* Boost Fetch
*
* @version 2
* @author emi
*
*/
namespace Minds\Controllers\api\v2\boost;
use Minds\Api\Exportable;
use Minds\Common\Urn;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Helpers;
use Minds\Entities;
use Minds\Interfaces;
use Minds\Api\Factory;
class feed implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws \Exception
*/
public function get($pages)
{
Factory::isLoggedIn();
/** @var Entities\User $currentUser */
$currentUser = Core\Session::getLoggedinUser();
if ($currentUser->disabled_boost && $currentUser->isPlus()) {
return Factory::response([
'boosts' => [],
]);
}
// Parse parameters
$type = $pages[0] ?? 'newsfeed';
$limit = abs(intval($_GET['limit'] ?? 2));
$offset = $_GET['offset'] ?? null;
$rating = intval($_GET['rating'] ?? $currentUser->getBoostRating());
$platform = $_GET['platform'] ?? 'other';
$quality = 0;
if ($limit === 0) {
return Factory::response([
'boosts' => [],
]);
} elseif ($limit > 500) {
$limit = 500;
}
$cacher = Core\Data\cache\factory::build('Redis');
$offset = $cacher->get(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator');
// Options specific to newly created users (<=1 hour) and iOS users
if ($platform === 'ios') {
$rating = 1; // they can only see safe content
$quality = 90;
} elseif (time() - $currentUser->getTimeCreated() <= 3600) {
$rating = 1; // they can only see safe content
$quality = 75;
}
//
$boosts = [];
$next = null;
switch ($type) {
case 'newsfeed':
// Newsfeed boosts
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator
->setLimit($limit)
->setOffset($offset)
->setRating($rating)
->setQuality($quality)
->setType($type)
->setPriority(true)
->setHydrate(false);
foreach ($iterator as $boost) {
$feedSyncEntity = new Core\Feeds\FeedSyncEntity();
$feedSyncEntity
->setGuid((string) $boost->getGuid())
->setOwnerGuid((string) $boost->getOwnerGuid())
->setTimestamp($boost->getCreatedTimestamp())
->setUrn(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
$boosts[] = $feedSyncEntity;
}
// $boosts = iterator_to_array($iterator, false);
$next = $iterator->getOffset();
if (isset($boosts[2])) { // Always offset to 3rd in list
$next = $boosts[2]->getTimestamp();
}
$ttl = 1800; // 30 minutes
if (($next / 1000) < strtotime('48 hours ago')) {
$ttl = 300; // 5 minutes;
}
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator', $next, $ttl);
break;
case 'content':
// TODO: Content boosts
default:
return Factory::response([
'status' => 'error',
'message' => 'Unsupported boost type'
]);
}
return Factory::response([
'entities' => Exportable::_($boosts),
'load-next' => $next ?: null,
]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public function post($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -121,7 +121,7 @@ class feeds implements Interfaces\Api
$asActivities = (bool) ($_GET['as_activities'] ?? true);
$query = $_GET['query'] ?? null;
$query = isset($_GET['query']) ? urldecode($_GET['query']) : null;
$container_guid = $_GET['container_guid'] ?? null;
$custom_type = isset($_GET['custom_type']) && $_GET['custom_type'] ? [$_GET['custom_type']] : null;
......@@ -157,6 +157,7 @@ class feeds implements Interfaces\Api
'sync' => $sync,
'query' => $query ?? null,
'single_owner_threshold' => 36,
'as_activities' => $asActivities,
];
$nsfw = $_GET['nsfw'] ?? '';
......
......@@ -132,6 +132,7 @@ class container implements Interfaces\Api
'from_timestamp' => $fromTimestamp,
'query' => $query,
'single_owner_threshold' => 0,
'pinned_guids' => $type === 'activity' ? array_reverse($container->getPinnedPosts()) : null,
];
if (isset($_GET['nsfw'])) {
......
<?php
/**
* Client based upload
*
* @author Mark Harding
*/
namespace Minds\Controllers\api\v2\media;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Interfaces;
use Minds\Core\Media\ClientUpload\ClientUploadLease;
class upload implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
return Factory::response([]);
}
public function post($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
$manager = Di::_()->get("Media\ClientUpload\Manager");
switch ($pages[0]) {
case 'prepare':
$mediaType = $pages[1] ?? 'not-set';
$lease = $manager->prepare($mediaType);
return Factory::response([
'lease' => $lease->export(),
]);
break;
case 'complete':
$mediaType = $pages[1] ?? 'not-set';
$guid = $pages[2] ?? null;
$lease = new ClientUploadLease();
$lease->setGuid($guid)
->setMediaType($mediaType);
$manager->complete($lease);
break;
}
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -642,6 +642,8 @@ class Blog extends RepositoryEntity
unset($output['deleted']);
}
$output['urn'] = $this->getUrn();
$output = array_merge(
$output,
$this->_eventsDispatcher->trigger('export:extender', 'blog', [ 'entity' => $this ], [])
......
Breaking news. [Bill Ottman](https://www.minds.com/ottman?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator']?>), CEO and co-founder of Minds, is attending The White House Social Media Summit to discuss transparency, privacy, digital rights and civil discourse between the left and right both online and offline.
Use hashtag [#minds](https://www.minds.com/newsfeed/global/top;period=24h;hashtag=minds) everywhere!
Happy July
| |
|:--:|
| [![The White House Invitation](https://cdn-assets.minds.com/emails/whitehouse.jpg){=600x}](https://www.minds.com/newsfeed/global/top;period=24h;hashtag=minds?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator']?>)
| |
......@@ -34,7 +34,15 @@ class EntityGuidResolverDelegate implements ResolverDelegate
*/
public function shouldResolve(Urn $urn)
{
return $urn->getNid() === 'entity' || $urn->getNid() === 'activity' || $urn->getNid() === 'user';
return in_array($urn->getNid(), [
'entity',
'activity',
'image',
'video',
'blog',
'user',
'group',
]);
}
/**
......
......@@ -52,7 +52,12 @@ class AttachmentPaywallDelegate implements ActivityDelegateInterface
if ($attachment->owner_guid == $activity->owner_guid) {
$attachment->access_id = $activity->isPaywall() ? 0 : 2;
$attachment->hidden = $activity->isPaywall();
if ($attachment->getSubtype() === 'blog') {
$attachment->setHidden($activity->isPaywall());
} else {
$attachment->hidden = $activity->isPaywall();
}
if (method_exists($attachment, 'setFlag')) {
$attachment->setFlag('paywall', (bool) $activity->isPaywall());
......
......@@ -25,7 +25,6 @@ use Minds\Traits\MagicAttributes;
class FeedSyncEntity
{
use MagicAttributes;
use Exportable;
/** @var int|string */
protected $guid;
......@@ -39,17 +38,21 @@ class FeedSyncEntity
/** @var string */
protected $urn;
/** @var Entity */
protected $entity;
/**
* Specifies the exportable properties
* @return array<string|\Closure>
* Export to public API
* @return array
*/
public function getExportable()
public function export()
{
return [
'urn',
'guid',
'ownerGuid',
'timestamp',
'guid' => (string) $this->guid,
'owner_guid' => (string) $this->ownerGuid,
'timestamp' => $this->timestamp,
'urn' => $this->urn,
'entity' => $this->entity ? $this->entity->export() : null,
];
}
}
......@@ -60,6 +60,10 @@ class Manager
$response = $this->topFeedsManager->getList($opts);
$response = $response->map(function($entity) {
return $entity->getEntity();
});
if ($opts['moderation_user']) {
foreach ($response->toArray() as $entity) {
$this->moderationCache->store($entity->guid, $opts['moderation_user']);
......
......@@ -22,6 +22,9 @@ class Manager
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Entities */
protected $entities;
private $from;
private $to;
......@@ -33,11 +36,13 @@ class Manager
public function __construct(
$repository = null,
$entitiesBuilder = null,
$entities = null,
$search = null
)
{
$this->repository = $repository ?: new Repository;
$this->entitiesBuilder = $entitiesBuilder ?: new EntitiesBuilder;
$this->entities = $entities ?: new Entities;
$this->search = $search ?: Di::_()->get('Search\Search');
$this->from = strtotime('-7 days') * 1000;
......@@ -89,6 +94,8 @@ class Manager
'nsfw' => null,
'single_owner_threshold' => 36,
'filter_hashtags' => false,
'pinned_guids' => null,
'as_activities' => false,
], $opts);
if (isset($opts['query']) && $opts['query']) {
......@@ -125,10 +132,25 @@ class Manager
++$i; // Update here as we don't want to count skipped
$entityType = $scoredGuid->getType() ?? 'entity';
if (strpos($entityType, 'object:', 0) === 0) {
$entityType = str_replace('object:', '', $entityType);
}
if ($opts['as_activities'] && !in_array($opts['type'], [ 'user', 'group' ])) {
$entityType = 'activity';
}
$urn = implode(':', [
'urn',
$entityType,
$scoredGuid->getGuid(),
]);
$feedSyncEntities[] = (new FeedSyncEntity())
->setGuid((string) $scoredGuid->getGuid())
->setOwnerGuid((string) $ownerGuid)
->setUrn(new Urn($scoredGuid->getGuid()))
->setUrn(new Urn($urn))
->setTimestamp($scoredGuid->getTimestamp());
$scores[(string) $scoredGuid->getGuid()] = $scoredGuid->getScore();
......@@ -142,29 +164,47 @@ class Manager
return min($feedSyncEntity->getTimestamp() ?: INF, $carry);
}, INF) - 1);
if (!$opts['sync']) {
$guids = array_map(function (FeedSyncEntity $feedSyncEntity) {
return $feedSyncEntity->getGuid();
}, $feedSyncEntities);
$entities = $this->entitiesBuilder->get(['guids' => $guids]);
} else {
$entities = $feedSyncEntities;
}
usort($entities, function ($a, $b) use ($scores) {
$aGuid = $a instanceof FeedSyncEntity ? $a->getGuid() : $a->guid;
$bGuid = $b instanceof FeedSyncEntity ? $b->getGuid() : $b->guid;
$aScore = $scores[(string) $aGuid];
$bScore = $scores[(string) $bGuid];
if ($aScore === $bScore) {
return 0;
}
return $aScore < $bScore ? 1 : -1;
});
$hydrateGuids = array_map(function (FeedSyncEntity $feedSyncEntity) {
return $feedSyncEntity->getGuid();
}, array_slice($feedSyncEntities, 0, 12)); // hydrate the first 12
$hydratedEntities = $this->entitiesBuilder->get(['guids' => $hydrateGuids]);
foreach ($hydratedEntities as $entity) {
if ($opts['pinned_guids'] && in_array($entity->getGuid(), $opts['pinned_guids'])) {
$entity->pinned = true;
}
if ($opts['as_activities']) {
$entity = $this->entities->cast($entity);
}
$entities[] = (new FeedSyncEntity)
->setGuid($entity->getGuid())
->setOwnerGuid($entity->getOwnerGuid())
->setUrn($entity->getUrn())
->setEntity($entity);
}
// TODO: Optimize this
foreach (array_slice($feedSyncEntities, 12) as $entity) {
$entities[] = $entity;
}
// TODO: confirm if the following is actually necessary
// especially after the first 12
/*usort($entities, function ($a, $b) use ($scores) {
$aGuid = $a instanceof FeedSyncEntity ? $a->getGuid() : $a->guid;
$bGuid = $b instanceof FeedSyncEntity ? $b->getGuid() : $b->guid;
$aScore = $scores[(string) $aGuid];
$bScore = $scores[(string) $bGuid];
if ($aScore === $bScore) {
return 0;
}
return $aScore < $bScore ? 1 : -1;
});*/
}
$response = new Response($entities);
......@@ -192,7 +232,7 @@ class Manager
$feedSyncEntities[] = (new FeedSyncEntity())
->setGuid((string) $row['guid'])
->setOwnerGuid((string) $row['guid'])
->setUrn(new Urn($row['guid']))
->setUrn("urn:user:{$row['guid']}")
->setTimestamp($row['time_created'] * 1000);
}
}
......@@ -209,12 +249,35 @@ class Manager
$feedSyncEntities[] = (new FeedSyncEntity())
->setGuid($row)
->setOwnerGuid(-1)
->setUrn(new Urn($row))
->setUrn("urn:group:{$row['guid']}")
->setTimestamp(0);
}
}
return $feedSyncEntities;
$entities = [];
$hydrateGuids = array_map(function (FeedSyncEntity $feedSyncEntity) {
return $feedSyncEntity->getGuid();
}, array_slice($feedSyncEntities, 0, 12)); // hydrate the first 12
if ($hydrateGuids) {
$hydratedEntities = $this->entitiesBuilder->get(['guids' => $hydrateGuids]);
foreach ($hydratedEntities as $entity) {
$entities[] = (new FeedSyncEntity)
->setGuid($entity->getGuid())
->setOwnerGuid($entity->getOwnerGuid())
->setUrn($entity->getUrn())
->setEntity($entity);
}
}
// TODO: Optimize this
foreach (array_slice($feedSyncEntities, 12) as $entity) {
$entities[] = $entity;
}
return $entities;
}
public function run($opts = [])
......
......@@ -51,7 +51,8 @@ class Repository
'nsfw' => null,
'from_timestamp' => null,
'exclude_moderated' => false,
'moderation_reservations' => null
'moderation_reservations' => null,
'pinned_guids' => null,
], $opts);
if (!$opts['type']) {
......@@ -338,6 +339,20 @@ class Repository
$response = $this->client->request($prepared);
if ($opts['pinned_guids']) { // Hack the response so we can have pinned posts
foreach ($opts['pinned_guids'] as $pinned_guid) {
array_unshift($response['hits']['hits'], [
'_type' => 'activity',
'_source' => [
'guid' => $pinned_guid,
'owner_guid' => null,
'score' => 0,
'timestamp' => 0,
],
]);
}
}
$guids = [];
foreach ($response['hits']['hits'] as $doc) {
$guid = $doc['_source'][$this->getSourceField($opts['type'])];
......@@ -347,6 +362,7 @@ class Repository
$guids[$guid] = true;
yield (new ScoredGuid())
->setGuid($doc['_source'][$this->getSourceField($opts['type'])])
->setType($doc['_type'])
->setScore($algorithm->fetchScore($doc))
->setOwnerGuid($doc['_source']['owner_guid'])
->setTimestamp($doc['_source']['@timestamp']);
......
......@@ -20,6 +20,8 @@ use Minds\Traits\MagicAttributes;
* @method ScoredGuid setOwnerGuid(int|string $ownerGuid)
* @method int getTimestamp()
* @method ScoredGuid setTimestamp(int $timestamp)
* @method string getType()
* @method ScoredGuid setType(string $type)
*/
class ScoredGuid
{
......@@ -28,6 +30,9 @@ class ScoredGuid
/** @var int|string */
protected $guid;
/** @var string */
protected $type;
/** @var float */
protected $score;
......
<?php
namespace Minds\Core\Media\ClientUpload;
use Minds\Traits\MagicAttributes;
/**
* Class ClientUploadLease
* @package Minds\Core\Media\ClientUpload
* @method string getGuid()
* @method string getMediaType()
* @method string getPresignedUrl()
*/
class ClientUploadLease
{
use MagicAttributes;
/** @var string $guid */
private $guid;
/** @var string $presignedUrl */
private $presignedUrl;
/** @var string $mediaType */
private $mediaType;
/**
* Export to API
* @param array $extra
* @return array
*/
public function export($extra = [])
{
return [
'guid' => (string) $this->getGuid(),
'presigned_url' => $this->getPresignedUrl(),
'media_type' => $this->getMediaType(),
];
}
}
<?php
/**
* Client Upload, direct from browser to storage
*/
namespace Minds\Core\Media\ClientUpload;
use Minds\Core\Media\Services\FFMpeg;
use Minds\Core\GuidBuilder;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Di\Di;
use Minds\Entities\Video;
class Manager
{
/** @var FFMepg */
private $ffmpeg;
/** @var Guid $guid */
private $guid;
/** @var Save $save */
private $save;
public function __construct(
FFMpeg $FFMpeg = null,
GuidBuilder $guid = null,
Save $save = null
)
{
$this->ffmpeg = $FFMpeg ?: Di::_()->get('Media\Services\FFMpeg');
$this->guid = $guid ?: new GuidBuilder();
$this->save = $save ?: new Save();
}
/**
* Prepare an upload, return a lease
* @param $type - the media type
* @return ClientUploadLease
*/
public function prepare($type = 'video')
{
if ($type != 'video') {
throw new \Exception("$type is not currently supported for client based uploads");
}
$guid = $this->guid->build();
$this->ffmpeg->setKey($guid);
$preSignedUrl = $this->ffmpeg->getPresignedUrl();
$lease = new ClientUploadLease();
$lease->setGuid($guid)
->setMediaType($type)
->setPresignedUrl($preSignedUrl);
return $lease;
}
/**
* Complete the client based upload
* @param ClientUploadLease $lease
* @return boolean
*/
public function complete(ClientUploadLease $lease)
{
if ($lease->getMediaType() !== 'video') {
throw new \Exception("{$lease->getMediaType()} is not currently supported for client based uploads");
}
$video = new Video();
$video->set('guid', $lease->getGuid());
$video->set('cinemr_guid', $lease->getGuid());
$video->set('access_id', 0); // Hide until published
// Save the video
$this->save->setEntity($video)->save();
$this->ffmpeg->setKey($lease->getGuid());
// Start the transcoding process
$this->ffmpeg->transcode();
return true;
}
}
......@@ -59,5 +59,17 @@ class MediaProvider extends Provider
$this->di->bind('Media\Imagick\Manager', function ($di) {
return new Imagick\Manager();
}, ['useFactory' => false]);
// ClientUpload
$this->di->bind('Media\ClientUpload\Manager', function ($di) {
return new ClientUpload\Manager();
}, ['useFactory' => true]);
// Services
$this->di->bind('Media\Services\FFMpeg', function ($di) {
return new Services\FFMpeg();
}, ['useFactory' => false]);
}
}
......@@ -78,6 +78,20 @@ class FFMpeg implements ServiceInterface
return $this;
}
/**
* Create a PresignedUr for client based uploads
* @return string
*/
public function getPresignedUrl()
{
$cmd = $this->s3->getCommand('PutObject', [
'Bucket' => 'cinemr',
'Key' => "$this->dir/$this->key/source",
]);
return (string) $this->s3->createPresignedRequest($cmd, '+20 minutes')->getUri();
}
public function saveToFilestore($file)
{
try {
......
......@@ -17,7 +17,7 @@ class Transcode implements Interfaces\QueueRunner
$transcoder = new Core\Media\Services\FFMpeg();
$transcoder->setKey($data->getData()['key']);
$transcoder->onQueue();
});
}, [ 'max_messages' => 1 });
}
}
......@@ -116,12 +116,14 @@ class Client implements QueueClient
return $response;
}
public function receive($callback)
public function receive($callback, $opts = [])
{
$config = $this->config->get('aws');
$maxMessages = isset($config['queue']['max_messages']) ? $config['queue']['max_messages'] : 10;
$waitSeconds = isset($config['queue']['wait_seconds']) ? $config['queue']['wait_seconds'] : 20;
$opts = array_merge([
'max_messages' => $config['queue']['max_messages'] ?? 10,
'wait_seconds' => $config['queue']['wait_seconds'] ?? 20,
], $opts);
// All values should be read here because
// queue calls within $callback can introduce
......@@ -130,8 +132,8 @@ class Client implements QueueClient
$queueUrl = $this->getQueueUrl();
$queueOpts = [
'QueueUrl' => $queueUrl,
'MaxNumberOfMessages' => $maxMessages,
'WaitTimeSeconds' => $waitSeconds,
'MaxNumberOfMessages' => $opts['max_messages'],
'WaitTimeSeconds' => $opts['wait_seconds'],
];
echo 'Queue Options: '.print_r($queueOpts, 1);
......
......@@ -186,7 +186,7 @@ class EntityMapping implements MappingInterface
$map['tags'] = [];
}
$map['tags'] = array_unique(array_merge($map['tags'], array_map('strtolower', $tags)));
$map['tags'] = array_values(array_unique(array_merge($map['tags'], array_map('strtolower', $tags))));
$map['nsfw'] = array_unique($this->entity->getNsfw());
......
......@@ -33,7 +33,7 @@ class GroupMapping extends EntityMapping implements MappingInterface
$map['membership'] = (int) $this->entity->getMembership();
$map['public'] = $map['membership'] == ACCESS_PUBLIC;
$map['tags'] = array_unique(array_merge($map['tags'], $this->entity->getTags()));
$map['tags'] = array_values(array_unique(array_merge($map['tags'], $this->entity->getTags())));
$map['rating'] = $this->entity->getRating();
return $map;
......
......@@ -53,7 +53,7 @@ class UserMapping extends EntityMapping implements MappingInterface
$map['group_membership'] = [];
}
$map['tags'] = array_unique($this->entity->getTags());
$map['tags'] = array_values(array_unique($this->entity->getTags()));
return $map;
}
......
......@@ -218,6 +218,7 @@ class Activity extends Entity
'rating',
'ephemeral',
'hide_impressions',
'pinned',
));
}
......
......@@ -839,8 +839,16 @@ class Group extends NormalizedEntity
$export['is:creator'] = $userIsAdmin || $this->isCreator(Core\Session::getLoggedInUser());
$export['is:awaiting'] = $this->isAwaiting(Core\Session::getLoggedInUser());
$export['urn'] = $this->getUrn();
$export = array_merge($export, Dispatcher::trigger('export:extender', 'group', [ 'entity' => $this ], []));
return $export;
}
public function getUrn()
{
return "urn:group:{$this->guid}";
}
}
......@@ -220,6 +220,7 @@ class Image extends File
$export['width'] = $this->width ?: 0;
$export['height'] = $this->height ?: 0;
$export['gif'] = (bool) $this->gif;
$export['urn'] = $this->getUrn();
if (!Helpers\Flags::shouldDiscloseStatus($this) && isset($export['flags']['spam'])) {
unset($export['flags']['spam']);
......@@ -352,4 +353,9 @@ class Image extends File
{
return $this->boost_rejection_reason;
}
public function getUrn()
{
return "urn:image:{$this->guid}";
}
}
......@@ -260,4 +260,9 @@ class Video extends Object
{
return $this->boost_rejection_reason;
}
public function getUrn()
{
return "urn:video:{$this->getGuid()}";
}
}
<?php
namespace Spec\Minds\Common;
use Minds\Common\Access;
use PhpSpec\ObjectBehavior;
class AccessSpec extends ObjectBehavior
{
public function it_should_return_string_for_an_access_id()
{
$this::idToString(Access::UNLISTED)->shouldBe('Unlisted');
}
public function it_should_return_unknown_for_invalid_access_id()
{
$this::idToString(666)->shouldBe('Unknown');
}
}
......@@ -15,6 +15,7 @@ use Minds\Core\Feeds\Firehose\ModerationCache;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Feeds\FeedSyncEntity;
class ManagerSpec extends ObjectBehavior
{
......@@ -94,7 +95,9 @@ class ManagerSpec extends ObjectBehavior
$this->getList([
'moderation_user' => $this->user,
])->shouldBeLike($response);
])->shouldBeLike($response->map(function($entity) {
return $entity->getEntity();
}));
}
public function it_should_return_results_without_a_user()
......@@ -112,7 +115,9 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn($response);
$this->getList()->shouldBeLike($response);
$this->getList()->shouldBeLike($response->map(function($entity) {
return $entity->getEntity();
}));
}
public function it_should_save_moderated_activites(Entity $activity)
......@@ -197,13 +202,15 @@ class ManagerSpec extends ObjectBehavior
{
$entities = [];
$entity = new Activity();
$entity->guid = 123;
$entities[] = $entity;
$entity = new FeedSyncEntity();
$activity = new Activity();
$activity->guid = 123;
$entities[] = $entity->setEntity($activity);
$entity = new Activity();
$entity->guid = 456;
$entities[] = $entity;
$entity = new FeedSyncEntity();
$activity = new Activity();
$activity->guid = 456;
$entities[] = $entity->setEntity($activity);
return $entities;
}
......
......@@ -65,10 +65,22 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(2);
$entity1->get('guid')
$scoredGuid1->getType()
->shouldBeCalled()
->willReturn('object:image');
$entity1->getGUID()
->shouldBeCalled()
->willReturn(5000);
$entity1->getOwnerGUID()
->shouldBeCalled()
->willReturn(1000);
$entity1->getUrn()
->shouldBeCalled()
->willReturn("urn:image:500");
$scoredGuid2->getGuid()
->shouldBeCalled()
->willReturn(5001);
......@@ -85,10 +97,22 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(1);
$entity2->get('guid')
$scoredGuid2->getType()
->shouldBeCalled()
->willReturn('activity');
$entity2->getGUID()
->shouldBeCalled()
->willReturn(5001);
$entity2->getOwnerGUID()
->shouldBeCalled()
->willReturn(1001);
$entity2->getUrn()
->shouldBeCalled()
->willReturn("urn:activity:5001");
$this->repository->getList(Argument::withEntry('cache_key', 'phpspec'))
->shouldBeCalled()
->willReturn([$scoredGuid1, $scoredGuid2]);
......@@ -97,11 +121,14 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn([$entity1, $entity2]);
$this
$response = $this
->getList([
'cache_key' => 'phpspec',
])
->shouldBeAResponse([$entity2, $entity1]);
]);
$response[0]->getUrn()
->shouldBe('urn:image:500');
$response[1]->getUrn()
->shouldBe('urn:activity:5001');
}
public function it_should_get_list_by_query(
......@@ -127,10 +154,22 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(2);
$entity1->get('guid')
$scoredGuid1->getType()
->shouldBeCalled()
->willReturn('activity');
$entity1->getGUID()
->shouldBeCalled()
->willReturn(5000);
$entity1->getOwnerGUID()
->shouldBeCalled()
->willReturn(1000);
$entity1->getUrn()
->shouldBeCalled()
->willReturn("urn:activity:5000");
$scoredGuid2->getGuid()
->shouldBeCalled()
->willReturn(5001);
......@@ -146,11 +185,23 @@ class ManagerSpec extends ObjectBehavior
$scoredGuid2->getTimestamp()
->shouldBeCalled()
->willReturn(1);
$scoredGuid2->getType()
->shouldBeCalled()
->willReturn('activity');
$entity2->get('guid')
$entity2->getGUID()
->shouldBeCalled()
->willReturn(5001);
$entity2->getOwnerGUID()
->shouldBeCalled()
->willReturn(1001);
$entity2->getUrn()
->shouldBeCalled()
->willReturn("urn:activity:5001");
$this->repository->getList(Argument::withEntry('query', 'activity with hashtags'))
->shouldBeCalled()
->willReturn([$scoredGuid1, $scoredGuid2]);
......@@ -159,11 +210,15 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn([$entity1, $entity2]);
$this
$response = $this
->getList([
'query' => 'Activity with #hashtags',
])
->shouldBeAResponse([$entity2, $entity1]);
]);
$response[0]->getUrn()
->shouldBe('urn:activity:5000');
$response[1]->getUrn()
->shouldBe('urn:activity:5001');
}
function getMatchers()
......
......@@ -56,7 +56,8 @@ class RepositorySpec extends ObjectBehavior
'time_created' => 1,
'@timestamp' => 1000,
],
'_score' => 100
'_score' => 100,
'_type' => 'activity',
],
[
'_source' => [
......@@ -65,7 +66,8 @@ class RepositorySpec extends ObjectBehavior
'time_created' => 1,
'@timestamp' => 1000,
],
'_score' => 50
'_score' => 50,
'_type' => 'activity',
],
]
]
......@@ -104,7 +106,8 @@ class RepositorySpec extends ObjectBehavior
'time_created' => 1,
'@timestamp' => 1000,
],
'_score' => 100
'_score' => 100,
'_type' => 'user',
],
[
'_source' => [
......@@ -113,7 +116,8 @@ class RepositorySpec extends ObjectBehavior
'time_created' => 2,
'@timestamp' => 2000,
],
'_score' => 50
'_score' => 50,
'_type' => 'user',
],
]
]
......@@ -153,7 +157,8 @@ class RepositorySpec extends ObjectBehavior
'@timestamp' => 1000,
'container_guid' => '1',
],
'_score' => 100
'_score' => 100,
'_type' => 'group',
],
[
'_source' => [
......@@ -163,7 +168,8 @@ class RepositorySpec extends ObjectBehavior
'@timestamp' => 2000,
'container_guid' => '2',
],
'_score' => 50
'_score' => 50,
'_type' => 'group',
],
]
]
......
<?php
namespace Spec\Minds\Core\Media\ClientUpload;
use Minds\Core\Media\ClientUpload\Manager;
use Minds\Core\Media\ClientUpload\ClientUploadLease;
use Minds\Core\Media\Services\FFMpeg;
use Minds\Core\GuidBuilder;
use Minds\Core\Entities\Actions\Save;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $ffmpeg;
private $guid;
private $save;
function let(FFMpeg $FFMpeg, GuidBuilder $guid, Save $save)
{
$this->beConstructedWith($FFMpeg, $guid, $save);
$this->ffmpeg = $FFMpeg;
$this->guid = $guid;
$this->save = $save;
}
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
function it_should_return_an_upload_lease()
{
$this->guid->build()
->willReturn(123);
$this->ffmpeg->setKey(123)
->shouldBeCalled();
$this->ffmpeg->getPresignedUrl()
->willReturn('s3-url-here');
$lease = $this->prepare('video');
$lease->getMediaType()
->shouldBe('video');
$lease->getGuid()
->shouldBe(123);
$lease->getPresignedUrl()
->shouldBe('s3-url-here');
}
function it_should_complete_an_upload(ClientUploadLease $lease)
{
$lease->getMediaType()
->willReturn('video');
$lease->getGuid()
->willReturn(456);
$this->save->setEntity(Argument::that(function ($video) {
return $video->guid == 456
&& $video->access_id == 0;
}))
->shouldBeCalled()
->willReturn($this->save);
$this->save->save()
->shouldBeCalled();
$this->ffmpeg->setKey(456)
->shouldBeCalled();
$this->ffmpeg->transcode()
->shouldBeCalled();
$this->complete($lease)
->shouldReturn(true);
}
}
<?php
namespace Spec\Minds\Core\Media\Services;
use Minds\Core\Media\Services\FFMpeg;
use FFMpeg\FFMpeg as FFMpegClient;
use FFMpeg\FFProbe as FFProbeClient;
use Minds\Core\Queue\Interfaces\QueueClient;
use Psr\Http\Message\RequestInterface;
use Aws\S3\S3Client;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class FFMpegSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(FFMpeg::class);
}
function it_should_get_a_presigned_urn(
QueueClient $queue,
FFMpegClient $ffmpeg,
FFProbeClient $ffprobe,
S3Client $s3,
\Aws\CommandInterface $cmd,
RequestInterface $request
)
{
$this->beConstructedWith($queue, $ffmpeg, $ffprobe, $s3);
$s3->getCommand('PutObject', [
'Bucket' => 'cinemr',
'Key' => "/123/source",
])
->shouldBeCalled()
->willReturn($cmd);
$s3->createPresignedRequest(Argument::any(), Argument::any())
->willReturn($request);
$request->getUri()
->willReturn('aws-signed-url');
$this->setKey(123);
$this->getPresignedUrl()
->shouldReturn('aws-signed-url');
}
}
......@@ -1372,7 +1372,7 @@ abstract class ElggEntity extends ElggData implements
*/
public function getExportableValues() {
return array(
'guid',
'guid',
'type',
'subtype',
'time_created',
......@@ -1399,6 +1399,7 @@ abstract class ElggEntity extends ElggData implements
$export = \Minds\Helpers\Export::sanitize($export);
$export['nsfw'] = $this->getNsfw();
$export['nsfw_lock'] = $this->getNsfwLock();
$export['urn']= $this->getUrn();
return $export;
}
......
......@@ -553,3 +553,13 @@ $CONFIG->cinemr_url = 'https://cinemr.s3.amazonaws.com/cinemr_dev/';
$CONFIG->mongodb_servers = ['minds_mongo_1'];
$CONFIG->set('last_tos_update', 1);
$CONFIG->set('gitlab', [
'project_id' => [
'mobile' => '10171280', // project id mobile
'front' => '10152778', // project id front
],
'private_key' => '{{private-key}}'
]);