...
 
Commits (13)
<?php
namespace Minds\Api;
use Minds\Interfaces;
abstract class AbstractApi implements Interfaces\Api
{
protected $accessControlAllowOrigin = ['*'];
protected $accessControlAllowHeaders = [];
protected $accessControlAllowMethods = [];
protected $defaultResponse = ['status' => 'success'];
const HTTP_CODES = [
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Moved Temporarily',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Time-out',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large',
415 => 'Unsupported Media Type',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'HTTP Version not supported',
];
public function __construct()
{
$this->sendAccessControlHeaders();
}
protected function sendAccessControlHeaders(): void
{
$this->sendAccessControlAllowOrigin();
$this->sendAccessControlAllowHeaders();
$this->sendAccessControlAllowMethods();
}
protected function sendAccessControlAllowOrigin(): void
{
if (!empty($this->accessControlAllowOrigin)) {
header("Access-Control-Allow-Origin: " .
$this->parseAccessControlArray($this->accessControlAllowOrigin), false);
}
}
protected function sendAccessControlAllowHeaders(): void
{
if (!empty($this->accessControlAllowHeaders)) {
header("Access-Control-Allow-Headers: " .
$this->parseAccessControlArray($this->accessControlAllowHeaders), false);
}
}
protected function sendAccessControlAllowMethods(): void
{
if (!empty($this->accessControlAllowMethods)) {
header("Access-Control-Allow-Methods: " .
$this->parseAccessControlArray($this->accessControlAllowMethods), false);
}
}
protected function parseAccessControlArray(array $accessControlArray): string
{
$output = "";
$lastHeader = end($accessControlArray);
foreach ($accessControlArray as $header) {
$output .= $header;
if ($header !== $lastHeader) {
$output .= ",";
}
}
return $output;
}
protected function setResponseCode(int $code = 200): int
{
if (!isset(self::HTTP_CODES[$code])) {
exit('Unknown http status code "' . htmlentities($code) . '"');
}
$text = self::HTTP_CODES[$code];
$protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
header("${protocol} ${code} ${text}");
return $code;
}
protected function sendArrayOfObjects($array, int $code = 200): void
{
$this->send(array_values($array), $code);
}
protected function send($responseArray, int $code = 200, $jsonOptions = 0): void
{
$responseArray = array_merge($this->defaultResponse, $responseArray);
$returnString = json_encode($responseArray, $jsonOptions);
$this->sendJsonString($returnString, $code);
}
protected function sendJsonString(string $jsonString, int $code = 200): void
{
header('Content-Type: application/json');
header('Content-Length:' . strlen($jsonString));
$this->setResponseCode($code);
echo $jsonString;
}
protected function sendInternalServerError(): void
{
$this->sendError(500);
}
protected function sendBadRequest(string $message = null): void
{
$this->sendError(400, $message);
}
protected function sendNotImplemented(): void
{
$this->sendError(501);
}
protected function sendNotModified(): void
{
$this->sendError(304);
}
protected function sendNotAcceptable(string $message = null): void
{
$this->sendError(406, $message);
}
protected function sendUnauthorised(): void
{
$this->sendError(401);
}
protected function sendSuccess(): void
{
$this->send([]);
}
protected function sendError(int $code = 406, string $message = null): void
{
if (is_null($message)) {
$message = self::HTTP_CODES[$code];
}
$this->send($this->buildError($message), $code);
}
protected function buildError(string $message): array
{
return [
'status' => 'error',
'message' => $message
];
}
public function get($pages): void
{
$this->sendNotImplemented();
}
public function post($pages): void
{
$this->sendNotImplemented();
}
public function put($pages): void
{
$this->sendNotImplemented();
}
public function delete($pages): void
{
$this->sendNotImplemented();
}
}
......@@ -171,16 +171,17 @@ class Controller
/**
* Gets the list of publically available commands and filters out the system ones.
* @param array $additionalExcludes Additional methods to exclude from command list (optional)
*/
public function getCommands()
public function getCommands(array $additionalExcludes = [])
{
$excludedMethods = ['__construct', 'help', 'out', 'setArgs', 'setApp', 'getApp', 'getExecCommand', 'getOpt', 'getOpts', 'getAllOpts', 'getCommands', 'displayCommandHelp'];
$excludedMethods = ['__construct', 'help', 'out', 'setArgs', 'setApp', 'getApp', 'getExecCommand', 'getOpt', 'getOpts', 'getAllOpts', 'getCommands', 'displayCommandHelp', 'exec', 'gatekeeper'];
$commands = [];
foreach ((new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$commands[] = $method->getName();
}
return array_diff($commands, $excludedMethods);
return array_diff($commands, $excludedMethods, $additionalExcludes);
}
public function displayCommandHelp()
......
......@@ -154,7 +154,7 @@ class All extends Cli\Controller implements Interfaces\CliControllerInterface
$this->out(sprintf(
"%s -> %s",
date('r', $from),
date('r', $to),
date('r', $to)
));
$this->out("Syncing {$displayType} / {$metric}");
......
......@@ -21,9 +21,14 @@ class Transcode extends Cli\Controller implements Interfaces\CliControllerInterf
public function exec()
{
$transcoder = new Core\Media\Services\FFMpeg;
$transcoder->setKey($this->getOpt('guid'));
$transcoder->setFullHD($this->getOpt('full_hd') ?? false);
$transcoder->onQueue();
$entity = Di::_()->get('EntitiesBuilder')->single($this->getOpt('guid'));
if (!$entity) {
$this->out('Entity not found');
return;
}
$manager = Di::_()->get('Media\Video\Transcoder\Manager');
$manager->createTranscodes($entity);
}
}
<?php
/**
* Minds Admin: Boost Analytics
*
* @version 1
* @author Emi Balbuena
*
*/
namespace Minds\Controllers\api\v1\admin\boosts;
use Minds\Controllers\api\v1\newsfeed\preview;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Helpers;
use Minds\Entities;
use Minds\Interfaces;
use Minds\Api\Factory;
class analytics implements Interfaces\Api, Interfaces\ApiAdminPam
{
/**
* GET
*/
public function get($pages)
{
$response = [];
$type = isset($pages[0]) ? $pages[0] : 'newsfeed';
/** @var Core\Boost\Network\Review $review */
$review = Di::_()->get('Boost\Network\Review');
$review->setType($type);
/** @var Core\Boost\Network\Metrics $metrics */
$metrics = Di::_()->get('Boost\Network\Metrics');
$metrics->setType($type);
$cache = Di::_()->get('Cache');
$cacheKey = "admin:boosts:analytics:{$type}";
if ($cached = $cache->get($cacheKey)) {
return Factory::response($cached);
}
$reviewQueue = $review->getReviewQueueCount();
$backlog = $metrics->getBacklogCount();
$priorityBacklog = $metrics->getPriorityBacklogCount();
$impressions = $metrics->getBacklogImpressionsSum();
$avgApprovalTime = $metrics->getAvgApprovalTime();
$avgImpressions = round($impressions / ($backlog ?: 1));
$timestamp = time();
$response = compact(
'reviewQueue',
'backlog',
'priorityBacklog',
'impressions',
'avgApprovalTime',
'avgImpressions',
'timestamp'
);
$cache->set($cacheKey, $response, 15 * 60 /* 15min cache */);
return Factory::response($response);
}
/**
* POST
*/
public function post($pages)
{
return Factory::response([]);
}
/**
* PUT
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* DELETE
*/
public function delete($pages)
{
return Factory::response([]);
}
}
<?php
/**
* Minds Boost Api endpoint
*
* @version 1
* @author Mark Harding
*
*/
namespace Minds\Controllers\api\v1\boost;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities;
use Minds\Entities\Entity;
use Minds\Helpers\Counters;
use Minds\Interfaces;
use Minds\Core\Boost;
class fetch implements Interfaces\Api
{
......@@ -28,52 +22,56 @@ class fetch implements Interfaces\Api
$user = Core\Session::getLoggedinUser();
if (!$user) {
return Factory::response([
Factory::response([
'status' => 'error',
'message' => 'You must be loggedin to view boosts',
]);
return;
}
if ($user->disabled_boost && $user->isPlus()) {
return Factory::response([]);
Factory::response([]);
return;
}
$limit = isset($_GET['limit']) ? (int) $_GET['limit'] : 2;
$rating = isset($_GET['rating']) ? (int) $_GET['rating'] : $user->getBoostRating();
$platform = isset($_GET['platform']) ? $_GET['platform'] : 'other';
$offset = !empty($_GET['offset']) ? (int) $_GET['offset'] : null;
$quality = 0;
// options specific to newly created users (<=1 hour) and iOS users
if (time() - $user->getTimeCreated() <= 3600) {
$rating = 1; // they can only see safe content
$rating = Boost\Network\Boost::RATING_SAFE;
$quality = 75;
}
if ($platform === 'ios') {
$rating = 1; // they can only see safe content
$rating = Boost\Network\Boost::RATING_SAFE;
$quality = 90;
}
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
/** @var $iterator */
$iterator = new Core\Boost\Network\Iterator();
$iterator->setLimit($limit)
->setRating($rating)
->setQuality($quality)
->setOffset($_GET['offset'])
->setType($pages[0])
->setPriority(true);
->setUserGuid($user->getGUID());
if (isset($_GET['rating']) && $pages[0] == 'newsfeed') {
if (!is_null($offset)) {
$iterator->setOffset($offset);
}
if (isset($_GET['rating']) && $pages[0] == Boost\Network\Boost::TYPE_NEWSFEED) {
$cacher = Core\Data\cache\factory::build('Redis');
$offset = $cacher->get(Core\Session::getLoggedinUser()->guid . ':boost-offset:newsfeed');
$iterator->setOffset($offset);
}
switch ($pages[0]) {
case 'content':
//$iterator->setOffset('');
$iterator->setIncrement(true);
case Boost\Network\Boost::TYPE_CONTENT:
/** @var $entity Entity */
foreach ($iterator as $guid => $entity) {
$response['boosts'][] = array_merge($entity->export(), [
'boosted_guid' => (string) $guid,
......@@ -87,7 +85,7 @@ class fetch implements Interfaces\Api
if (!$response['boosts']) {
$result = Di::_()->get('Trending\Repository')->getList([
'type' => 'images',
'rating' => isset($rating) ? (int) $rating : 1,
'rating' => isset($rating) ? (int) $rating : Boost\Network\Boost::RATING_SAFE,
'limit' => $limit,
]);
......@@ -97,7 +95,7 @@ class fetch implements Interfaces\Api
}
}
break;
case 'newsfeed':
case Boost\Network\Boost::TYPE_NEWSFEED:
foreach ($iterator as $guid => $entity) {
$response['boosts'][] = array_merge($entity->export(), [
'boosted' => true,
......@@ -109,70 +107,25 @@ class fetch implements Interfaces\Api
if (isset($_GET['rating']) && $pages[0] == 'newsfeed') {
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset:newsfeed', $iterator->getOffset(), (3600 / 2));
}
if (!$iterator->list && false) {
$cacher = Core\Data\cache\factory::build('apcu');
$offset = (int) $cacher->get(Core\Session::getLoggedinUser()->guid . ":newsfeed-fallover-boost-offset") ?: 0;
$posts = $this->getSuggestedPosts([
'offset' => $offset,
'limit' => $limit,
'rating' => $rating,
]);
foreach ($posts as $entity) {
$entity->boosted = true;
$response['boosts'][] = array_merge($entity->export(), [ 'boosted' => true ]);
}
if (!$response['boosts'] || count($response['boosts']) < 5) {
$cacher->destroy(Core\Session::getLoggedinUser()->guid . ":newsfeed-fallover-boost-offset");
} else {
$cacher->set(Core\Session::getLoggedinUser()->guid . ":newsfeed-fallover-boost-offset", ((int) $offset) + count($posts));
}
}
break;
}
return Factory::response($response);
Factory::response($response);
}
/**
*/
public function post($pages)
{
/* Not Implemented */
}
/**
* @param array $pages
*/
public function put($pages)
{
$expire = Core\Di\Di::_()->get('Boost\Network\Expire');
$metrics = Core\Di\Di::_()->get('Boost\Network\Metrics');
$boost = Core\Boost\Factory::build($pages[0])->getBoostEntity($pages[1]);
if (!$boost) {
return Factory::response([
'status' => 'error',
'message' => 'Boost not found'
]);
}
$count = $metrics->incrementViews($boost);
if ($count > $boost->getImpressions()) {
$expire->setBoost($boost);
$expire->expire();
}
Counters::increment($boost->getEntity()->guid, "impression");
Counters::increment($boost->getEntity()->owner_guid, "impression");
return Factory::response([]);
/* Not Implemented */
}
/**
*/
public function delete($pages)
{
/* Not Implemented */
}
private function getSuggestedPosts($opts = [])
......
......@@ -181,24 +181,18 @@ class newsfeed implements Interfaces\Api
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator->setPriority(!get_input('offset', ''))
->setType('newsfeed')
$iterator->setType('newsfeed')
->setLimit($limit)
->setOffset($offset)
//->setRating(0)
->setQuality(0)
->setIncrement(false);
->setUserGuid(Core\Session::getLoggedinUserGuid())
->setRating((int) Core\Session::getLoggedinUser()->getBoostRating());
foreach ($iterator as $guid => $boost) {
$boost->boosted = true;
$boost->boosted_guid = (string) $guid;
array_unshift($activity, $boost);
//if (get_input('offset')) {
//bug: sometimes views weren't being calculated on scroll down
//Counters::increment($boost->guid, "impression");
//Counters::increment($boost->owner_guid, "impression");
//}
}
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset:newsfeed', $iterator->getOffset(), (3600 / 2));
} catch (\Exception $e) {
......
......@@ -23,9 +23,8 @@ class views implements Interfaces\Api
switch ($pages[0]) {
case 'boost':
$expire = Di::_()->get('Boost\Network\Expire');
$metrics = Di::_()->get('Boost\Network\Metrics');
$manager = Di::_()->get('Boost\Network\Manager');
$metrics = new Core\Boost\Network\Metrics();
$manager = new Core\Boost\Network\Manager();
$urn = "urn:boost:newsfeed:{$pages[1]}";
......@@ -40,8 +39,7 @@ class views implements Interfaces\Api
$count = $metrics->incrementViews($boost);
if ($count > $boost->getImpressions()) {
$expire->setBoost($boost);
$expire->expire();
$manager->expire($boost);
}
Counters::increment($boost->getEntity()->guid, "impression");
......@@ -71,10 +69,11 @@ class views implements Interfaces\Api
$entity = Entities\Factory::build($pages[1]);
if (!$entity) {
return Factory::response([
Factory::response([
'status' => 'error',
'message' => 'Could not the entity'
]);
return;
}
if ($entity->type === 'activity') {
......@@ -123,16 +122,16 @@ class views implements Interfaces\Api
break;
}
return Factory::response([]);
Factory::response([]);
}
public function put($pages)
{
return Factory::response([]);
Factory::response([]);
}
public function delete($pages)
{
return Factory::response([]);
Factory::response([]);
}
}
This diff is collapsed.
<?php
namespace Minds\Controllers\api\v2\boost;
use Exception;
use Minds\Common\Urn;
use Minds\Core\Boost\Network\Campaign;
use Minds\Core\Boost\Network\Manager;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Interfaces;
use Minds\Api\Factory;
class campaigns implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function get($pages)
{
$limit = $_GET['limit'] ?? 12;
$offset = $_GET['offset'] ?? '';
$urn = $pages[0] ?? null;
if ($limit > 50 || $limit < 0) {
$limit = 12;
}
$guid = '';
if ($urn) {
$limit = 1;
$offset = '';
$urn = new Urn($urn);
$guid = (string) $urn->getNss();
}
/** @var Manager $manager */
$manager = Di::_()->get('Boost\Network\Manager');
$manager->setActor(Session::getLoggedInUser());
$response = $manager->getCampaigns([
'limit' => $limit,
'offset' => $offset,
'guid' => $guid,
]);
Factory::response([
'campaigns' => $response,
'load-next' => $response->getPagingToken(),
]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public function post($pages)
{
$isEditing = false;
$guid = null;
if ($pages[0]) {
$isEditing = true;
$guid = $pages[0];
}
$campaign = new Campaign();
if (!$isEditing) {
$campaign
->setType($_POST['type'] ?? '')
->setEntityGuid($_POST['entity_guid'] ?? [])
->setChecksum($_POST['checksum'] ?? '');
} else {
$campaign->setGuid($guid);
}
$campaign
->setName(trim($_POST['name'] ?? ''))
->setStart((int) ($_POST['start'] ?? 0))
->setEnd((int) ($_POST['end'] ?? 0))
->setBudget((float) ($_POST['budget'] ?? 0));
/** @var Manager $manager */
$manager = Di::_()->get('Boost\Network\Manager');
$manager->setActor(Session::getLoggedInUser());
try {
if (!$isEditing) {
$campaign = $manager->createCampaign($campaign);
} else {
$campaign = $manager->updateCampaign($campaign);
}
Factory::response([
'campaign' => $campaign,
]);
} catch (\Exception $e) {
Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
*/
public function put($pages)
{
Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
*/
public function delete($pages)
{
$guid = $pages[0] ?? null;
if (is_null($guid)) {
Factory::response([
'status' => 'error',
'message' => 'Missing Guid',
]);
return;
}
$campaign = new Campaign();
$campaign->setGuid($guid);
/** @var Manager $manager */
$manager = Di::_()->get('Boost\Network\Manager');
$manager->setActor(Session::getLoggedInUser());
try {
$campaign = $manager->cancelCampaign($campaign);
Factory::response([
'campaign' => $campaign,
]);
} catch (\Exception $e) {
Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
}
<?php
namespace Minds\Controllers\api\v2\boost\campaigns;
use Minds\Api\AbstractApi;
use Minds\Core\Analytics\EntityCentric\BoostViewsDaily;
class analytics extends AbstractApi
{
public function get($pages): void
{
switch ($pages[0]) {
case 'rate':
// Get current boost rate
$avgRate = (new BoostViewsDaily())->lastSevenDays()->getAvg();
$this->send(['rate' => $avgRate]);
break;
case 'days':
$days = (new BoostViewsDaily())->lastSevenDays()->getAll();
$this->send(['days' => $days]);
break;
default:
$this->sendBadRequest();
}
}
}
<?php
namespace Minds\Controllers\api\v2\boost\campaigns;
use Minds\Api\AbstractApi;
use Minds\Common\Urn;
use Minds\Core\Boost\Network\Campaign;
use Minds\Core\Boost\Network\CampaignStats;
class preview extends AbstractApi
{
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public function post($pages): void
{
$urns = $_POST['entity_urns'] ?? [];
if (empty($urns)) {
$this->sendNotAcceptable('No entity_urns');
return;
}
try {
$urn = new Urn($urns[0]);
$entityGuid = (string)$urn->getNss();
} catch (\Exception $e) {
$this->sendNotAcceptable($e->getMessage());
return;
}
$campaign = (new Campaign())
->setType($_POST['type'] ?? '')
->setEntityGuid($entityGuid)
->setStart((int)($_POST['start'] ?? 0))
->setEnd((int)($_POST['end'] ?? 0))
->setBudget((float)($_POST['budget'] ?? 0))
->setImpressions($_POST['impressions']);
$this->send([
'preview' => (new CampaignStats())->setCampaign($campaign)->getAll()
]);
}
}
<?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\Entities\User;
use Minds\Interfaces;
use Minds\Api\Factory;
class feed implements Interfaces\Api
{
/** @var User */
protected $currentUser;
/** @var array */
protected $boosts = [];
protected $next;
protected $type;
protected $limit;
protected $offset;
protected $rating;
protected $platform;
protected $quality = 0;
protected $isBoostFeed;
/**
* Equivalent to HTTP GET method
* @param array $pages
......@@ -30,133 +34,84 @@ class feed implements Interfaces\Api
{
Factory::isLoggedIn();
/** @var Entities\User $currentUser */
$currentUser = Core\Session::getLoggedinUser();
$this->currentUser = Core\Session::getLoggedinUser();
if ($currentUser->disabled_boost && $currentUser->isPlus()) {
return Factory::response([
'boosts' => [],
]);
if (!$this->parseAndValidateParams() || !$this->validBoostUser()) {
$this->sendResponse();
return;
}
// 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;
$isBoostFeed = $_GET['boostfeed'] ?? false;
if ($limit === 0) {
return Factory::response([
'boosts' => [],
]);
} elseif ($limit > 500) {
$limit = 500;
}
$this->type = $pages[0] ?? 'newsfeed';
$cacher = Core\Data\cache\factory::build('Redis');
$offset = $cacher->get(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator');
if ($this->isBoostFeed) {
$this->offset = $_GET['from_timestamp'] ?? 0;
}
if ($isBoostFeed) {
$offset = $_GET['from_timestamp'] ?? 0;
switch ($this->type) {
case 'newsfeed':
$this->getBoosts();
break;
default:
$this->sendError('Unsupported boost type');
return;
}
// Options specific to newly created users (<=1 hour) and iOS users
$this->sendResponse();
}
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;
protected function parseAndValidateParams(): bool
{
$this->limit = abs(intval($_GET['limit'] ?? 2));
$this->offset = $_GET['offset'] ?? 0;
$this->rating = intval($_GET['rating'] ?? $this->currentUser->getBoostRating());
$this->platform = $_GET['platform'] ?? 'other';
$this->isBoostFeed = $_GET['boostfeed'] ?? false;
if ($this->limit === 0) {
return false;
}
//
if ($this->limit > 500) {
$this->limit = 500;
}
$boosts = [];
$next = null;
return true;
}
switch ($type) {
case 'newsfeed':
// Newsfeed boosts
$resolver = new Core\Entities\Resolver();
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator
->setLimit(10)
->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()}"));
$entity = $resolver->single(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
if (!$entity) {
continue; // Duff entity?
}
$feedSyncEntity->setEntity($entity);
$boosts[] = $feedSyncEntity;
}
// $boosts = iterator_to_array($iterator, false);
$next = $iterator->getOffset();
if (isset($boosts[1]) && !$isBoostFeed) { // Always offset to 2rd in list if in rotator
// if (!$offset) {
// $next = $boosts[1]->getTimestamp();
// } else {
// $next = 0;
// }
$len = count($boosts);
if ($boosts[$len -1]) {
$next = $boosts[$len -1]->getTimestamp();
}
} elseif ($isBoostFeed) {
$len = count($boosts);
if ($boosts[$len -1]) {
$next = $boosts[$len -1]->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;
protected function validBoostUser(): bool
{
return !($this->currentUser->disabled_boost && $this->currentUser->isPlus());
}
case 'content':
// TODO: Content boosts
default:
return Factory::response([
'status' => 'error',
'message' => 'Unsupported boost type'
]);
}
protected function sendResponse(): void
{
$boosts = empty($this->boosts) ? [] : Exportable::_($this->boosts);
Factory::response([
'entities' => $boosts,
'load-next' => $this->next,
]);
}
return Factory::response([
'entities' => Exportable::_($boosts),
'load-next' => $next ?: null,
protected function sendError(string $message): void
{
Factory::response([
'status' => 'error',
'message' => $message
]);
}
protected function getBoosts()
{
$feed = new Core\Boost\Feeds\Boost($this->currentUser);
$this->boosts = $feed->setLimit($this->limit)
->setOffset($this->offset)
->setRating($this->rating)
->setPlatform($this->platform)
->setQuality($this->quality)
->get();
$this->next = $feed->getOffset();
}
/**
* Equivalent to HTTP POST method
* @param array $pages
......
<?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 fetch 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;
$sync = (bool) ($_GET['sync'] ?? true);
if ($limit === 0) {
return Factory::response([
'boosts' => [],
]);
} elseif ($sync && $limit > 500) {
$limit = 500;
} elseif (!$sync && $limit > 50) {
$limit = 50;
}
// 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(!$sync);
if ($sync) {
foreach ($iterator as $boost) {
$feedSyncEntity = new Core\Feeds\FeedSyncEntity();
$feedSyncEntity
->setGuid((string) $boost->getGuid())
->setOwnerGuid((string) $boost->getOwnerGuid())
->setUrn(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
$boosts[] = $feedSyncEntity;
}
} else {
$boosts = iterator_to_array($iterator, false);
}
$next = $iterator->getOffset();
break;
case 'content':
// TODO: Content boosts
default:
return Factory::response([
'status' => 'error',
'message' => 'Unsupported boost type'
]);
}
return Factory::response([
'boosts' => 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([]);
}
}
......@@ -54,13 +54,13 @@ class suggested implements Interfaces\Api
$offset = intval($_GET['offset']);
}
$rating = Core\Session::getLoggedinUser()->boost_rating ?: 1;
$rating = Core\Session::getLoggedinUser()->boost_rating ?: Core\Boost\Network\Boost::RATING_SAFE;
if (isset($_GET['rating'])) {
$rating = intval($_GET['rating']);
}
if ($type == 'user') {
$rating = 1;
$rating = Core\Boost\Network\Boost::RATING_SAFE;
}
$hashtag = null;
......
......@@ -58,9 +58,7 @@ class upload implements Interfaces\Api
$lease->setGuid($guid)
->setMediaType($mediaType);
$manager
->setFullHD(Session::getLoggedinUser()->isPro())
->complete($lease);
$manager->complete($lease);
break;
}
return Factory::response([]);
......
<?php
/**
* Minds Video Controller
*
* @author Mark Harding
*/
namespace Minds\Controllers\api\v2\media;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Media\Video\Transcoder\TranscodeStates;
use Minds\Interfaces;
class video implements Interfaces\Api, Interfaces\ApiIgnorePam
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
$videoManager = Di::_()->get('Media\Video\Manager');
$transcodeStates = Di::_()->get('Media\Video\Transcoder\TranscodeStates');
$video = $videoManager->get($pages[0]);
$sources = $videoManager->getSources($video);
$status = $transcodeStates->getStatus($video); // Currently not efficient as no caching
if ($status === TranscodeStates::FAILED && count($sources)) {
$status = TranscodeStates::COMPLETED;
}
Factory::response([
'entity' => $video->export(),
'sources' => Factory::exportable($sources),
'poster' => $video->getIconUrl(),
'transcode_status' => $status,
]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public function post($pages)
{
http_response_code(501);
exit;
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
http_response_code(501);
exit;
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
http_response_code(501);
exit;
}
}
<?php
namespace Minds\Core\Analytics\EntityCentric;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Data\ElasticSearch\Prepared\Search;
use Minds\Core\Di\Di;
use Minds\Helpers\Time;
class BoostViewsDaily
{
/** @var Client */
protected $es;
/** @var array */
protected $dailyViews = [];
/** @var int */
protected $totalViews = 0;
/** @var int */
protected $startDayMs;
/** @var int */
protected $endDayMs;
public function __construct(Client $esClient = null)
{
$this->es = $esClient ?: Di::_()->get('Database\ElasticSearch');
$this->lastSevenDays();
}
protected function clearData(): void
{
$this->dailyViews = [];
$this->totalViews = 0;
}
public function lastSevenDays(): self
{
return $this->dateRange(strtotime('yesterday -1 week'), strtotime('yesterday'));
}
public function dateRange(int $start, int $end): self
{
$this->clearData();
$this->startDayMs = Time::toInterval($start, Time::ONE_DAY) * 1000;
$this->endDayMs = Time::toInterval($end, Time::ONE_DAY) * 1000;
return $this;
}
protected function query(): void
{
if (!empty($this->dailyViews)) {
return;
}
$prepared = new Search();
$prepared->query($this->buildQuery());
$response = $this->es->request($prepared);
if (isset($response['aggregations']['boost_views_total'])) {
$this->totalViews = $response['aggregations']['boost_views_total']['value'];
}
if (isset($response['aggregations']['boost_views_daily']['buckets'])) {
foreach ($response['aggregations']['boost_views_daily']['buckets'] as $bucket) {
$this->dailyViews[$bucket['key']] = $bucket['boost_views']['value'];
}
}
}
protected function buildQuery(): array
{
$must = [
'range' => [
'@timestamp' => [
'gte' => $this->startDayMs,
'lte' => $this->endDayMs,
]
]
];
$query = [
'index' => 'minds-entitycentric-*',
'size' => 0,
'body' => [
'query' => [
'bool' => [
'must' => $must,
],
],
'aggs' => [
'boost_views_total' => [
'sum' => [
'field' => 'views::boosted',
],
],
'boost_views_daily' => [
'date_histogram' => [
'field' => '@timestamp',
'interval' => '1d'
],
'aggs' => [
'boost_views' => [
'sum' => [
'field' => 'views::boosted'
]
]
]
]
]
]
];
return $query;
}
public function getAll(): array
{
$this->query();
return $this->dailyViews;
}
public function getTotal(): int
{
$this->query();
return $this->totalViews;
}
public function getMax(): int
{
$this->query();
return max($this->dailyViews);
}
public function getAvg(): float
{
$this->query();
return !empty($this->dailyViews) ? array_sum($this->dailyViews) / count($this->dailyViews) : 0;
}
}
<?php
/**
* Blockchain Peer Boost Events
*
* @author emi
*/
namespace Minds\Core\Blockchain\Events;
use Minds\Core\Blockchain\Transactions\Repository;
use Minds\Core\Blockchain\Transactions\Transaction;
use Minds\Core\Data;
use Minds\Core\Config;
use Minds\Core\Di\Di;
class BoostEvent implements BlockchainEventInterface
......@@ -23,8 +17,6 @@ class BoostEvent implements BlockchainEventInterface
'blockchain:fail' => 'boostFail',
];
protected $mongo;
/** @var Repository $txRepository */
protected $txRepository;
......@@ -35,12 +27,10 @@ class BoostEvent implements BlockchainEventInterface
protected $config;
public function __construct(
$mongo = null,
$txRepository = null,
$boostRepository = null,
$config = null
) {
$this->mongo = $mongo ?: Data\Client::build('MongoDB');
$this->txRepository = $txRepository ?: Di::_()->get('Blockchain\Transactions\Repository');
$this->boostRepository = $boostRepository ?: Di::_()->get('Boost\Repository');
$this->config = $config ?: Di::_()->get('Config');
......@@ -95,13 +85,8 @@ class BoostEvent implements BlockchainEventInterface
}
$transaction->setFailed(true);
$this->txRepository->update($transaction, [ 'failed' ]);
$boost->setState('failed')
->save();
$this->mongo->remove("boost", [ '_id' => $boost->getId() ]);
$boost->setState('failed')->save();
}
public function boostSent($log, $transaction)
......@@ -126,8 +111,7 @@ class BoostEvent implements BlockchainEventInterface
throw new \Exception("Boost with hash {$tx} already processed. State: " . $boost->getState());
}
$boost->setState('created')
->save();
$boost->setState('created')->save();
echo "{$boost->getGuid()} now marked completed";
}
......
<?php
namespace Minds\Core\Boost;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Entities\Resolver;
use Minds\Entities\User;
use Minds\Core;
use Minds\Helpers\Time;
/**
* Class AbstractFeed
* @package Minds\Core\Boost
*/
abstract class AbstractFeed
{
/** @var Resolver */
protected $resolver;
/** @var User */
protected $currentUser;
/** @var abstractCacher */
protected $cacher;
protected $mockIterator;
/** @var array */
protected $boosts = [];
/** @var int */
protected $offset;
/** @var int */
protected $limit;
/** @var int */
protected $rating;
/** @var string */
protected $platform;
/** @var int */
protected $quality = 0;
/** @var string */
protected $type = 'newsfeed';
/** @var int */
protected $offsetCacheTtl = Time::FIVE_MIN;
/**
* Feed constructor.
* @param User|null $currentUser
* @param Resolver|null $resolver
* @param abstractCacher|null $cacher
*/
public function __construct(
User $currentUser = null,
Resolver $resolver = null,
abstractCacher $cacher = null
) {
$this->currentUser = $currentUser ?: Core\Session::getLoggedinUser();
$this->resolver = $resolver ?: new Resolver();
$this->cacher = $cacher ?: Core\Data\cache\factory::build('Redis');
}
/**
* Set a mock iterator
* @param $mockIterator
* @return $this
*/
public function setMockIterator($mockIterator): self
{
$this->mockIterator = $mockIterator;
return $this;
}
/**
* Set limit
* @param int $limit
* @return $this
*/
public function setLimit(int $limit): self
{
$this->limit = $limit;
return $this;
}
/**
* Get offset
* @return int
*/
public function getOffset(): int
{
return $this->offset;
}
/**
* Set offset
* @param int $offset
* @return $this
*/
public function setOffset(int $offset): self
{
$this->offset = $offset;
return $this;
}
/**
* Set rating
* @param int $rating
* @return $this
*/
public function setRating(int $rating): self
{
$this->rating = $rating;
return $this;
}
/**
* Set platform
* @param string $platform
* @return $this
*/
public function setPlatform(string $platform): self
{
$this->platform = $platform;
return $this;
}
/**
* Set quality
* @param int $quality
* @return $this
*/
public function setQuality(int $quality): self
{
$this->quality = $quality;
return $this;
}
/**
* Set type
* @param string $type
* @return $this
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Get the feed items
* @return Core\Feeds\FeedSyncEntity[]
*/
public function get(): array
{
$this->makeRatingAndQualitySafe();
$this->setOffsetFromCache();
$this->getItems();
$this->setOffsetCache();
return $this->boosts;
}
/**
* Make rating and quality safe
*/
protected function makeRatingAndQualitySafe(): void
{
if ($this->platform === 'ios') {
$this->rating = Core\Boost\Network\Boost::RATING_SAFE;
$this->quality = 90;
} elseif (time() - $this->currentUser->getTimeCreated() <= Time::ONE_HOUR) {
$this->rating = Core\Boost\Network\Boost::RATING_SAFE;
$this->quality = 75;
}
}
/**
* Set the offset from cache
*/
protected function setOffsetFromCache(): void
{
$offsetCache = $this->getOffsetCache();
if (is_int($offsetCache)) {
$this->offset = $offsetCache;
}
}
/**
* Set the offset cache
*/
protected function setOffsetCache(): void
{
$this->cacher->set($this->getOffsetCacheKey(), $this->offset, $this->offsetCacheTtl);
}
/**
* Get the offset cache
* @return mixed
*/
protected function getOffsetCache()
{
return $this->cacher->get($this->getOffsetCacheKey());
}
/**
* Get items
*/
abstract protected function getItems(): void;
/**
* Get offset cache key
* @return string
*/
abstract protected function getOffsetCacheKey(): string;
}
......@@ -3,18 +3,17 @@
namespace Minds\Core\Boost;
use Minds\Core\Boost\Network;
use Minds\Core\Data;
use Minds\Core\Data\Client;
use Minds\Core\Di\Provider;
use Minds\Core\Di;
/**
* Boost Providers
*/
class BoostProvider extends Provider
class BoostProvider extends Di\Provider
{
/**
* Registers providers onto DI
* @return void
* @throws Di\ImmutableException
*/
public function register()
{
......@@ -22,40 +21,22 @@ class BoostProvider extends Provider
return new Repository();
}, ['useFactory' => true]);
$this->di->bind('Boost\Network', function ($di) {
return new Network([], Client::build('MongoDB'), new Data\Call('entities_by_time'));
}, ['useFactory' => true]);
$this->di->bind('Boost\Network\Manager', function ($di) {
return new Network\Manager;
return new Network\Manager();
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Iterator', function ($di) {
return new Network\Iterator();
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Metrics', function ($di) {
return new Network\Metrics(Client::build('MongoDB'));
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Review', function ($di) {
return new Network\Review();
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Expire', function ($di) {
return new Network\Expire();
}, ['useFactory' => false]);
$this->di->bind('Boost\Newsfeed', function ($di) {
return new Newsfeed([], Client::build('MongoDB'), new Data\Call('entities_by_time'));
}, ['useFactory' => true]);
$this->di->bind('Boost\Content', function ($di) {
return new Content([], Client::build('MongoDB'), new Data\Call('entities_by_time'));
}, ['useFactory' => true]);
$this->di->bind('Boost\Peer', function ($di) {
return new Peer();
}, ['useFactory' => true]);
$this->di->bind('Boost\Peer\Metrics', function ($di) {
return new Peer\Metrics(Client::build('MongoDB'));
}, ['useFactory' => false]);
$this->di->bind('Boost\Peer\Review', function ($di) {
return new Peer\Review();
}, ['useFactory' => false]);
$this->di->bind('Boost\Payment', function ($di) {
return new Payment();
}, ['useFactory' => true]);
......
<?php
namespace Minds\Core\Boost;
use Minds\Core;
use Minds\Core\Data;
use Minds\Interfaces;
use Minds\Entities;
use Minds\Helpers;
/**
* Channel boost handler
* @deprecated Please use the Peer controller instead. This is for polyfill support of mobile only
*/
class Channel implements Interfaces\BoostHandlerInterface
{
private $guid;
public function __construct($options)
{
if (isset($options['destination'])) {
if (is_numeric($options['destination'])) {
$this->guid = $options['destination'];
} elseif (is_string($options['destination'])) {
$lookup = new Data\lookup();
$this->guid = key($lookup->get(strtolower($options['destination'])));
}
}
}
/**
* Boost an entity
* @param object/int $entity - the entity to boost
* @param int $points
* @return boolean
*/
public function boost($entity_guid, $points)
{
$entity = Entities\Factory::build($entity_guid);
$destination = Entities\Factory::build($this->guid);
$boost = (new Entities\Boost\Peer())
->setEntity($entity)
->setType('points')
->setBid($points)
->setDestination($destination)
->setOwner(Core\Session::getLoggedInUser())
->setState('created')
->save();
Core\Events\Dispatcher::trigger('notification', 'boost', [
'to'=> [$boost->getDestination()->guid],
'entity' => $boost->getEntity(),
'notification_view' => 'boost_peer_request',
'params' => [
'bid' => $boost->getBid(),
'type' => $boost->getType(),
'title' => $boost->getEntity()->title ?: $boost->getEntity()->message
]
]);
return $boost->getGuid();
/*if (is_object($entity)) {
$guid = $entity->guid;
} else {
$guid = $entity;
}
$db = new Data\Call('entities_by_time');
$result = $db->insert("boost:channel:$this->guid:review", array($guid => $points));
//send a notification of boost offer
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to' => array($this->guid),
'entity' => $guid,
'notification_view' => 'boost_request',
'params' => array('points'=>$points),
'points' => $points
));
//send back to use
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array(Core\Session::getLoggedinUser()->guid),
'entity' => $guid,
'notification_view' => 'boost_submitted_p2p',
'params' => array(
'points' => $points,
'channel' => isset($_POST['destination']) ? $_POST['destination'] : $this->guid
),
'points' => $points
));
return $result;*/
}
/**
* Return boosts for review
* @param int $limit
* @param string $offset
* @return array
*/
public function getReviewQueue($limit, $offset = "")
{
return false;
}
/**
* Accept a boost and do a remind
* @param object/int $entity
* @param int points
* @return boolean
*/
public function accept($entity, $points)
{
/*if (is_object($entity)) {
$guid = $entity->guid;
} else {
$guid = $entity;
}
$db = new Data\Call('entities_by_time');
$embeded = new Entities\Entity($guid);
$embeded = core\Entities::build($embeded); //more accurate, as entity doesn't do this @todo maybe it should in the future
\Minds\Helpers\Counters::increment($guid, 'remind');
$activity = new Entities\Activity();
$activity->p2p_boosted = true;
switch ($embeded->type) {
case 'activity':
if ($embeded->remind_object) {
$activity->setRemind($embeded->remind_object)->save();
} else {
$activity->setRemind($embeded->export())->save();
}
break;
case 'object':
break;
default:
switch ($embeded->subtype) {
case 'blog':
$message = false;
if ($embeded->owner_guid != elgg_get_logged_in_user_guid()) {
$message = 'via <a href="'.$embeded->getOwnerEntity()->getURL() . '">'. $embeded->getOwnerEntity()->name . '</a>';
}
$activity->p2p_boosted = true;
$activity->setTitle($embeded->title)
->setBlurb(elgg_get_excerpt($embeded->description))
->setURL($embeded->getURL())
->setThumbnail($embeded->getIconUrl())
->setMessage($message)
->setFromEntity($embeded)
->save();
break;
}
}
//remove from review
error_log('user_guid is ' . $this->guid);
$db->removeAttributes("boost:channel:$this->guid:review", array($guid));
$db->removeAttributes("boost:channel:all:review", array("$this->guid:$guid"));
$entity = new \Minds\Entities\Activity($guid);
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array($entity->owner_guid),
'entity' => $guid,
'title' => $entity->title,
'notification_view' => 'boost_accepted',
'params' => array('points'=>$points),
'points' => $points
));
return true;
*/
}
/**
* Reject a boost
* @param object/int $entity
* @return boolean
*/
public function reject($entity)
{
///
/// REFUND THE POINTS TO THE USER
///
/* if (is_object($entity)) {
$guid = $entity->guid;
} else {
$guid = $entity;
}
$db = new Data\Call('entities_by_time');
$db->removeAttributes("boost:channel:$this->guid:review", array($guid));
$db->removeAttributes("boost:channel:all:review", array("$this->guid:$guid"));
$entity = new \Minds\Entities\Activity($guid);
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array($entity->owner_guid),
'entity' => $guid,
'title' => $entity->title,
'notification_view' => 'boost_rejected',
));
return true;//need to double check somehow..
*/
}
/**
* Return a boost
* @return array
*/
public function getBoost($offset = "")
{
///
//// THIS DOES NOT APPLY BECAUSE IT'S PRE-AGREED
///
}
public function autoExpire()
{
/*$db = new Data\Call('entities_by_time');
$boosts = $db->getRow("boost:channel:all:review");
foreach ($boosts as $boost => $ts) {
list($destination, $guid) = explode(':', $boost);
if (time() > $ts + (3600 * 48)) {
$this->guid = $destination;
$guids = $this->getReviewQueue(1, $guid);
$points = reset($guids);
if (!$destination) {
echo "$guid issue with destination.. \n";
continue;
}
echo "$guid has expired. refunding ($points) points to $destination \n";
$db->removeAttributes("boost:channel:all:review", array($boost));
$db->removeAttributes("boost:channel:$destination:review", array($guid));
$entity = new \Minds\Entities\Activity($guid);
Helpers\Wallet::createTransaction($entity->owner_guid, $points, $guid, "boost refund");
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array($entity->owner_guid),
'from'=> $destination,
'entity' => $entity,
'title' => $entity->title,
'notification_view' => 'boost_rejected',
));
} else {
echo "$guid is ok... \n";
}
}*/
}
/**
* @param mixed $entity
* @return boolean
*/
public static function validateEntity($entity)
{
return true;
}
}
<?php
/**
* Description
*
* @author emi
*/
namespace Minds\Core\Boost;
use Minds\Entities\Entity;
......
<?php
namespace Minds\Core\Boost\Delegates;
use Minds\Core\Boost\Network\Campaign;
use Minds\Core\Boost\Network\CampaignException;
class ValidateCampaignDatesDelegate
{
/**
* @param Campaign $campaign
* @return Campaign
* @throws CampaignException
*/
public function onCreate(Campaign $campaign)
{
$start = $this->normaliseStartTime($campaign->getStart());
$end = $this->normaliseEndTime($campaign->getEnd());
$this->validateStartTime($start);
$this->validateEndTime($end);
$this->validateStartAgainstEnd($start, $end);
return $campaign->setStart($start)->setEnd($end);
}
/**
* @param Campaign $campaign
* @param Campaign $campaignRef
* @return Campaign
* @throws CampaignException
*/
public function onUpdate(Campaign $campaign, Campaign $campaignRef)
{
// TODO: Ensure date updates from ref are valid against original campaign budget, etc.
$start = $this->normaliseStartTime($campaignRef->getStart());
$end = $this->normaliseEndTime($campaignRef->getEnd());
$this->validateStartTime($start);
$this->validateEndTime($end);
$this->validateStartAgainstEnd($start, $end);
if (!$campaign->hasStarted()) {
$campaign->setStart($start);
}
if (!$campaign->hasFinished() && $campaign->getEnd() < $end) {
$campaign->setEnd($end);
}
return $campaign;
}
private function normaliseStartTime(int $startTime): int
{
return strtotime(date('Y-m-d', $startTime / 1000) . ' 00:00:00') * 1000;
}
private function normaliseEndTime(int $endTime): int
{
return strtotime(date('Y-m-d', $endTime / 1000) . ' 23:59:59') * 1000;
}
private function validateStartTime(int $startTime): void
{
if ($startTime <= 0) {
throw new CampaignException('Campaign should have a start date');
}
$today = strtotime(date('Y-m-d') . ' 00:00:00') * 1000;
if ($startTime < $today) {
throw new CampaignException('Campaign start should not be in the past');
}
}
private function validateEndTime(int $endTime): void
{
if ($endTime <= 0) {
throw new CampaignException('Campaign should have an end date');
}
}
private function validateStartAgainstEnd(int $start, int $end): void
{
if ($start >= $end) {
throw new CampaignException('Campaign end before starting');
}
$startPlusOneMonth = strtotime('+1 month', $start / 1000) * 1000;
if ($startPlusOneMonth < $end) {
throw new CampaignException('Campaign must not be longer than 1 month');
}
}
}
......@@ -4,7 +4,6 @@ namespace Minds\Core\Boost;
use Minds\Core\Email\Campaigns;
use Minds\Core\Events\Dispatcher;
use Minds\Core\Payments;
/**
* Minds Payments Events.
......
<?php
namespace Minds\Core\Boost\Exceptions;
use Throwable;
class EntityAlreadyBoostedException extends \Exception
{
public function __construct($code = 0, Throwable $previous = null)
{
parent::__construct("There's already an ongoing boost for this entity", $code, $previous);
}
}
<?php
namespace Minds\Core\Boost;
use Minds\Core\Data;
use Minds\Interfaces;
/**
* A factory providing handlers boosting items
*/
class Factory
{
public static function getClassHandler($handler)
{
$handler = ucfirst($handler);
$handler = "Minds\\Core\\Boost\\$handler";
if (class_exists($handler)) {
return $handler;
}
throw new \Exception("Handler not found");
}
/**
* Build the handler
* @param string $handler
* @param array $options (optional)
* @return BoostHandlerInterface
*/
public static function build($handler, $options = [], $db = null)
{
if ($handler == 'newsfeed') {
$handler = 'network';
}
$handler = ucfirst($handler);
$handler = "Minds\\Core\\Boost\\$handler";
if (class_exists($handler)) {
$class = new $handler($options, $db);
if ($class instanceof Interfaces\BoostHandlerInterface) {
return $class;
}
}
throw new \Exception("Handler not found");
}
}
<?php
namespace Minds\Core\Boost\Feeds;
use Minds\Common\Urn;
use Minds\Core;
use Minds\Core\Boost\AbstractFeed;
class Boost extends AbstractFeed
{
protected function getItems(): void
{
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = $this->mockIterator ?: Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator
->setLimit($this->limit)
->setOffset($this->offset)
->setQuality($this->quality)
->setType($this->type)
->setHydrate(false);
if (!is_null($this->currentUser)) {
$iterator->setUserGuid($this->currentUser->getGUID());
if (!is_null($this->rating)) {
$iterator->setRating($this->currentUser->getBoostRating());
}
}
/** @var Core\Boost\Network\Boost $boost */
foreach ($iterator as $boost) {
$boostUrn = new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}");
$feedSyncEntity = new Core\Feeds\FeedSyncEntity();
$feedSyncEntity
->setGuid((string)$boost->getGuid())
->setOwnerGuid((string)$boost->getOwnerGuid())
->setTimestamp($boost->getCreatedTimestamp())
->setUrn($boostUrn);
$entity = $this->resolver->single($boostUrn);
if (!$entity) {
continue; // Duff entity?
}
$feedSyncEntity->setEntity($entity);
$this->boosts[] = $feedSyncEntity;
}
$this->offset = $iterator->getOffset();
if (isset($this->boosts[1])) { // Always offset to 2rd in list if in rotator
$len = count($this->boosts);
if ($this->boosts[$len - 1]) {
$this->offset = $this->boosts[$len - 1]->getTimestamp();
}
}
}
protected function getOffsetCacheKey(): string
{
return $this->currentUser->guid . ':boost-offset-rotator';
}
}
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces;
/**
* Channel boost handler
*/
class Channel implements Interfaces\BoostHandlerInterface
{
/**
* @param mixed $entity
* @return boolean
*/
public function validateEntity($entity)
{
return true;
}
}
<?php
namespace Minds\Core\Boost;
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
use Minds\Core;
use Minds\Core\Data;
use Minds\Entities;
use Minds\Helpers;
/**
* Content Boost handler
*/
class Content extends Network implements BoostHandlerInterface
class Content implements BoostHandlerInterface
{
protected $handler = 'content';
......@@ -18,7 +17,7 @@ class Content extends Network implements BoostHandlerInterface
* @param mixed $entity
* @return bool
*/
public static function validateEntity($entity)
public function validateEntity($entity)
{
if (!$entity || !is_object($entity)) {
return false;
......
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
/**
* A factory providing handlers boosting items
*/
class Factory
{
const HANDLER_CHANNEL = 'channel';
const HANDLER_CONTENT = 'content';
const HANDLER_NETWORK = 'network';
const HANDLER_NEWSFEED = 'newsfeed';
const HANDLER_PEER = 'peer';
const HANDLERS = [
self::HANDLER_CHANNEL => Channel::class,
self::HANDLER_CONTENT => Content::class,
self::HANDLER_NETWORK => Network::class,
self::HANDLER_NEWSFEED => Newsfeed::class,
self::HANDLER_PEER => Peer::class
];
/**
* @param string $handler
* @return BoostHandlerInterface
* @throws \Exception
*/
public static function get(string $handler): BoostHandlerInterface
{
if (!isset(self::HANDLERS[$handler]) || !class_exists(self::HANDLERS[$handler])) {
throw new \Exception("Handler not found");
}
$class = self::HANDLERS[$handler];
return new $class;
}
}
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
/**
* Newsfeed Boost handler
*/
class Network implements BoostHandlerInterface
{
/**
* @param mixed $entity
* @return boolean
*/
public function validateEntity($entity)
{
return true;
}
}
<?php
namespace Minds\Core\Boost;
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
use Minds\Core;
use Minds\Core\Data;
use Minds\Entities;
use Minds\Helpers;
/**
* Newsfeed Boost handler
*/
class Newsfeed extends Network implements BoostHandlerInterface
class Newsfeed implements BoostHandlerInterface
{
protected $handler = 'newsfeed';
......@@ -19,7 +17,7 @@ class Newsfeed extends Network implements BoostHandlerInterface
* @param mixed $entity
* @return bool
*/
public static function validateEntity($entity)
public function validateEntity($entity)
{
if (!$entity || !is_object($entity)) {
return false;
......
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces;
/**
* Peer Boost Handler
*/
class Peer implements Interfaces\BoostHandlerInterface
{
/**
* @param mixed $entity
* @return boolean
*/
public function validateEntity($entity)
{
return true;
}
}
This diff is collapsed.
......@@ -4,9 +4,7 @@
*/
namespace Minds\Core\Boost\Network;
use Minds\Common\Repository\Response;
use Minds\Core\Di\Di;
use Minds\Helpers;
use Minds\Core\Data\ElasticSearch\Prepared;
class Analytics
......@@ -56,11 +54,6 @@ class Analytics
'field' => '@reviewed',
],
],
[
'term' => [
'is_campaign' => true
],
],
],
],
],
......
......@@ -4,109 +4,145 @@
*/
namespace Minds\Core\Boost\Network;
use Minds\Entities\Entity;
use Minds\Entities\User;
use Minds\Traits\MagicAttributes;
/**
* Boost Entity
* @package Minds\Core\Boost\Network
* @method Boost setGuid(long $guid)
* @method long getGuid()
* @method string getEntityGuid()
* @method Boost setEntiyGuid()
* @method Boost setEntity()
* @method Boost setGuid(int $guid)
* @method int getGuid()
* @method Boost setEntityGuid(int $entityGuid)
* @method int getEntityGuid()
* @method Boost setEntity($entity)
* @method Entity getEntity()
* @method Boost setBid()
* @method Boost setBidType()
* @method Booot setImpressions()
* @method Boost setBid(double $bid)
* @method double getBid()
* @method Boost setBidType(string $bidType)
* @method string getBidType()
* @method Boost setImpressions(int $impressions)
* @method int getImpressions()
* @method Boost setOwnerGuid()
* @method long getOwnerGuid()
* @method Boost setOwner()
* @method Boost setImpressionsMet(int $impressions)
* @method int getImpressionsMet()
* @method Boost setOwnerGuid(int $ownerGuid)
* @method int getOwnerGuid()
* @method Boost setOwner(User $owner)
* @method User getOwner()
*
* @method Boost setRejectedReason(int $reason)
* @method int getRejectedReason()
* @method Boost setCompletedTimestamp(int $ts)
* @method int getCreatedTimestamp()
* @method Boost setCreatedTimestamp(int $ts)
* @method int getReviewedTimestamp()
* @method Boost setReviewedTimestamp(int $ts)
* @method int getRejectedTimestamp()
* @method Boost setRejectedTimestamp(int $ts)
* @method Boost setCreatedTimestamp(int $ts)
* @method int getRevokedTimestamp()
* @method Boost setRevokedTimestamp(int $ts)
* @method int getCompletedTimestamp()
* @method Boost setCompletedTimestamp(int $ts)
* @method string getTransactionId()
* @method Boost setTransactionId(string $transactionId)
* @method string getType()
* @method Boost setType(string $value)
* @method bool getPriority()
* @method Boost setPriority(bool $priority)
* @method int getRating()
* @method Boost setRating(int $rating)
* @method array getTags()
* @method Boost setTags(array $value)
* @method string getType()
* @method array getNsfw()
* @method Boost setNsfw(array $nsfw)
* @method int getRejectedReason()
* @method Boost setRejectedReason(int $reason)
* @method string getChecksum()
* @method Boost setChecksum(string $checksum)
* @method string getBoostType()
* @method Boost setBoostType(string $boostType)
*/
class Boost
{
use MagicAttributes;
/** @var long $guid */
private $guid;
const STATE_COMPLETED = 'completed';
const STATE_REJECTED = 'rejected';
const STATE_APPROVED = 'approved';
const STATE_REVOKED = 'revoked';
const STATE_CREATED = 'created';
const TYPE_NEWSFEED = 'newsfeed';
const TYPE_CONTENT = 'content';
const RATING_SAFE = 1;
const RATING_OPEN = 2;
const BOOST_TYPE_NOW = 'now';
const BOOST_TYPE_CAMPAIGN = 'campaign';
/** @var long $entityGuid */
private $entityGuid;
/** @var int $guid */
protected $guid;
/** @var int $entityGuid */
protected $entityGuid;
/** @var Entity $entity */
private $entity;
protected $entity;
/** @var double $bid */
private $bid;
protected $bid;
/** @var string $bidType */
private $bidType;
protected $bidType;
/** @var int $impressions */
private $impressions;
protected $impressions;
/** @var int $impressionsMet */
private $impressionsMet;
protected $impressionsMet;
/** @var long $ownerGuid */
private $ownerGuid;
/** @var int $ownerGuid */
protected $ownerGuid;
/** @var User $owner */
private $owner;
protected $owner;
/** @var int $createdTimestamp */
private $createdTimestamp;
protected $createdTimestamp;
/** @var int $reviewedTimestamp */
private $reviewedTimestamp;
protected $reviewedTimestamp;
/** @var int $rejectedTimestamp */
private $rejectedTimestamp;
protected $rejectedTimestamp;
/** @var int $revokedTimestamp */
private $revokedTimestamp;
protected $revokedTimestamp;
/** @var int $completedTimestamp */
private $completedTimestamp;
protected $completedTimestamp;
/** @var string $transactionId */
private $transactionId;
protected $transactionId;
/** @var string $type */
private $type = 'newsfeed';
protected $type = 'newsfeed';
/** @var bool $priority */
private $priority = false;
protected $priority = false;
/** @var int $rating */
private $rating;
protected $rating;
/** @var array $tags */
private $tags = [];
protected $tags = [];
/** @var array $nsfw */
private $nsfw = [];
protected $nsfw = [];
/** @var int $rejectedReason */
private $rejectedReason = -1;
protected $rejectedReason = -1;
/** @var string $checksum */
private $checksum;
protected $checksum;
/** @var string $mongoId */
private $mongoId;
/** @var string $boostType */
protected $boostType = self::BOOST_TYPE_NOW;
/**
* Return the state
......@@ -114,18 +150,18 @@ class Boost
public function getState()
{
if ($this->completedTimestamp) {
return 'completed';
return self::STATE_COMPLETED;
}
if ($this->rejectedTimestamp) {
return 'rejected';
return self::STATE_REJECTED;
}
if ($this->reviewedTimestamp) {
return 'approved';
return self::STATE_APPROVED;
}
if ($this->revokedTimestamp) {
return 'revoked';
return self::STATE_REVOKED;
}
return 'created';
return self::STATE_CREATED;
}
/**
......@@ -165,6 +201,34 @@ class Boost
'checksum' => $this->checksum,
'state' => $this->getState(),
'transaction_id' => $this->transactionId,
'type' => $this->type,
'rejection_reason' => $this->rejectedReason,
'boost_type' => $this->boostType,
];
}
/* TODO - Spec Test this */
/**
* Validate the boost type string
* @param string $type
* @return bool
*/
public static function validType(string $type): bool
{
$validTypes = [
self::TYPE_CONTENT,
self::TYPE_NEWSFEED
];
return in_array($type, $validTypes, true);
}
/**
* Returns true if Boost has an entity object set
* @return bool
*/
public function hasEntity(): bool
{
return !is_null($this->entity);
}
}
<?php
namespace Minds\Core\Boost\Network;
use Minds\Entities\Entity;
use Minds\Entities\User;
use Minds\Traits\MagicAttributes;
/**
* Class Campaign
* @package Minds\Core\Boost\Network
* @method Campaign setGuid(int $guid)
* @method int getGuid()
* @method Campaign setEntityGuid(int $entityGuid)
* @method int getEntityGuid()
* @method Campaign setEntity($entity)
* @method Entity getEntity()
* @method Campaign setBid(double $bid)
* @method double getBid()
* @method Campaign setBidType(string $bidType)
* @method string getBidType()
* @method Campaign setImpressions(int $impressions)
* @method int getImpressions()
* @method Campaign setImpressionsMet(int $impressions)
* @method int getImpressionsMet()
* @method Campaign setOwnerGuid(int $ownerGuid)
* @method int getOwnerGuid()
* @method Campaign setOwner(User $owner)
* @method User getOwner()
* @method int getCreatedTimestamp()
* @method Campaign setCreatedTimestamp(int $ts)
* @method int getReviewedTimestamp()
* @method Campaign setReviewedTimestamp(int $ts)
* @method int getRejectedTimestamp()
* @method Campaign setRejectedTimestamp(int $ts)
* @method int getRevokedTimestamp()
* @method Campaign setRevokedTimestamp(int $ts)
* @method int getCompletedTimestamp()
* @method Campaign setCompletedTimestamp(int $ts)
* @method string getTransactionId()
* @method Campaign setTransactionId(string $transactionId)
* @method string getType()
* @method Campaign setType(string $value)
* @method bool getPriority()
* @method Campaign setPriority(bool $priority)
* @method int getRating()
* @method Campaign setRating(int $rating)
* @method array getTags()
* @method Campaign setTags(array $value)
* @method array getNsfw()
* @method Campaign setNsfw(array $nsfw)
* @method int getRejectedReason()
* @method Campaign setRejectedReason(int $reason)
* @method string getChecksum()
* @method Campaign setChecksum(string $checksum)
* @method string getBoostType()
* @method Campaign setBoostType()
* @method string getName()
* @method Campaign setName(string $name)
* @method int getStart()
* @method Campaign setStart(int $start)
* @method int getEnd()
* @method Campaign setEnd(int $end)
* @method int getBudget()
* @method Campaign setBudget(int $budget)
* @method int getDailyCap()
* @method Campaign setDailyCap(int $dailyCap)
*/
class Campaign extends Boost implements \JsonSerializable
{
use MagicAttributes;
const STATE_PENDING = 'pending';
/** @var string $boostType */
protected $boostType = self::BOOST_TYPE_CAMPAIGN;
/** @var string $name */
protected $name;
/** @var int $start */
protected $start;
/** @var int $end */
protected $end;
/** @var int $budget */
protected $budget;
/** @var int $dailyCap */
protected $dailyCap;
public function export($fields = []): array
{
$boostExport = parent::export($fields);
$campaignExport = [
'name' => $this->name,
'@start' => $this->start,
'@end' => $this->end,
'budget' => $this->budget,
'daily_cap' => $this->dailyCap,
'delivery_status' => $this->getDeliveryStatus(),
'cpm' => $this->cpm()
];
return array_merge($boostExport, $campaignExport);
}
public function getDeliveryStatus(): string
{
if ($this->completedTimestamp) {
return self::STATE_COMPLETED;
} elseif ($this->rejectedTimestamp) {
return self::STATE_REJECTED;
} elseif ($this->revokedTimestamp) {
return self::STATE_REVOKED;
} elseif ($this->reviewedTimestamp) {
return self::STATE_APPROVED;
} elseif ($this->createdTimestamp) {
return self::STATE_CREATED;
}
return self::STATE_PENDING;
}
public function cpm(): float
{
if (!$this->impressions || $this->impressions === 0) {
return 0;
}
return ($this->budget / $this->impressions) * 1000;
}
public function isDelivering(): bool
{
return $this->getDeliveryStatus() === self::STATE_APPROVED;
}
public function shouldBeStarted(int $now): bool
{
$isCreated = $this->getDeliveryStatus() === self::STATE_CREATED;
$started = $now >= $this->getStart() && $now < $this->getEnd();
return $isCreated && $started;
}
public function shouldBeCompleted(int $now): bool
{
$isDelivering = $this->isDelivering();
$ended = $now >= $this->getEnd();
$fulfilled = $this->getImpressionsMet() >= $this->getImpressions();
return $isDelivering && ($ended || $fulfilled);
}
public function hasStarted(): bool
{
return !in_array($this->getDeliveryStatus(), [self::STATE_PENDING, self::STATE_CREATED], true);
}
public function hasFinished(): bool
{
return in_array($this->getDeliveryStatus(), [
self::STATE_COMPLETED,
self::STATE_REJECTED,
self::STATE_REVOKED,
], false);
}
/**
* Specify data which should be serialized to JSON
* @link https://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return $this->export();
}
public function getData(): array
{
$data = $this->export();
/* TODO: Filter data here */
return $data;
}
}
<?php
namespace Minds\Core\Boost\Network;
use Exception;
class CampaignException extends Exception
{
}
<?php
namespace Minds\Core\Boost\Network;
use Minds\Core\Analytics\EntityCentric\BoostViewsDaily;
use Minds\Helpers\Time;
class CampaignStats
{
/** @var Campaign */
protected $campaign;
/** @var BoostViewsDaily */
protected $boostViewsDaily;
public function __construct(BoostViewsDaily $boostViewsDaily = null)
{
$this->boostViewsDaily = $boostViewsDaily ?: new BoostViewsDaily();
}
/**
* @param Campaign $campaign
* @return CampaignStats
*/
public function setCampaign(Campaign $campaign): self
{
$this->campaign = $campaign;
return $this;
}
/**
* @return array
*/
public function getAll(): array
{
/* TODO: Evaluate the campaign targeting parameters against our data */
$campaignDurationDays = ($this->campaign->getEnd() - $this->campaign->getStart()) / Time::ONE_DAY_MS;
$campaignViewsPerDayReq = ($campaignDurationDays > 0) ? $this->campaign->getImpressions() / $campaignDurationDays : 0;
$globalViewsPerDay = $this->boostViewsDaily->getAvg();
return [
'canBeDelivered' => ($campaignViewsPerDayReq < $globalViewsPerDay),
'durationDays' => $campaignDurationDays,
'viewsPerDayRequested' => $campaignViewsPerDayReq,
'globalViewsPerDay' => $globalViewsPerDay
];
}
}
......@@ -9,8 +9,9 @@ use Minds\Common\Repository\Response;
use Minds\Core\Di\Di;
use Minds\Core\Data\Cassandra\Prepared;
use Cassandra;
use Minds\Helpers\Time;
class Repository
class CassandraRepository
{
/** @var Client $client */
private $client;
......@@ -65,27 +66,13 @@ class Repository
$boost = new Boost();
$data = json_decode($row['data'], true);
if (!isset($data['schema']) && $data['schema'] != '04-2019') {
$data['entity_guid'] = $data['entity']['guid'];
$data['owner_guid'] = $data['owner']['guid'];
$data['@created'] = $data['time_created'] * 1000;
$data['@reviewed'] = $data['state'] === 'accepted' ? ($data['last_updated'] * 1000) : null;
$data['@revoked'] = $data['state'] === 'revoked' ? ($data['last_updated'] * 1000) : null;
$data['@rejected'] = $data['state'] === 'rejected' ? ($data['last_updated'] * 1000) : null;
$data['@completed'] = $data['state'] === 'completed' ? ($data['last_updated'] * 1000) : null;
}
if ($data['@created'] < 1055503139000) {
$data['@created'] = $data['@created'] * 1000;
}
$data = $this->updateTimestampsToMsValues($data);
if ($data['is_campaign'] ?? false) {
// Skip campaigns
continue;
if (!isset($data['schema']) && $data['schema'] != '04-2019') {
$data = $this->updateOldSchema($data);
}
$boost->setGuid((string) $row['guid'])
->setMongoId($data['_id'])
->setEntityGuid($data['entity_guid'])
->setOwnerGuid($data['owner_guid'])
->setType($row['type'])
......@@ -102,7 +89,7 @@ class Repository
->setRating($data['rating'])
->setTags($data['tags'])
->setNsfw($data['nsfw'])
->setRejectReason($data['rejection_reason'])
->setRejectedReason($data['rejection_reason'])
->setChecksum($data['checksum']);
$response[] = $boost;
......@@ -116,6 +103,51 @@ class Repository
return $response;
}
/**
* Update any s timestamps to ms values
* @param array $data
* @return array
*/
protected function updateTimestampsToMsValues(array $data): array
{
if (isset($data['last_updated'])) {
if ($data['last_updated'] < Time::HISTORIC_MS_VALUE) {
$data['last_updated'] = Time::sToMs($data['last_updated']);
}
}
if (isset($data['time_created'])) {
if ($data['time_created'] < Time::HISTORIC_MS_VALUE) {
$data['time_created'] = Time::sToMs($data['time_created']);
}
}
if ($data['@created'] < Time::HISTORIC_MS_VALUE) {
$data['@created'] = Time::sToMs($data['@created']);
}
return $data;
}
/**
* Update the old schema
* @param array $data
* @return array
*/
protected function updateOldSchema(array $data): array
{
$data['entity_guid'] = $data['entity']['guid'];
$data['owner_guid'] = $data['owner']['guid'];
$data['@created'] = $data['time_created'];
$data['@reviewed'] = $data['state'] === Boost::STATE_APPROVED ? $data['last_updated'] : null;
$data['@revoked'] = $data['state'] === Boost::STATE_REVOKED ? $data['last_updated'] : null;
$data['@rejected'] = $data['state'] === Boost::STATE_REJECTED ? $data['last_updated'] : null;
$data['@completed'] = $data['state'] === Boost::STATE_COMPLETED ? $data['last_updated'] : null;
$data['schema'] = '04-2019';
return $data;
}
/**
* Return a single boost via urn
* @param string $urn
......@@ -150,48 +182,22 @@ class Repository
}
$template = "INSERT INTO boosts
(type, guid, owner_guid, destination_guid, mongo_id, state, data)
(type, guid, owner_guid, state, data)
VALUES
(?, ?, ?, ?, ?, ?, ?)
(?, ?, ?, ?, ?)
";
$data = [
'guid' => $boost->getGuid(),
'schema' => '04-2019',
'_id' => $boost->getMongoId(), //TODO: remove once on production
'entity_guid' => $boost->getEntityGuid(),
'entity' => $boost->getEntity() ? $boost->getEntity()->export() : null, //TODO: remove once on production
'bid' => $boost->getBid(),
'impressions' => $boost->getImpressions(),
//'bidType' => $boost->getBidType(),
'bidType' => in_array($boost->getBidType(), [ 'onchain', 'offchain' ], true) ? 'tokens' : $boost->getBidType(), //TODO: remove once on production
'owner_guid' => $boost->getOwnerGuid(),
'owner' => $boost->getOwner() ? $boost->getOwner()->export() : null, //TODO: remove once on production
'@created' => $boost->getCreatedTimestamp(),
'time_created' => $boost->getCreatedTimestamp(), //TODO: remove once on production
'last_updated' => time(), //TODO: remove once on production
'@reviewed' => $boost->getReviewedTimestamp(),
'@rejected' => $boost->getRejectedTimestamp(),
'@revoked' => $boost->getRevokedTimestamp(),
'@completed' => $boost->getCompletedTimestamp(),
'transactionId' => $boost->getTransactionId(),
'type' => $boost->getType(),
'handler' => $boost->getType(), //TODO: remove once on production
'state' => $boost->getState(), //TODO: remove once on production
'priority' => $boost->isPriority(),
'rating' => $boost->getRating(),
'tags' => $boost->getTags(),
'nsfw' => $boost->getNsfw(),
'rejection_reason'=> $boost->getRejectReason(),
'checksum' => $boost->getChecksum(),
];
$data = $boost->export();
/* Additional parameters that differ from boost export */
$data['schema'] = '04-2019';
$data['bidType'] = in_array($boost->getBidType(), ['onchain', 'offchain'], true) ? 'tokens' : $boost->getBidType();
$data['handler'] = $boost->getType();
$values = [
(string) $boost->getType(),
new Cassandra\Varint($boost->getGuid()),
new Cassandra\Varint($boost->getOwnerGuid()),
null,
(string) $boost->getMongoId(),
(string) $boost->getState(),
json_encode($data)
];
......
......@@ -5,6 +5,7 @@
namespace Minds\Core\Boost\Network;
use Minds\Common\Repository\Response;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Data\ElasticSearch\Prepared;
use Minds\Core\Util\BigNumber;
......@@ -27,7 +28,7 @@ class ElasticRepository
public function getList($opts = [])
{
$opts = array_merge([
'rating' => 3,
'rating' => Boost::RATING_OPEN,
'token' => 0,
'offset' => null,
'order' => null,
......@@ -50,12 +51,6 @@ class ElasticRepository
],
];
$must_not[] = [
'term' => [
'is_campaign' => true,
]
];
if ($opts['offset']) {
$must[] = [
'range' => [
......@@ -82,7 +77,7 @@ class ElasticRepository
];
}
if ($opts['state'] === 'approved') {
if ($opts['state'] === Manager::OPT_STATEQUERY_APPROVED) {
$must[] = [
'exists' => [
'field' => '@reviewed',
......@@ -95,26 +90,48 @@ class ElasticRepository
],
],
];
}
if ($opts['offchain']) {
$must[] = [
'term' => [
'token_method' => 'offchain',
$must_not[] = [
'exists' => [
'field' => '@completed',
],
];
$must_not[] = [
'exists' => [
'field' => '@rejected',
],
];
$must_not[] = [
'exists' => [
'field' => '@revoked',
],
];
}
if ($opts['state'] === 'review') {
if ($opts['state'] === Manager::OPT_STATEQUERY_REVIEW) {
$must_not[] = [
'exists' => [
'field' => '@reviewed',
],
];
$must_not[] = [
'exists' => [
'field' => '@completed',
],
];
$must_not[] = [
'exists' => [
'field' => '@rejected',
],
];
$must_not[] = [
'exists' => [
'field' => '@revoked',
],
];
$sort = ['@timestamp' => 'asc'];
}
if ($opts['state'] === 'approved' || $opts['state'] === 'review' || $opts['state'] === 'active') {
if ($opts['state'] === Manager::OPT_STATEQUERY_ACTIVE) {
$must_not[] = [
'exists' => [
'field' => '@completed',
......@@ -132,6 +149,14 @@ class ElasticRepository
];
}
if ($opts['offchain']) {
$must[] = [
'term' => [
'token_method' => 'offchain',
],
];
}
$body = [
'query' => [
'bool' => [
......@@ -174,6 +199,9 @@ class ElasticRepository
->setImpressionsMet($doc['_source']['impressions_met'])
->setBid($doc['_source']['bid'])
->setBidType($doc['_source']['bid_type']);
if (isset($doc['_source']['boost_type'])) {
$boost->setBoostType($doc['_source']['boost_type']);
}
$offset = $boost->getCreatedTimestamp();
$response[] = $boost;
}
......@@ -211,7 +239,7 @@ class ElasticRepository
'owner_guid' => $boost->getOwnerGuid(),
'rating' => $boost->getRating(),
'type' => $boost->getType(),
'priority' => (bool) $boost->isPriority(),
'boost_type' => $boost->getBoostType(),
],
'doc_as_upsert' => true,
];
......
<?php
namespace Minds\Core\Boost\Network;
use Minds\Core;
use Minds\Core\Data;
class Expire
{
/** @var Boost $boost */
protected $boost;
/** @var Manager $manager */
protected $manager;
public function __construct($manager = null)
{
$this->manager = $manager ?: new Manager;
}
/**
* Set the boost to expire
* @param Boost $boost
* @return void
*/
public function setBoost($boost)
{
$this->boost = $boost;
}
/**
* Expire a boost from the queue
* @return bool
*/
public function expire()
{
if (!$this->boost) {
return false;
}
if ($this->boost->getState() == 'completed') {
// Re-sync ElasticSearch
$this->manager->resync($this->boost);
// Already completed
return true;
}
$this->boost->setCompletedTimestamp(round(microtime(true) * 1000));
$this->manager->update($this->boost);
Core\Events\Dispatcher::trigger('boost:completed', 'boost', ['boost' => $this->boost]);
Core\Events\Dispatcher::trigger('notification', 'boost', [
'to' => [$this->boost->getOwnerGuid()],
'from' => 100000000000000519,
'entity' => $this->boost->getEntity(),
'notification_view' => 'boost_completed',
'params' => [
'impressions' => $this->boost->getImpressions(),
'title' => $this->boost->getEntity()->title ?: $this->boost->getEntity()->message
],
'impressions' => $this->boost->getImpressions()
]);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -3,16 +3,12 @@
namespace Minds\Core\Boost\Network;
use Minds\Core;
use Minds\Core\Data;
use Minds\Entities\Boost\BoostEntityInterface;
use Minds\Entities\Boost\Network;
use Minds\Helpers\MagicAttributes;
use Minds\Interfaces\BoostReviewInterface;
use Minds\Core\Boost\Delegates;
use Minds\Core\Boost\Delegates\OnchainBadgeDelegate;
use MongoDB\BSON;
class Review implements BoostReviewInterface
{
/** @var Network $boost */
......@@ -209,7 +205,7 @@ class Review implements BoostReviewInterface
{
return $this->manager->getList([
'type' => $this->type,
'state' => 'review',
'state' => Manager::OPT_STATEQUERY_REVIEW,
'limit' => $limit,
'offset' => $offset,
]);
......
This diff is collapsed.
This diff is collapsed.
<?php
namespace Minds\Core\Boost\Peer;
use Minds\Core\Boost\Repository;
use Minds\Core\Data;
use Minds\Core\Di\Di;
class Metrics
{
protected $mongo;
public function __construct(Data\Interfaces\ClientInterface $mongo = null)
{
$this->mongo = $mongo ?: Data\Client::build('MongoDB');
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
/**
* Minds FFMpeg.
* Minds FFMpeg. (This now deprecated in favour of Core/Media/Video/Transcoder/Manager)
*/
namespace Minds\Core\Media\Services;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
namespace Minds\Core\Media\Video\Transcoder\TranscodeExecutors;
class FailedTranscodeException extends \Exception
{
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.