Skip to content
Commits on Source (32)
......@@ -80,7 +80,7 @@ review:start:
image: minds/helm-eks:latest
script:
- aws eks update-kubeconfig --name=sandbox
- git clone --branch=sandbox-wip https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- git clone --branch=master https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- echo "Upgrading helm for pipeline ${CI_PIPELINE_ID}"
- echo "Setting to image ${CI_REGISTRY_IMAGE}"
- "helm upgrade \
......
......@@ -181,7 +181,10 @@ class Factory
'status' => 'success', //should success be assumed?
], $data);
ob_end_clean();
if (ob_get_level() > 1) {
// New PSR-7 Router has an OB started all the time
ob_end_clean();
}
static::setCORSHeader();
......
......@@ -339,6 +339,18 @@ class Response implements \Iterator, \ArrayAccess, \Countable, \JsonSerializable
return array_reduce($this->data, $callback, $initialValue);
}
/**
* @param callable $callback
* @return Response
*/
public function sort(callable $callback): Response
{
$data = $this->data;
usort($data, $callback);
return new static($data, $this->pagingToken);
}
/**
* Returns the first element of the Response, or null if empty
* @return mixed|null
......
......@@ -9,6 +9,7 @@ use Minds\Entities\User;
use Minds\Core\Email\Campaigns\UserRetention\GoneCold;
use Minds\Core\Email\Campaigns\WhenBoost;
use Minds\Core\Email\Campaigns\WireReceived;
use Minds\Core\Email\Campaigns\WirePromotions;
use Minds\Core\Email\Campaigns\UserRetention\WelcomeComplete;
use Minds\Core\Email\Campaigns\UserRetention\WelcomeIncomplete;
use Minds\Core\Suggestions\Manager;
......@@ -189,6 +190,28 @@ class Email extends Cli\Controller implements Interfaces\CliControllerInterface
}
}
public function testWirePromotion()
{
$userguid = $this->getOpt('guid');
$output = $this->getOpt('output');
$send = $this->getOpt('send');
$user = new User($userguid);
if (!$user->guid) {
$this->out('User not found');
exit;
}
$campaign = (new WirePromotions())
->setUser($user);
$message = $campaign->build();
if ($send) {
$campaign->send();
}
}
public function testWelcomeIncomplete()
{
$userguid = $this->getOpt('guid');
......
......@@ -37,7 +37,7 @@ class EntityCentric extends Cli\Controller implements Interfaces\CliControllerIn
$i = 0;
foreach ($manager->sync() as $record) {
$this->out(++$i);
$this->out(++$i .": {$record->getUrn()}");
}
}
}
......@@ -31,4 +31,31 @@ class Transcode extends Cli\Controller implements Interfaces\CliControllerInterf
$manager = Di::_()->get('Media\Video\Transcoder\Manager');
$manager->createTranscodes($entity);
}
/**
* Retries the transcode on the current thread
* @return void
*/
public function retry()
{
$entity = Di::_()->get('EntitiesBuilder')->single($this->getOpt('guid'));
if (!$entity) {
$this->out('Entity not found');
return;
}
$manager = Di::_()->get('Media\Video\Transcoder\Manager');
$transcode = $manager->getList([
'guid' => $this->getOpt('guid'),
'profileId' => $this->getOpt('profile-id'),
])[0];
if (!$transcode) {
$this->out('Transcode not found');
return;
}
$manager->transcode($transcode);
}
}
......@@ -81,4 +81,21 @@ class User extends Cli\Controller implements Interfaces\CliControllerInterface
$this->out($e);
}
}
public function register_complete()
{
$username = $this->getOpt('username');
if (!$username) {
throw new Exceptions\CliException('Missing username');
}
$user = new Entities\User(strtolower($username));
if (!$user->guid) {
throw new Exceptions\CliException('User does not exist');
}
Core\Events\Dispatcher::trigger('register/complete', 'user', [ 'user' => $user ]);
}
}
......@@ -337,6 +337,15 @@ class blog implements Interfaces\Api
]);
}
// This is a first create blog that should have a banner
// We are trying to stop spam with this check
if ($blog->isPublished() && !$editing && !is_uploaded_file($_FILES['file']['tmp_name'])) {
return Factory::response([
'status' => 'error',
'message' => 'You must upload a banner'
]);
}
try {
if ($editing) {
$saved = $manager->update($blog);
......
......@@ -11,6 +11,7 @@ use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Manager as EmailConfirmation;
use Minds\Core\Queue\Client as Queue;
use Minds\Entities;
use Minds\Interfaces;
......@@ -79,8 +80,14 @@ class settings implements Interfaces\Api
$user->name = trim($_POST['name']);
}
$emailChange = false;
if (isset($_POST['email']) && $_POST['email']) {
$user->setEmail($_POST['email']);
if (strtolower($_POST['email']) !== strtolower($user->getEmail())) {
$emailChange = true;
}
}
if (isset($_POST['boost_rating'])) {
......@@ -146,6 +153,23 @@ class settings implements Interfaces\Api
$response['status'] = 'error';
}
if ($emailChange) {
/** @var EmailConfirmation $emailConfirmation */
$emailConfirmation = Di::_()->get('Email\Confirmation');
$emailConfirmation
->setUser($user);
$reset = $emailConfirmation
->reset();
if ($reset) {
$emailConfirmation
->sendEmail();
} else {
error_log('Cannot reset email confirmation for ' . $user->guid);
}
}
return Factory::response($response);
}
......
<?php
/**
* confirmation
*
* @author edgebal
*/
namespace Minds\Controllers\api\v2\email;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Manager;
use Minds\Core\Session;
use Minds\Entities\User;
use Minds\Interfaces;
class confirmation implements Interfaces\Api
{
/**
* GET method
*/
public function get($pages)
{
return Factory::response([]);
}
/**
* POST method
*/
public function post($pages)
{
Factory::isLoggedIn();
/** @var User $user */
$user = Session::getLoggedinUser();
switch ($pages[0] ?? '') {
case 'resend':
try {
/** @var Manager $emailConfirmation */
$emailConfirmation = Di::_()->get('Email\Confirmation');
$emailConfirmation
->setUser($user)
->sendEmail();
return Factory::response([
'sent' => true
]);
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage()
]);
}
break;
}
return Factory::response([]);
}
/**
* PUT method
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* DELETE method
*/
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -17,7 +17,8 @@ class feeds implements Interfaces\Api
'12h' => '7d',
'24h' => '7d',
'7d' => '30d',
'30d' => '1y'
'30d' => '1y',
'1y' => 'all'
];
/**
......@@ -212,6 +213,7 @@ class feeds implements Interfaces\Api
!$periodFallback ||
$opts['algorithm'] !== 'top' ||
!isset(static::PERIOD_FALLBACK[$opts['period']]) ||
in_array($opts['type'], ['user', 'group'], true) ||
++$i > 2 // Stop at 2nd fallback (i.e. 12h > 7d > 30d)
) {
break;
......@@ -221,6 +223,7 @@ class feeds implements Interfaces\Api
$from = $now - $periodsInSecs[$period];
$opts['from_timestamp'] = $from * 1000;
$opts['period'] = static::PERIOD_FALLBACK[$period];
$opts['limit'] = $limit - $entities->count();
if (!$fallbackAt) {
$fallbackAt = $from;
......
......@@ -28,11 +28,13 @@ class channel implements Interfaces\Api
*/
public function get($pages)
{
$currentUser = Session::getLoggedinUser();
$channel = new User(strtolower($pages[0]));
$channel->fullExport = true; //get counts
$channel->exportCounts = true;
if (!$channel->isPro()) {
if (!$channel->isPro() && $channel->getGuid() !== $currentUser->getGuid()) {
return Factory::response([
'status' => 'error',
'message' => 'E_NOT_PRO'
......
......@@ -11,6 +11,7 @@ use Minds\Common\Repository\Response;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities\Factory as EntitiesFactory;
use Minds\Entities\Group;
use Minds\Entities\User;
use Minds\Interfaces;
......@@ -24,7 +25,6 @@ class content implements Interfaces\Api
$currentUser = Core\Session::getLoggedinUser();
$container_guid = $pages[0] ?? null;
$owner_guid = null;
if (!$container_guid) {
return Factory::response([
......@@ -61,8 +61,6 @@ class content implements Interfaces\Api
break;
case 'groups':
$type = 'group';
$container_guid = null;
$owner_guid = $pages[0];
break;
case 'all':
$type = 'all';
......@@ -125,10 +123,10 @@ class content implements Interfaces\Api
$opts = [
'cache_key' => $currentUser ? $currentUser->guid : null,
'container_guid' => $container_guid,
'owner_guid' => $owner_guid,
'access_id' => $isOwner && !$forcePublic ? [0, 1, 2, $container_guid] : [2, $container_guid],
'custom_type' => null,
'limit' => $limit,
'offset' => $offset,
'type' => $type,
'algorithm' => $algorithm,
'period' => '7d',
......@@ -162,7 +160,11 @@ class content implements Interfaces\Api
try {
$result = $this->getData($entities, $opts, $asActivities, $sync);
if ($opts['algorithm'] !== 'latest' && $result->count() <= static::MIN_COUNT) {
if (
$opts['algorithm'] !== 'latest' &&
$opts['type'] !== 'group' &&
$result->count() <= static::MIN_COUNT
) {
$opts['algorithm'] = 'latest';
$result = $this->getData($entities, $opts, $asActivities, $sync);
}
......@@ -190,20 +192,34 @@ class content implements Interfaces\Api
*/
private function getData($entities, $opts, $asActivities, $sync)
{
switch ($opts['type']) {
case 'group':
/** @var Core\Groups\Ownership $manager */
$manager = Di::_()->get('Groups\Ownership');
/** @var Core\Feeds\Elastic\Manager $manager */
$manager = Di::_()->get('Feeds\Elastic\Manager');
$result = $manager->getList($opts);
$result = $manager
->setUserGuid($opts['container_guid'])
->fetch();
if (!$sync) {
// Remove all unlisted content, if ES document is not in sync, it'll
// also remove pending activities
$result = $result->filter([$entities, 'filter']);
break;
if ($asActivities) {
// Cast to ephemeral Activity entities, if another type
$result = $result->map([$entities, 'cast']);
}
default:
/** @var Core\Feeds\Elastic\Manager $manager */
$manager = Di::_()->get('Feeds\Elastic\Manager');
$result = $manager->getList($opts);
if (!$sync) {
// Remove all unlisted content, if ES document is not in sync, it'll
// also remove pending activities
$result = $result->filter([$entities, 'filter']);
if ($asActivities) {
// Cast to ephemeral Activity entities, if another type
$result = $result->map([$entities, 'cast']);
}
}
break;
}
return $result;
......
......@@ -75,14 +75,14 @@ class settings implements Interfaces\Api
->setUser($user)
->setActor(Session::getLoggedinUser());
if (!$manager->isActive()) {
return Factory::response([
'status' => 'error',
'message' => 'You are not Pro',
]);
}
if (isset($_POST['domain'])) {
// if (!$manager->isActive()) {
// return Factory::response([
// 'status' => 'error',
// 'message' => 'You are not Pro',
// ]);
// }
if (isset($_POST['domain']) && $manager->isActive()) {
/** @var ProDomain $proDomain */
$proDomain = Di::_()->get('Pro\Domain');
......
......@@ -85,12 +85,12 @@ class assets implements Interfaces\Api
->setUser($user)
->setActor(Session::getLoggedinUser());
if (!$manager->isActive()) {
return Factory::response([
'status' => 'error',
'message' => 'You are not Pro',
]);
}
// if (!$manager->isActive()) {
// return Factory::response([
// 'status' => 'error',
// 'message' => 'You are not Pro',
// ]);
// }
/** @var AssetsManager $assetsManager */
$assetsManager = Di::_()->get('Pro\Assets\Manager');
......
......@@ -4,6 +4,7 @@ namespace Minds\Controllers\api\v2\settings;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Manager as EmailConfirmation;
use Minds\Core\Email\EmailSubscription;
use Minds\Entities\User;
use Minds\Interfaces;
......@@ -67,8 +68,27 @@ class emails implements Interfaces\Api
]);
}
$emailChange = strtolower($_POST['email']) !== strtolower($user->getEmail());
$user->setEmail($_POST['email']);
$user->save();
if ($emailChange) {
/** @var EmailConfirmation $emailConfirmation */
$emailConfirmation = Di::_()->get('Email\Confirmation');
$emailConfirmation
->setUser($user);
$reset = $emailConfirmation
->reset();
if ($reset) {
$emailConfirmation
->sendEmail();
} else {
error_log('Cannot reset email confirmation for ' . $user->guid);
}
}
}
if (isset($_POST['notifications'])) {
......
......@@ -104,7 +104,7 @@ class account implements Interfaces\Api
Factory::isLoggedIn();
$response = [];
$vars = Core\Router::getPutVars();
$vars = Core\Router\PrePsr7\Router::getPutVars();
$user = Core\Session::getLoggedInUser();
......
......@@ -77,7 +77,8 @@ class EngagementDashboard implements DashboardInterface
new Metrics\Engagement\VotesUpMetric(),
new Metrics\Engagement\CommentsMetric(),
new Metrics\Engagement\RemindsMetric(),
new Metrics\Engagement\SubscribersMetric()
new Metrics\Engagement\SubscribersMetric(),
new Metrics\Engagement\ReferralsMetric()
)
->build();
......
......@@ -7,6 +7,8 @@ use Minds\Core\Data\ElasticSearch;
use Minds\Core\Analytics\Dashboards\Metrics\AbstractMetric;
use Minds\Core\Analytics\Dashboards\Metrics\MetricSummary;
use Minds\Core\Analytics\Dashboards\Metrics\Visualisations;
use Minds\Core\Analytics\Dashboards\Metrics\HistogramSegment;
use Minds\Core\Analytics\Dashboards\Metrics\HistogramBucket;
abstract class AbstractEngagementMetric extends AbstractMetric
{
......@@ -31,6 +33,9 @@ abstract class AbstractEngagementMetric extends AbstractMetric
/** @var string */
protected $aggField = '';
/** @var HistogramSegment[] */
protected $segments = [];
public function __construct($es = null)
{
$this->es = $es ?? Di::_()->get('Database\ElasticSearch');
......@@ -118,6 +123,15 @@ abstract class AbstractEngagementMetric extends AbstractMetric
*/
public function buildVisualisation(): self
{
// This is for backwards compatability. We should put deprecated notice here soon
if (empty($this->segments)) {
$this->segments = [
(new HistogramSegment)
->setAggField($this->aggField)
->setAggType('sum'),
];
}
$timespan = $this->timespansCollection->getSelected();
$filters = $this->filtersCollection->getSelected();
......@@ -140,10 +154,20 @@ abstract class AbstractEngagementMetric extends AbstractMetric
$must[] = [
'exists' => [
'field' => $this->aggField,
'field' => $this->segments[0]->getAggField(),
],
];
$aggs = [];
foreach ($this->segments as $i => $segment) {
$key = (string) $i + 2;
$aggs[$key] = [
$segment->getAggType() => [
'field' => $segment->getAggField(),
],
];
}
// Do the query
$query = [
'index' => 'minds-entitycentric-*',
......@@ -165,18 +189,12 @@ abstract class AbstractEngagementMetric extends AbstractMetric
'max' => time() * 1000,
],
],
'aggs' => [
'2' => [
'sum' => [
'field' => $this->aggField,
],
],
],
'aggs' => $aggs,
],
],
],
];
// Query elasticsearch
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
......@@ -185,17 +203,22 @@ abstract class AbstractEngagementMetric extends AbstractMetric
$buckets = [];
foreach ($response['aggregations']['1']['buckets'] as $bucket) {
$date = date(Visualisations\ChartVisualisation::DATE_FORMAT, $bucket['key'] / 1000);
$buckets[] = [
'key' => $bucket['key'],
'date' => date('c', $bucket['key'] / 1000),
'value' => $bucket['2']['value']
];
foreach ($this->segments as $i => $segment) {
$key = (string) $i + 2;
$segment->addBucket(
(new HistogramBucket)
->setKey($bucket['key'])
->setTimestampMs($bucket['key'])
->setValue($bucket[$key]['value'])
);
}
}
$this->visualisation = (new Visualisations\ChartVisualisation())
->setXLabel('Date')
->setYLabel('Count')
->setBuckets($buckets);
->setSegments($this->segments);
return $this;
}
......
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics\Engagement;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Analytics\Dashboards\Metrics\AbstractMetric;
use Minds\Core\Analytics\Dashboards\Metrics\MetricSummary;
use Minds\Core\Analytics\Dashboards\Metrics\Visualisations;
class ReferralsActiveMetric extends AbstractEngagementMetric
{
/** @var string */
protected $id = 'referrals_active';
/** @var string */
protected $label = 'Active Referrals';
/** @var string */
protected $description = "Referred users who are active for more than 50% of their first 7 days";
/** @var array */
protected $permissions = [ 'user', 'admin' ];
/** @var string */
protected $aggField = 'referral::active';
}