...
 
Commits (33)
......@@ -9,6 +9,7 @@ stages:
- prepare
- review
- deploy:staging
- qa
- deploy:canary
- deploy:production
......@@ -127,6 +128,17 @@ review:stop:
- master
- test/gitlab-ci
qa:manual:
stage: qa
script:
- echo "Manually approved"
when: manual
only:
refs:
- master
- test/gitlab-ci
allow_failure: false
staging:fpm:
stage: deploy:staging
image: minds/ci:latest
......
......@@ -2,11 +2,12 @@
namespace Minds\Api;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Domain\Security as ProDomainSecurity;
use Minds\Interfaces;
use Minds\Helpers;
use Minds\Core\Security;
use Minds\Core\Session;
use Minds\Core\Di\Di;
/**
* API Factory
......@@ -33,6 +34,7 @@ class Factory
$loop = count($segments);
while ($loop >= 0) {
$offset = $loop -1;
if ($loop < count($segments)) {
$slug_length = strlen($segments[$offset+1].'\\');
$route_length = strlen($route);
......@@ -43,32 +45,51 @@ class Factory
$actual = str_replace('\\', '/', $route);
if (isset(Routes::$routes[$actual])) {
$class_name = Routes::$routes[$actual];
if (class_exists($class_name)) {
$handler = new $class_name();
if (property_exists($handler, 'request')) {
$handler->request = $request;
}
if ($handler instanceof Interfaces\ApiAdminPam) {
self::adminCheck();
}
if (!$handler instanceof Interfaces\ApiIgnorePam) {
self::pamCheck($request, $response);
}
$pages = array_splice($segments, $loop) ?: [];
return $handler->$method($pages);
}
}
//autloaded routes
$class_name = "\\Minds\\Controllers\api\\$route";
if (class_exists($class_name)) {
$handler = new $class_name();
if (property_exists($handler, 'request')) {
$handler->request = $request;
}
if ($handler instanceof Interfaces\ApiAdminPam) {
self::adminCheck();
}
if (!$handler instanceof Interfaces\ApiIgnorePam) {
self::pamCheck($request, $response);
}
$pages = array_splice($segments, $loop) ?: [];
return $handler->$method($pages);
}
--$loop;
}
}
......@@ -79,15 +100,18 @@ class Factory
*/
public static function pamCheck($request, $response)
{
if ($request->getAttribute('oauth_user_id')
|| Security\XSRF::validateRequest()
if (
$request->getAttribute('oauth_user_id') ||
Security\XSRF::validateRequest()
) {
return true;
} else {
//error_log('failed authentication:: OAUTH via API');
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
header('HTTP/1.1 401 Unauthorized', true, 401);
echo json_encode([
'error' => 'Sorry, you are not authenticated',
......@@ -109,8 +133,10 @@ class Factory
} else {
error_log('security: unauthorized access to admin api');
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
header('HTTP/1.1 401 Unauthorized', true, 401);
echo json_encode(['error'=>'You are not an admin', 'code'=>401]);
exit;
......@@ -127,8 +153,10 @@ class Factory
return true;
} else {
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
header('HTTP/1.1 401 Unauthorized', true, 401);
echo json_encode([
'status' => 'error',
......@@ -152,11 +180,26 @@ class Factory
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
echo json_encode($data);
}
/**
* Sets the CORS header, if not already set
*/
public static function setCORSHeader(): void
{
$wasSet = count(array_filter(headers_list(), function ($header) {
return stripos($header, 'Access-Control-Allow-Origin:') === 0;
})) > 0;
if (!$wasSet) {
header("Access-Control-Allow-Origin: *");
}
}
/**
* Returns the exportable form of the entities
* @param array $entities - an array of entities
......
<?php
/**
* Jwt
* @author edgebal
*/
namespace Minds\Common;
use Exception;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Claim;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Hmac\Sha256;
class Jwt
{
/** @var string */
protected $key;
/**
* @param string $key
* @return Jwt
*/
public function setKey(string $key): Jwt
{
$this->key = $key;
return $this;
}
/**
* @param object|array $payload
* @param int|null $exp
* @param int|null $nbf
* @return string
* @throws Exception
*/
public function encode($payload, $exp = null, $nbf = null): string
{
if (!$this->key) {
throw new Exception('Invalid JWT key');
}
$builder = new Builder();
foreach ($payload as $key => $value) {
$builder->set($key, $value);
}
if ($exp !== null) {
$builder->setExpiration($exp);
}
if ($nbf !== null) {
$builder->setNotBefore($nbf);
}
$builder->sign(new Sha256(), $this->key);
return (string) $builder->getToken();
}
/**
* @param string $jwt
* @return array
* @throws Exception
*/
public function decode($jwt): array
{
if (!$this->key) {
throw new Exception('Invalid JWT key');
}
$token = (new Parser())->parse($jwt);
if (!$token->verify(new Sha256(), $this->key)) {
throw new Exception('Invalid JWT');
}
return array_map(function (Claim $claim) {
return $claim->getValue();
}, $token->getClaims());
}
/**
* @return string
*/
public function randomString(): string
{
$bytes = openssl_random_pseudo_bytes(128);
return hash('sha512', $bytes);
}
}
......@@ -328,4 +328,13 @@ class Response implements \Iterator, \ArrayAccess, \Countable, \JsonSerializable
{
return array_reduce($this->data, $callback, $initialValue);
}
/**
* Returns the first element of the Response, or null if empty
* @return mixed|null
*/
public function first()
{
return $this->data[0] ?? null;
}
}
......@@ -16,6 +16,8 @@ use Minds\Entities;
use Minds\Interfaces;
use Minds\Api\Factory;
use Minds\Exceptions\TwoFactorRequired;
use Minds\Core\Queue;
use Minds\Core\Subscriptions;
class authenticate implements Interfaces\Api, Interfaces\ApiIgnorePam
{
......@@ -44,6 +46,7 @@ class authenticate implements Interfaces\Api, Interfaces\ApiIgnorePam
}
$user = new Entities\User(strtolower($_POST['username']));
/** @var Core\Security\LoginAttempts $attempts */
$attempts = Core\Di\Di::_()->get('Security\LoginAttempts');
......@@ -116,8 +119,9 @@ class authenticate implements Interfaces\Api, Interfaces\ApiIgnorePam
public function delete($pages)
{
/** @var Core\Sessions\Manager $sessions */
$sessions = Di::_()->get('Sessions\Manager');
if (isset($pages[0]) && $pages[0] === 'all') {
$sessions->destroy(true);
} else {
......
......@@ -362,7 +362,7 @@ class blog implements Interfaces\Api
}
if ($saved) {
if ($blog->isPublished() && $blog->getAccessId() == Access::PUBLIC) {
if ($blog->isPublished() && in_array($blog->getAccessId(), [Access::PUBLIC, Access::LOGGED_IN], false)) {
if (!$editing || ($editing && !$alreadyPublished) || ($editing && $oldAccessId == Access::UNLISTED)) {
(new CreateActivity())->save($blog);
}
......
......@@ -92,6 +92,19 @@ class channel implements Interfaces\Api
$block = Core\Security\ACL\Block::_();
$response['channel']['blocked'] = $block->isBlocked($user);
if ($user->isPro()) {
/** @var Core\Pro\Manager $manager */
$manager = Core\Di\Di::_()->get('Pro\Manager');
$manager
->setUser($user);
$proSettings = $manager->get();
if ($proSettings) {
$response['channel']['pro_settings'] = $proSettings;
}
}
return Factory::response($response);
}
......
......@@ -514,9 +514,6 @@ class newsfeed implements Interfaces\Api
$activity->indexes = ["activity:$activity->owner_guid:edits"]; //don't re-index on edit
(new Core\Translation\Storage())->purge($activity->guid);
$attachmentPaywallDelegate = new Core\Feeds\Activity\Delegates\AttachmentPaywallDelegate();
$attachmentPaywallDelegate->onUpdate($activity);
if (isset($_POST['time_created']) && ($_POST['time_created'] != $activity->getTimeCreated())) {
try {
$timeCreatedDelegate = new Core\Feeds\Activity\Delegates\TimeCreatedDelegate();
......@@ -532,6 +529,8 @@ class newsfeed implements Interfaces\Api
$save->setEntity($activity)
->save();
(new Core\Entities\PropagateProperties())->from($activity);
$activity->setExportContext(true);
return Factory::response(['guid' => $activity->guid, 'activity' => $activity->export(), 'edited' => true]);
}
......
......@@ -5,14 +5,14 @@
* @version 1
* @author Mark Harding
*/
namespace Minds\Controllers\api\v1;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities;
use Minds\Entities\User;
use Minds\Interfaces;
use Minds\Api\Factory;
use Minds\Helpers;
class register implements Interfaces\Api, Interfaces\ApiIgnorePam
{
......@@ -21,7 +21,7 @@ class register implements Interfaces\Api, Interfaces\ApiIgnorePam
*/
public function get($pages)
{
return Factory::response(['status'=>'error', 'message'=>'GET is not supported for this endpoint']);
return Factory::response(['status' => 'error', 'message' => 'GET is not supported for this endpoint']);
}
/**
......@@ -37,11 +37,11 @@ class register implements Interfaces\Api, Interfaces\ApiIgnorePam
public function post($pages)
{
if (!isset($_POST['username']) || !isset($_POST['password']) || !isset($_POST['username']) || !isset($_POST['email'])) {
return Factory::response(['status'=>'error']);
return Factory::response(['status' => 'error']);
}
if (!$_POST['username'] || !$_POST['password'] || !$_POST['username'] || !$_POST['email']) {
return Factory::response(['status'=>'error', 'message' => "Please fill out all the fields"]);
return Factory::response(['status' => 'error', 'message' => "Please fill out all the fields"]);
}
try {
......@@ -119,11 +119,11 @@ class register implements Interfaces\Api, Interfaces\ApiIgnorePam
$sessions->save(); // Save to db and cookie
$response = [
'guid' => $guid,
'user' => $user->export()
'guid' => $guid,
'user' => $user->export(),
];
} catch (\Exception $e) {
$response = ['status'=>'error', 'message'=>$e->getMessage()];
$response = ['status' => 'error', 'message' => $e->getMessage()];
}
return Factory::response($response);
}
......
......@@ -69,10 +69,6 @@ class settings implements Interfaces\Api
{
Factory::isLoggedIn();
if (!Core\Security\XSRF::validateRequest()) {
//return false;
}
if (Core\Session::getLoggedInUser()->isAdmin() && isset($pages[0])) {
$user = new entities\User($pages[0]);
} else {
......
<?php
/**
* pro
*
* @author edgebal
*/
namespace Minds\Controllers\api\v2\admin;
use Minds\Api\Factory;
use Minds\Core\Pro\Manager;
use Minds\Entities\User as UserEntity;
use Minds\Interfaces;
use Minds\Core\Di\Di;
class pro implements Interfaces\Api, Interfaces\ApiAdminPam
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
return Factory::response([]);
}
/**
* 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)
{
if (!($pages[0] ?? null)) {
return Factory::response([
'status' => 'error',
'message' => 'Emtpy target',
]);
}
$target = new UserEntity($pages[0]);
if (!$target || !$target->guid) {
return Factory::response([
'status' => 'error',
'message' => 'Invalid target',
]);
}
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager
->setUser($target);
$success = $manager->enable(time() + (365 * 86400));
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => 'Error disabling Pro',
]);
}
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
if (!($pages[0] ?? null)) {
return Factory::response([
'status' => 'error',
'message' => 'Emtpy target',
]);
}
$target = new UserEntity($pages[0]);
if (!$target || !$target->guid) {
return Factory::response([
'status' => 'error',
'message' => 'Invalid target',
]);
}
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager
->setUser($target);
$success = $manager->disable();
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => 'Error disabling Pro',
]);
}
return Factory::response([]);
}
}
......@@ -67,46 +67,49 @@ class views implements Interfaces\Api
]);
break;
case 'activity':
$activity = new Entities\Activity($pages[1]);
case 'entity':
$entity = Entities\Factory::build($pages[1]);
if (!$activity->guid) {
if (!$entity) {
return Factory::response([
'status' => 'error',
'message' => 'Could not find activity post'
'message' => 'Could not the entity'
]);
}
try {
Core\Analytics\App::_()
if ($entity->type === 'activity') {
try {
Core\Analytics\App::_()
->setMetric('impression')
->setKey($activity->guid)
->setKey($entity->guid)
->increment();
if ($activity->remind_object) {
Core\Analytics\App::_()
if ($entity->remind_object) {
Core\Analytics\App::_()
->setMetric('impression')
->setKey($activity->remind_object['guid'])
->setKey($entity->remind_object['guid'])
->increment();
Core\Analytics\App::_()
Core\Analytics\App::_()
->setMetric('impression')
->setKey($activity->remind_object['owner_guid'])
->setKey($entity->remind_object['owner_guid'])
->increment();
}
}
Core\Analytics\User::_()
Core\Analytics\User::_()
->setMetric('impression')
->setKey($activity->owner_guid)
->setKey($entity->owner_guid)
->increment();
} catch (\Exception $e) {
error_log($e->getMessage());
} catch (\Exception $e) {
error_log($e->getMessage());
}
}
try {
$viewsManager->record(
(new Core\Analytics\Views\View())
->setEntityUrn($activity->getUrn())
->setOwnerGuid((string) $activity->getOwnerGuid())
->setEntityUrn($entity->getUrn())
->setOwnerGuid((string) $entity->getOwnerGuid())
->setClientMeta($_POST['client_meta'] ?? [])
);
} catch (\Exception $e) {
......@@ -114,7 +117,7 @@ class views implements Interfaces\Api
}
Di::_()->get('Referrals\Cookie')
->setEntity($activity)
->setEntity($entity)
->create();
break;
......
......@@ -124,10 +124,14 @@ class feed implements Interfaces\Api
// $next = 0;
// }
$len = count($boosts);
$next = $boosts[$len -1]->getTimestamp();
if ($boosts[$len -1]) {
$next = $boosts[$len -1]->getTimestamp();
}
} elseif ($isBoostFeed) {
$len = count($boosts);
$next = $boosts[$len -1]->getTimestamp();
if ($boosts[$len -1]) {
$next = $boosts[$len -1]->getTimestamp();
}
}
// $ttl = 1800; // 30 minutes
......
<?php
/**
* pro
* @author edgebal
*/
namespace Minds\Controllers\api\v2;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Manager;
use Minds\Core\Session;
use Minds\Interfaces;
use Minds\Api\Factory;
class pro implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function get($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager
->setUser(Session::getLoggedinUser());
return Factory::response([
'isActive' => $manager->isActive(),
]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed
*/
public function post($pages)
{
return Factory::response([
'status' => 'error',
'message' => 'Minds Pro is not yet publicly available.',
]);
// /** @var Manager $manager */
// $manager = Di::_()->get('Pro\Manager');
// $manager
// ->setUser(Session::getLoggedinUser());
//
// // TODO: Send and process payment data
// $success = $manager->enable(time() + (365 * 86400));
//
// if (!$success) {
// return Factory::response([
// 'status' => 'error',
// 'message' => 'Error activating Pro',
// ]);
// }
//
// return Factory::response([
// 'isActive' => $manager->isActive(),
// 'settings' => $manager->get(),
// ]);
}
/**
* 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
*/
public function delete($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager
->setUser(Session::getLoggedinUser());
$success = $manager->disable();
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => 'Error disabling Pro',
]);
}
return Factory::response([]);
}
}
<?php
/**
* channel
* @author edgebal
*/
namespace Minds\Controllers\api\v2\pro;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Channel\Manager as ChannelManager;
use Minds\Core\Pro\Manager;
use Minds\Core\Session;
use Minds\Entities\User;
use Minds\Helpers\Campaigns\Referrals;
use Minds\Interfaces;
use Minds\Api\Factory;
class channel implements Interfaces\Api
{
public $request;
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function get($pages)
{
$currentUser = Session::getLoggedinUser();
$channel = new User($pages[0]);
$channel->fullExport = true; //get counts
$channel->exportCounts = true;
if (!$channel->isPro()) {
return Factory::response([
'status' => 'error',
'message' => 'E_NOT_PRO'
]);
}
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager->setUser($channel);
/** @var ChannelManager $manager */
$channelManager = Di::_()->get('Pro\Channel\Manager');
$channelManager->setUser($channel);
switch ($pages[1] ?? '') {
case 'content':
return Factory::response([
'content' => $channelManager->getAllCategoriesContent(),
]);
default:
$proSettings = $manager->get();
$exportedChannel = $channel->export();
$exportedChannel['pro_settings'] = $proSettings;
$origin = strtolower($this->request->getServerParams()['HTTP_X_MINDS_ORIGIN'] ?? '');
$domain = strtolower($proSettings->getDomain());
if ($domain === $origin) {
Referrals::register($channel->username);
}
return Factory::response([
'channel' => $exportedChannel,
'me' => $currentUser ? $currentUser->export() : 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([]);
}
}
<?php
/**
* @author: eiennohi.
*/
namespace Minds\Controllers\api\v2\pro;
use Minds\Api\Exportable;
use Minds\Api\Factory;
use Minds\Common\Repository\Response;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities\Factory as EntitiesFactory;
use Minds\Entities\User;
use Minds\Interfaces;
class content implements Interfaces\Api
{
const MIN_COUNT = 6;
public function get($pages)
{
/** @var User $currentUser */
$currentUser = Core\Session::getLoggedinUser();
$container_guid = $pages[0] ?? null;
$owner_guid = null;
if (!$container_guid) {
return Factory::response([
'status' => 'error',
'message' => 'Invalid container',
]);
}
$container = EntitiesFactory::build($container_guid);
if (!$container || !Core\Security\ACL::_()->read($container, $currentUser)) {
return Factory::response([
'status' => 'error',
'message' => 'Forbidden',
]);
}
$type = '';
switch ($pages[1]) {
case 'activities':
$type = 'activity';
break;
case 'images':
$type = 'object:image';
break;
case 'videos':
$type = 'object:video';
break;
case 'blogs':
$type = 'object:blog';
break;
case 'groups':
$type = 'group';
$container_guid = null;
$owner_guid = $pages[0];
break;
case 'all':
$type = 'all';
break;
}
$hardLimit = 5000;
$offset = 0;
if (isset($_GET['offset'])) {
$offset = intval($_GET['offset']);
}
$limit = 12;
if (isset($_GET['limit'])) {
$limit = abs(intval($_GET['limit']));
}
if (($offset + $limit) > $hardLimit) {
$limit = $hardLimit - $offset;
}
if ($limit <= 0) {
return Factory::response([
'status' => 'success',
'entities' => [],
'load-next' => $hardLimit,
'overflow' => true,
]);
}
$sync = (bool) ($_GET['sync'] ?? false);
$fromTimestamp = $_GET['from_timestamp'] ?? 0;
$exclude = explode(',', $_GET['exclude'] ?? '');
$forcePublic = (bool) ($_GET['force_public'] ?? false);
$asActivities = (bool) ($_GET['as_activities'] ?? true);
$query = null;
if (isset($_GET['query'])) {
$query = $_GET['query'];
}
/** @var Core\Feeds\Top\Entities $entities */
$entities = new Core\Feeds\Top\Entities();
$entities->setActor($currentUser);
$isOwner = false;
if ($currentUser) {
$entities->setActor($currentUser);
$isOwner = $currentUser->guid == $container_guid;
}
$opts = [
'cache_key' => $currentUser ? $currentUser->guid : null,
'container_guid' => $container_guid,
'owner_guid' => $owner_guid,
'access_id' => $isOwner && !$forcePublic ? [0, 1, 2, $container_guid] : [2, $container_guid],
'custom_type' => null,
'limit' => $limit,
'type' => $type,
'algorithm' => 'top',
'period' => '7d',
'sync' => $sync,
'from_timestamp' => $fromTimestamp,
'query' => $query,
'single_owner_threshold' => 0,
'pinned_guids' => $type === 'activity' ? array_reverse($container->getPinnedPosts()) : null,
'exclude' => $exclude,
];
if (isset($_GET['nsfw'])) {
$nsfw = $_GET['nsfw'] ?? '';
$opts['nsfw'] = explode(',', $nsfw);
}
$hashtag = null;
if (isset($_GET['hashtag'])) {
$hashtag = strtolower($_GET['hashtag']);
}
if ($hashtag) {
$opts['hashtags'] = [$hashtag];
$opts['filter_hashtags'] = true;
} elseif (isset($_GET['hashtags']) && $_GET['hashtags']) {
$opts['hashtags'] = explode(',', $_GET['hashtags']);
$opts['filter_hashtags'] = true;
}
try {
$result = $this->getData($entities, $opts, $asActivities, $sync);
if ($result->count() <= static::MIN_COUNT) {
$opts['algorithm'] = 'latest';
$result = $this->getData($entities, $opts, $asActivities, $sync);
}
$response = [
'status' => 'success',
'entities' => Exportable::_($result),
'load-next' => $result->getPagingToken(),
];
return Factory::response($response);
} catch (\Exception $e) {
error_log($e);
return Factory::response(['status' => 'error', 'message' => $e->getMessage()]);
}
}
/**
* @param Core\Feeds\Top\Entities $entities
* @param array $opts
* @param bool $asActivities
* @param bool $sync
* @return Response
* @throws \Exception
*/
private function getData($entities, $opts, $asActivities, $sync)
{
/** @var Core\Feeds\Top\Manager $manager */
$manager = Di::_()->get('Feeds\Top\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']);
}
}
return $result;
}
public function post($pages)
{
return Factory::response([]);
}
public function put($pages)
{
return Factory::response([]);
}
public function delete($pages)
{
return Factory::response([]);
}
}
<?php
/**
* settings
* @author edgebal
*/
namespace Minds\Controllers\api\v2\pro;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Manager;
use Minds\Core\Session;
use Minds\Entities\User;
use Minds\Interfaces;
use Minds\Api\Factory;
class settings implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
$user = Session::getLoggedinUser();
if (isset($pages[0]) && $pages[0]) {
if (!Session::isAdmin()) {
return Factory::response([
'status' => 'error',
'message' => 'You are not authorized',
]);
}
$user = new User($pages[0]);
}
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager
->setUser($user)
->setActor(Session::getLoggedinUser());
return Factory::response([
'isActive' => $manager->isActive(),
'settings' => $manager->get(),
]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public function post($pages)
{
$user = Session::getLoggedinUser();
if (isset($pages[0]) && $pages[0]) {
if (!Session::isAdmin()) {
return Factory::response([
'status' => 'error',
'message' => 'You are not authorized',
]);
}
$user = new User($pages[0]);
}
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager
->setUser($user)
->setActor(Session::getLoggedinUser());
if (!$manager->isActive()) {
return Factory::response([
'status' => 'error',
'message' => 'You are not Pro',
]);
}
try {
$success = $manager->set($_POST);
if (!$success) {
throw new Exception('Cannot save Pro settings');
}
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
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([]);
}
}
......@@ -13,6 +13,12 @@ class emails implements Interfaces\Api
public function get($pages)
{
$user = Core\Session::getLoggedInUser();
if (!$user) {
return Factory::response([
'status' => 'error',
'message' => 'User must be logged in.'
]);
}
$campaigns = [ 'when', 'with', 'global' ];
......
......@@ -46,7 +46,15 @@ class thumbnail extends Core\page implements Interfaces\page
$contents = $thumbnail->read();
}
header('Content-type: image/jpeg');
try {
$finfo = new \finfo(FILEINFO_MIME);
$contentType = $finfo->buffer($contents) ?: 'image/jpeg';
} catch (\Exception $e) {
error_log($e);
$contentType = 'image/jpeg';
}
header('Content-type: ' . $contentType);
header('Expires: ' . date('r', strtotime('today + 6 months')), true);
header('Pragma: public');
header('Cache-Control: public');
......
......@@ -78,6 +78,9 @@ class Sums
public function getContractBalance($contract = '', $onlySpend = false)
{
if (!$this->user) {
return 0;
}
$cql = "SELECT SUM(amount) as balance from blockchain_transactions_mainnet WHERE user_guid = ? AND wallet_address = ?";
$values = [
new Varint($this->user->guid),
......
<?php
namespace Minds\Core\Blogs\Delegates;
use Minds\Core\Entities\Propagator\Properties;
use Minds\Entities\Activity;
/**
* Class PropagateProperties
* @package Minds\Core\Blogs\Delegates
*/
class PropagateProperties extends Properties
{
protected $actsOnSubtype = ['blog'];
/**
* Propagate Entity properties to activity
* @param $from
* @param Activity $to
* @return Activity
*/
public function toActivity($from, Activity $to): Activity
{
if ($this->valueHasChanged($from->getTitle(), $to->get('title'))) {
$to->set('title', $from->getTitle());
}
$blurb = strip_tags($from->getBody());
if ($this->valueHasChanged($blurb, $to->get('blurb'))) {
$to->set('blurb', $blurb);
}
if ($this->valueHasChanged($from->getUrl(), $to->getURL())) {
$to->setURL($from->getUrl());
}
if ($this->valueHasChanged($from->getIconUrl(), $to->get('thumbnail_src'))) {
$to->set('thumbnail_src', $from->getIconUrl());
}
return $to;
}
/**
* Propagate activity properties to entity
* @param Activity $from
* @param $to
* @return mixed
*/
public function fromActivity(Activity $from, $to)
{
return $to;
}
}
......@@ -9,6 +9,7 @@
namespace Minds\Core\Blogs;
use Minds\Core\Di\Di;
use Minds\Core\Entities\PropagateProperties;
use Minds\Core\Security\Spam;
class Manager
......@@ -31,6 +32,9 @@ class Manager
/** @var Delegates\Search */
protected $search;
/** @var PropagateProperties */
protected $propagateProperties;
/**
* Manager constructor.
* @param null $repository
......@@ -39,6 +43,7 @@ class Manager
* @param null $feeds
* @param null $spam
* @param null $search
* @param PropagateProperties $propagateProperties
* @throws \Exception
*/
public function __construct(
......@@ -47,7 +52,8 @@ class Manager
$slug = null,
$feeds = null,
$spam = null,
$search = null
$search = null,
PropagateProperties $propagateProperties = null
) {
$this->repository = $repository ?: new Repository();
$this->paywallReview = $paywallReview ?: new Delegates\PaywallReview();
......@@ -55,6 +61,7 @@ class Manager
$this->feeds = $feeds ?: new Delegates\Feeds();
$this->spam = $spam ?: Di::_()->get('Security\Spam');
$this->search = $search ?: new Delegates\Search();
$this->propagateProperties = $propagateProperties ?? Di::_()->get('PropagateProperties');
}
/**
......@@ -170,6 +177,7 @@ class Manager
}
$this->paywallReview->queue($blog);
$this->propagateProperties->from($blog);
}
return $saved;
......
......@@ -6,6 +6,7 @@ use Minds\Core;
use Minds\Entities;
use Minds\Helpers;
use Minds\Helpers\Counters;
use Zend\Diactoros\ServerRequestFactory;
class SEO
{
......@@ -76,18 +77,30 @@ class SEO
$params = $event->getParameters();
$slugs = $params['slugs'];
if ((count($slugs) < 3) || ($slugs[1] != 'blog')) {
/** @var Core\Pro\Domain $proDomain */
$proDomain = Core\Di\Di::_()->get('Pro\Domain');
$request = ServerRequestFactory::fromGlobals();
$serverParams = $request->getServerParams() ?? [];
$host = parse_url($serverParams['HTTP_ORIGIN'] ?? '', PHP_URL_HOST) ?: $serverParams['HTTP_HOST'];
$proSettings = $proDomain->lookup($host);
if ($proSettings && (count($slugs) < 2 || $slugs[0] === 'blog')) {
$slugParts = explode('-', $slugs[1]);
} elseif (!$proSettings && count($slugs) >= 3 && $slugs[1] === 'blog') {
$slugParts = explode('-', $slugs[2]);
} else {
return;
}
$slugParts = explode('-', $slugs[2]);
$guid = $slugParts[count($slugParts) - 1];
if (!is_numeric($guid)) {
return;
}
$event->setResponse($this->viewHandler([ $guid ]));
$event->setResponse($this->viewHandler([$guid]));
});
}
......
You've received a gift of 2 Minds tokens! You can spend these tokens to earn 2,000 extra views on your content with [Boost](https://www.minds.com/boost?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator'] ?>) or to tip your favorite content creators with [Wire](https://www.minds.com/wire?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator'] ?>).
Please use the button below to claim your gift (note: you will need to open the link in a web browser, the mobile app is not yet supported):
| |
|:--:|
| [![Claim Gift](https://cdn-assets.minds.com/emails/claim-gift.png){=150x}](https://www.minds.com/wallet/tokens/transactions?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator'] ?>) |
| |
......@@ -14,8 +14,6 @@ use Minds\Helpers\MagicAttributes;
/**
* Save Action
* @method Save setEntity($entity)
* @method bool save(...$args)
*/
class Save
{
......
<?php
namespace Minds\Core\Entities\Delegates;
use Minds\Core\Entities\Propagator\Properties;
use Minds\Entities\Activity;
/**
* Class PropagateProperties
* @package Minds\Core\Entities\Delegates
*/
class PropagateProperties extends Properties
{
/**
* Propagate Entity properties to activity
* @param $from
* @param Activity $to
* @return Activity
*/
public function toActivity($from, Activity $to): Activity
{
if ($this->valueHasChanged($from->getNsfw(), $to->getNsfw())) {
$to->setNsfw($from->getNsfw());
}
if ($this->valueHasChanged($from->getNsfwLock(), $to->getNsfwLock())) {
$to->setNsfwLock($from->getNsfwLock());
}
return $to;
}
/**
* Propagate activity properties to entity
* @param Activity $from
* @param $to
* @return mixed
*/
public function fromActivity(Activity $from, $to)
{
if ($this->valueHasChanged($from->getNsfw(), $to->getNsfw())) {
$to->setNsfw($from->getNsfw());
}
if ($this->valueHasChanged($from->getNsfwLock(), $to->getNsfwLock())) {
$to->setNsfwLock($from->getNsfwLock());
}
return $to;
}
}
<?php
namespace Minds\Core\Entities;
use Minds\Core\Data\Call;
use Minds\Core\Di\Di;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Entities\Propagator\Properties;
use Minds\Core\EntitiesBuilder;
use Minds\Entities\Activity;
use Minds\Core;
/**
* Class PropagateProperties
* @package Minds\Core\Entities
*/
class PropagateProperties
{
/** @var Properties[] */
protected $propagators;
/** @var Call */
private $db;
/** @var Save */
private $save;
/** @var EntitiesBuilder */
private $entitiesBuilder;
/** @var bool */
private $changed = false;
/**
* PropagateProperties constructor.
* @param Call|null $db
* @param Save|null $save
* @param EntitiesBuilder|null $entitiesBuilder
*/
public function __construct(Call $db = null, Save $save = null, EntitiesBuilder $entitiesBuilder = null)
{
$this->db = $db ?? new Call('entities_by_time');
$this->save = $save ?? new Save();
$this->entitiesBuilder = $entitiesBuilder ?? Di::_()->get('EntitiesBuilder');
$this->registerPropagators();
}
/**
* Register PropagateProperties classes
* @throws \Exception
*/
protected function registerPropagators(): void
{
$this->addPropagator(Core\Blogs\Delegates\PropagateProperties::class);
$this->addPropagator(Core\Feeds\Delegates\PropagateProperties::class);
$this->addPropagator(Core\Media\Delegates\PropagateProperties::class);
$this->addPropagator(Core\Entities\Delegates\PropagateProperties::class);
$this->addPropagator(Core\Permissions\Delegates\PropagateProperties::class);
}
public function clearPropogators(): void
{
$this->propagators = [];
}
/**
* Add a propagator to be called in the chain
* @param string $class
* @throws \Exception
*/
protected function addPropagator(string $class): void
{
$obj = new $class();
if (!$obj instanceof Properties) {
throw new \Exception('Propagator class is not a Property Propagator');
}
$this->propagators[] = $obj;
}
/**
* Propagate the properties from the passed entity
* @param $entity
*/
public function from($entity): void
{
if ($entity instanceof Activity) {
$this->fromActivity($entity);
} else {
$this->toActivities($entity);
}
}
/**
* Propagate properties from an Activity to to it's attachment
* @param Activity $activity
* @throws \Minds\Exceptions\StopEventException
* @throws \Exception
*/
protected function fromActivity(Activity $activity): void
{
$this->changed = false;
$attachment = $this->entitiesBuilder->single($activity->get('entity_guid'));
if (empty($attachment)) {
return;
}
foreach ($this->propagators as $propagator) {
if ($propagator->willActOnEntity($attachment)) {
$attachment = $propagator->fromActivity($activity, $attachment);
$this->changed |= $propagator->changed();
if (!is_object($attachment)) {
throw new \Exception(get_class($propagator) . ' fromActivity method did not return a modified object');
}
}
}
if ($this->changed) {
$this->save->setEntity($attachment)->save();
}
}
/**
* Propagate properties from an Entity to it's activities
* @param $entity
* @throws \Minds\Exceptions\StopEventException
*/
protected function toActivities($entity): void
{
$activities = $this->getActivitiesForEntity($entity->getGuid());
foreach ($activities as $activity) {
$this->propagateToActivity($entity, $activity);
if ($this->changed) {
$this->save->setEntity($activity)->save();
}
}
}
/**
* Get activities for an entity
* @param string $entityGuid
* @return Activity[]
*/
private function getActivitiesForEntity(string $entityGuid): array
{
$activities = [];
foreach ($this->db->getRow("activity:entitylink:{$entityGuid}") as $activityGuid => $ts) {
$activities[] = $this->entitiesBuilder->single($activityGuid);
}
return $activities;
}
/**
* Propagate properties from and entity to an activity
* @param $entity
* @param Activity $activity
*/
public function propagateToActivity($entity, Activity &$activity): void
{
$this->changed = false;
foreach ($this->propagators as $propagator) {
if ($propagator->willActOnEntity($entity)) {
$activity = $propagator->toActivity($entity, $activity);
$this->changed |= $propagator->changed();
}
}
}
}
<?php
namespace Minds\Core\Entities\Propagator;
use Minds\Entities\Activity;
/**
* Properties class that all PropagateProperties delegates should inherit
* @package Minds\Core\Entities\Propagator
*/
abstract class Properties
{
/**
* @var array
*/
protected $actsOnType = [];
/**
* @var array
*/
protected $actsOnSubtype = [];
/**
* @var bool
*/
protected $changed = false;
/**
* @return array
*/
public function actsOnType(): array
{
return $this->actsOnType;
}
/**
* @return array
*/
public function actsOnSubType(): array
{
return $this->actsOnSubtype;
}
/**
* @param $entity
* @return bool
* @throws \Exception
*/
public function willActOnEntity($entity): bool
{
if (!is_array($this->actsOnType)) {
throw new \Exception('actsOnType must be an array');
}
if (!is_array($this->actsOnSubtype)) {
throw new \Exception('actsOnSubType must be an array');
}
if ($this->actsOnType === [] || in_array($entity->getType(), $this->actsOnType, true)) {
if ($this->actsOnSubtype === []) {
return true;
}
if (!is_callable([$entity, 'getSubtype'], true)) {
return false;
}
return in_array($entity->getSubtype(), $this->actsOnSubtype, true);
}
return false;
}
/**
* @param $from
* @param $to
* @return bool
*/
protected function valueHasChanged($from, $to): bool
{
$changed = $from !== $to;
$this->changed |= $changed;
return $changed;
}
/**
* @return bool
*/
public function changed(): bool
{
return $this->changed;
}
/**
* @param $from
* @param Activity $to
* @return Activity
*/
abstract public function toActivity($from, Activity $to): Activity;
/**
* @param Activity $from
* @param $to
* @return mixed
*/
abstract public function fromActivity(Activity $from, $to);
}
<?php
/**
* ActivityDelegateInterface
* @author edgebal
*/
namespace Minds\Core\Feeds\Activity\Delegates;
use Minds\Entities\Activity;
interface ActivityDelegateInterface
{
public function onAdd();
public function onUpdate(Activity $activity);
}
<?php
/**
* AttachmentPaywallDelegate
* @author edgebal
*/
namespace Minds\Core\Feeds\Activity\Delegates;
use Minds\Core\Di\Di;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\EntitiesBuilder;
use Minds\Entities\Activity;
class AttachmentPaywallDelegate implements ActivityDelegateInterface
{
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Save */
protected $save;
/**
* AttachmentPaywallDelegate constructor.
* @param EntitiesBuilder $entitiesBuilder
* @param Save $save
*/
public function __construct(
$entitiesBuilder = null,
$save = null
) {
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->save = $save ?: new Save();
}
/**
* @throws \NotImplementedException
*/
public function onAdd()
{
throw new \NotImplementedException();
}
/**
* @param Activity $activity
* @return bool
*/
public function onUpdate(Activity $activity)
{
if ($activity->entity_guid) {
$attachment = $this->entitiesBuilder->single($activity->entity_guid);
if ($attachment->owner_guid == $activity->owner_guid) {
$attachment->access_id = $activity->isPaywall() ? 0 : 2;
if ($attachment->getSubtype() === 'blog') {
$attachment->setHidden($activity->isPaywall());
} else {
$attachment->hidden = $activity->isPaywall();
}
if (method_exists($attachment, 'setFlag')) {
$attachment->setFlag('paywall', (bool) $activity->isPaywall());
}
if (method_exists($attachment, 'setWireThreshold')) {
$attachment->setWireThreshold($activity->getWireThreshold() ?: false);
}
$this->save
->setEntity($attachment)
->save();
}
}
return true;
}
}
<?php
namespace Minds\Core\Feeds\Delegates;
use Minds\Core\Blogs\Blog;
use Minds\Core\Entities\Propagator\Properties;
use Minds\Entities\Activity;
use Minds\Entities\Entity;
/**
* Class PropagateProperties
* @package Minds\Core\Feeds\Delegates
*/
class PropagateProperties extends Properties
{
/**
* Propagate Entity properties to activity
* @param $from
* @param Activity $to
* @return Activity
*/
public function toActivity($from, Activity $to): Activity
{
if ($this->valueHasChanged((int)$from->getModeratorGuid(), (int)$to->getModeratorGuid())) {
$to->setModeratorGuid((int)$from->getModeratorGuid());
}
if ($this->valueHasChanged((int)$from->getTimeModerated(), (int)$to->getTimeModerated())) {
$to->setTimeModerated((int)$from->getTimeModerated());
}
return $to;
}
/**
* Propagate activity properties to entity
* @param Activity $from
* @param Entity|Blog $to
* @return mixed
*/
public function fromActivity(Activity $from, $to)
{
if ($this->valueHasChanged((int)$from->getModeratorGuid(), (int)$to->getModeratorGuid())) {
$to->setModeratorGuid((int)$from->getModeratorGuid());
}
if ($this->valueHasChanged((int)$from->getTimeModerated(), (int)$to->getTimeModerated())) {
$to->setTimeModerated((int)$from->getTimeModerated());
}
$to = $this->propagateAttachmentPaywallProperties($from, $to);
return $to;
}
/**
* @param Activity $from
* @param Entity $to
* @return mixed
*/
private function propagateAttachmentPaywallProperties(Activity $from, $to)
{
if ($to->owner_guid == $from->owner_guid) {
$newAccessId = $from->isPaywall() ? 0 : 2;
if ($this->valueHasChanged($to->access_id, $from->access_id)) {
$to->access_id = $newAccessId;
}
$newHidden = $from->isPayWall();
if ($to->getSubtype() === 'blog') {
/** @var $to Blog */
if ($this->valueHasChanged($to->getHidden(), $newHidden)) {
$to->setHidden($newHidden);
}
} else {
if ($this->valueHasChanged($to->hidden, $newHidden)) {
$to->hidden = $newHidden;
}
}
if (method_exists($to, 'setFlag')) {
if ($this->valueHasChanged($to->getFlag('paywall'), (bool)$from->isPaywall())) {
$to->setFlag('paywall', (bool)$from->isPaywall());
}
}
if (method_exists($to, 'setWireThreshold')) {
if ($this->valueHasChanged($to->getWireThreshold(), $from->getWireThreshold())) {
$to->setWireThreshold($from->getWireThreshold() ?: false);
}
}
}
return $to;
}
}
......@@ -7,6 +7,7 @@
namespace Minds\Core\Feeds;
use JsonSerializable;
use Minds\Traits\Exportable;
use Minds\Traits\MagicAttributes;
......@@ -22,7 +23,7 @@ use Minds\Traits\MagicAttributes;
* @method string getUrn()
* @method FeedSyncEntity setUrn(string $urn)
*/
class FeedSyncEntity
class FeedSyncEntity implements JsonSerializable
{
use MagicAttributes;
......@@ -55,4 +56,16 @@ class FeedSyncEntity
'entity' => $this->entity ? $this->entity->export() : null,
];
}
/**
* 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(): array
{
return $this->export();
}
}
......@@ -3,12 +3,10 @@
namespace Minds\Core\Feeds\Firehose;
use Minds\Entities\User;
use Minds\Entities\Entity;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Di\Di;
use Minds\Core\Feeds\Top\Manager as TopFeedsManager;
use Minds\Core\Entities\PropagateProperties;
class Manager
{
......@@ -16,25 +14,21 @@ class Manager
protected $topFeedsManager;
/** @var ModerationCache */
protected $moderationCache;
/** @var EntitiesBuilder $entitiesBuilder */
protected $entitiesBuilder;
/** @var Call */
protected $db;
/** @var Save */
protected $save;
/** @var PropagateProperties */
protected $propagateProperties;
public function __construct(
TopFeedsManager $topFeedsManager = null,
ModerationCache $moderationCache = null,
EntitiesBuilder $entitiesBuilder = null,
Call $db = null,
Save $save = null
Save $save = null,
PropagateProperties $propagateProperties = null
) {
$this->topFeedsManager = $topFeedsManager ?: Di::_()->get('Feeds\Top\Manager');
$this->moderationCache = $moderationCache ?: new ModerationCache();
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->db = $db ?: new Call('entities_by_time');
$this->save = $save ?: new Save(); //Mockable, else instantiate a new one on save.
$this->save = $save ?: new Save();
$this->propagateProperties = $propagateProperties ?? Di::_()->get('PropagateProperties');
}
/**
......@@ -79,7 +73,7 @@ class Manager
* Marks an entity as moderated.
*
* @param $entity the entity to mark as moderated, typeless because images do not inherit entity
* @param User $user the moderator
* @param User $moderator the moderator
* @param int $time
*/
public function save(
......@@ -91,22 +85,8 @@ class Manager
$time = time();
}
//Save the entity
$this->saveEntity($entity, $moderator, $time);
if (method_exists($entity, 'getType')
&& $entity->getType() == 'activity'
&& $entity->get('entity_guid')
) {
$attachment = $this->entitiesBuilder->single($entity->get('entity_guid'));
$this->saveEntity($attachment, $moderator, $time);
}
//Moderate parents
foreach ($this->db->getRow('activity:entitylink:'.$entity->getGUID()) as $parentGuid => $ts) {
$activity = $this->entitiesBuilder->single($parentGuid);
$this->saveEntity($activity, $moderator, $time);
}
$this->propagateProperties->from($entity);
}
private function saveEntity(
......
<?php
namespace Minds\Core\Media\Delegates;
use Minds\Core\Entities\Propagator\Properties;
use Minds\Entities\Activity;
/**
* Class PropagateProperties
* @package Minds\Core\Media\Delegates
*/
class PropagateProperties extends Properties
{
protected $actsOnType = ['object'];
protected $actsOnSubtype = ['image', 'video'];
/**
* Propagate Entity properties to activity
* @param $from
* @param Activity $to
* @return Activity
*/
public function toActivity($from, Activity $to): Activity
{
if ($this->valueHasChanged($from->title, $to->getMessage())) {
$to->setMessage($from->title);
}
$fromData = $from->getActivityParameters();
$toData = $to->getCustom();
if ((!isset($toData[1])) || (isset($toData[1]) && $this->valueHasChanged($fromData[1], $toData[1]))) {
$to->setCustom($fromData[0], $fromData[1]);
}
return $to;
}
/**
* Propagate activity properties to entity
* @param Activity $from
* @param $to
* @return mixed
*/
public function fromActivity(Activity $from, $to)
{
if ($this->valueHasChanged($from->getMessage(), $to->title)) {
$to->title = $from->getMessage();
}
return $to;
}
}
<?php
/**
* @author: eiennohi.
*/
namespace Minds\Core\Media\Delegates;
use Minds\Core\Data\Call;
use Minds\Entities\Image;
use Minds\Entities\Video;
class UpdateActivities
{
/** @var Call */
private $indexDb;
private $entityDb;
public function __construct($indexDb = null, $entityDb = null)
{
$this->indexDb = $indexDb ?: new Call('entities_by_time');
$this->entityDb = $entityDb ?: new Call('entities');
}
/**
* @param Image|Video $entity
*/
public function updateActivities($entity)
{
foreach ($this->indexDb->getRow("activity:entitylink:{$entity->guid}") as $guid => $ts) {
$this->entityDb->insert($guid, ['message' => $entity->title]);
$parameters = $entity->getActivityParameters();
$this->entityDb->insert($guid, ['custom_type' => $parameters[0]]);
$this->entityDb->insert($guid, ['custom_data' => json_encode($parameters[1])]);
}
}
}
......@@ -5,17 +5,15 @@ namespace Minds\Core\Media;
use Minds\Core;
use Minds\Entities;
use Minds\Helpers;
use Minds\Core\Media;
class Feeds
{
private $updateActivitiesDelegate;
protected $entity;
protected $propagateProperties;
public function __construct($updateActivitiesDelegate = null)
public function __construct(Core\Entities\PropagateProperties $propagateProperties = null)
{
$this->updateActivitiesDelegate = $updateActivitiesDelegate ?: new Delegates\UpdateActivities();
$this->propagateProperties = $propagateProperties ?? Core\Di\Di::_()->get('PropagateProperties');
}
public function setEntity($entity)
......@@ -25,7 +23,7 @@ class Feeds
return $this;
}
public function createActivity()
public function createActivity(): Entities\Activity
{
if (!$this->entity) {
throw new \Exception('Entity not set');
......@@ -54,7 +52,7 @@ class Feeds
throw new \Exception('Entity not set');
}
$this->updateActivitiesDelegate->updateActivities($this->entity);
$this->propagateProperties->from($this->entity);
}
public function dispatch(array $targets = [])
......
......@@ -80,7 +80,7 @@ class TranscodingStatus
public function isTranscodingComplete()
{
$transcodes = $this->getTranscodes();
return (count($transcodes) === $this->getExpectedTranscodeCount());
return (count($transcodes) >= $this->getExpectedTranscodeCount());
}
/**
......@@ -90,7 +90,7 @@ class TranscodingStatus
{
return array_reduce($this->presets, function ($count, $preset) {
return $count + count($preset['formats']);
}, 0);
}, 0) / 2; // 50% is ok
}
/**
......
......@@ -73,6 +73,7 @@ class Minds extends base
(new \Minds\Entities\EntitiesProvider())->register();
(new Config\ConfigProvider())->register();
(new Router\RouterProvider())->register();
(new OAuth\OAuthProvider())->register();
(new Sessions\SessionsProvider())->register();
(new Boost\BoostProvider())->register();
......@@ -105,6 +106,7 @@ class Minds extends base
(new Faq\FaqProvider())->register();
(new Rewards\RewardsProvider())->register();
(new Plus\PlusProvider())->register();
(new Pro\ProProvider())->register();
(new Hashtags\HashtagsProvider())->register();
(new Feeds\FeedsProvider())->register();
(new Analytics\AnalyticsProvider())->register();
......
......@@ -51,10 +51,10 @@ class Manager
{
$dob = explode('-', $account->getDateOfBirth());
$data = [
'managed' => true,
'type' => 'custom',
'country' => $account->getCountry(),
'legal_entity' => [
'type' => 'individual',
'business_type' => 'individual',
'individual' => [
'first_name' => $account->getFirstName(),
'last_name' => $account->getLastName(),
'address' => [
......@@ -78,21 +78,22 @@ class Manager
// Required for JP only
if ($account->getGender()) {
$data['legal_entity']['gender'] = $account->getGender();
$data['individual']['gender'] = $account->getGender();
}
if ($account->getPhoneNumber()) {
$data['legal_entity']['phone_number'] = $account->getPhoneNumber();
$data['individual']['phone'] = $account->getPhoneNumber();
}
// US 1099 requires SSN
if ($account->getSSN()) {
$data['legal_entity']['ssn_last_4'] = $account->getSSN();
$data['individual']['ssn_last_4'] = $account->getSSN();
$data['requested_capabilities'] = ['card_payments', 'transfers'];
}
if ($account->getPersonalIdNumber()) {
$data['legal_entity']['personal_id_number'] = $account->getPersonalIdNumber();
$data['individual']['id_number'] = $account->getPersonalIdNumber();
}
$result = $this->accountInstance->create($data);
......
<?php
namespace Minds\Core\Permissions\Delegates;
use Minds\Core\Entities\Propagator\Properties;
use Minds\Entities\Activity;
/**
* Class PropagateProperties
* @package Minds\Core\Permissions\Delegates
*/
class PropagateProperties extends Properties
{
/**
* Propagate Entity properties to activity
* @param $from
* @param Activity $to
* @return Activity
*/
public function toActivity($from, Activity $to): Activity
{
if ($this->valueHasChanged($from->getAllowComments(), $to->getAllowComments())) {
$to->setAllowComments($from->getAllowComments());
}
return $to;
}
/**
* Propagate activity properties to entity
* @param Activity $from
* @param $to
* @return mixed
*/
public function fromActivity(Activity $from, $to)
{
if ($this->valueHasChanged($from->getAllowComments(), $to->getAllowComments())) {
$to->setAllowComments($from->getAllowComments());
}
return $to;
}
}
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\Pro\Channel;
use Exception;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Di\Di;
use Minds\Core\Feeds\Top\Manager as TopManager;
use Minds\Core\Pro\Repository;
use Minds\Core\Pro\Settings;
use Minds\Entities\User;
class Manager
{
const CACHE_TTL = 300; // Cache homepage content for 5 minutes
/** @var Repository */
protected $repository;
/** @var TopManager */
protected $top;
/** @var abstractCacher */
protected $cache;
/** @var User */
protected $user;
/**
* Manager constructor.
* @param Repository $repository
* @param TopManager $top
* @param abstractCacher $cache
*/
public function __construct(
$repository = null,
$top = null,
$cache = null
) {
$this->repository = $repository ?: new Repository();
$this->top = $top ?: new TopManager();
$this->cache = $cache ?: Di::_()->get('Cache');
}
/**
* @param User $user
* @return Manager
*/
public function setUser(User $user): Manager
{
$this->user = $user;
return $this;
}
/**
* @return array
* @throws Exception
*/
public function getAllCategoriesContent(): array
{
if (!$this->user) {
throw new Exception('No user set');
}
/** @var Settings $settings */
$settings = $this->repository->getList([
'user_guid' => $this->user->guid
])->first();
if (!$settings) {
throw new Exception('Invalid Pro user');
}
$cacheKey = sprintf("pro::v1::getAllCategoriesContent::%s", $this->user->guid);
$cachedContent = $this->cache->get($cacheKey);
if ($cachedContent) {
return $cachedContent;
}
$tags = $settings->getTagList() ?: [];
$output = [];
$container = (string) $this->user->guid;
foreach ($tags as $tag) {
$opts = [
'container_guid' => $container,
'access_id' => [2, $container],
'hashtags' => [strtolower($tag['tag'])],
'filter_hashtags' => true,
'limit' => 4,
'type' => 'all',
'algorithm' => 'top',
'period' => '7d',
'sync' => true,
'single_owner_threshold' => 0,
];
$content = $this->top->getList($opts)->toArray();
if (count($content) < 2) {
$opts['algorithm'] = 'latest';
$content = $this->top->getList($opts)->toArray();
}
$output[] = [
'tag' => $tag,
'content' => $content,
];
}
$this->cache->set($cacheKey, $output, static::CACHE_TTL);
return $output;
}
}
<?php
/**
* HydrateSettingsDelegate
* @author edgebal
*/
namespace Minds\Core\Pro\Delegates;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Pro\Settings;
use Minds\Entities\Object\Carousel;
use Minds\Entities\User;
use Minds\Helpers\Text;
class HydrateSettingsDelegate
{
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Config */
protected $config;
/**
* HydrateSettingsDelegate constructor.
* @param EntitiesBuilder $entitiesBuilder
* @param Config $config
*/
public function __construct(
$entitiesBuilder = null,
$config = null
) {
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @param User $user
* @param Settings $settings
* @return Settings
*/
public function onGet(User $user, Settings $settings): Settings
{
try {
$logoImage = $settings->getLogoGuid() ? sprintf(
'%sfs/v1/thumbnail/%s/master',
$this->config->get('cdn_url'),
$settings->getLogoGuid()
) : $user->getIconURL('large');
if ($logoImage) {
$settings
->setLogoImage($logoImage);
}
} catch (\Exception $e) {
error_log($e);
}
try {
$carousels = $this->entitiesBuilder->get(['subtype' => 'carousel', 'owner_guid' => (string) $user->guid]);
$carousel = $carousels[0] ?? null;
if ($carousel) {
$settings
->setBackgroundImage(sprintf(
'%sfs/v1/banners/%s/fat/%s',
$this->config->get('cdn_url'),
$carousel->guid,
$carousel->last_updated
));
}
} catch (\Exception $e) {
error_log($e);
}
try {
if ($user->getPinnedPosts()) {
$pinnedPosts = $this->entitiesBuilder->get(['guids' => Text::buildArray($user->getPinnedPosts())]);
uasort($pinnedPosts, function ($a, $b) {
if (!$a || !$b) {
return 0;
}
return ($a->time_created < $b->time_created) ? 1 : -1;
});
$featuredContent = Text::buildArray(array_values(array_filter(array_map(function ($pinnedPost) {
return $pinnedPost->entity_guid ?: $pinnedPost->guid ?: null;
}, $pinnedPosts))));
$settings->setFeaturedContent($featuredContent);
}
} catch (\Exception $e) {
error_log($e);
}
return $settings;
}
}
<?php
/**
* InitializeSettingsDelegate
* @author edgebal
*/
namespace Minds\Core\Pro\Delegates;
use Exception;
use Minds\Core\Pro\Repository;
use Minds\Core\Pro\Settings;
use Minds\Entities\User;
class InitializeSettingsDelegate
{
/** @var Repository */
protected $repository;
/** @var SetupRoutingDelegate */
protected $setupRoutingDelegate;
/**
* InitializeSettingsDelegate constructor.
* @param Repository $repository
* @param SetupRoutingDelegate $setupRoutingDelegate
*/
public function __construct(
$repository = null,
$setupRoutingDelegate = null
) {
$this->repository = $repository ?: new Repository();
$this->setupRoutingDelegate = $setupRoutingDelegate ?: new SetupRoutingDelegate();
}
/**
* @param User $user
* @throws Exception
*/
public function onEnable(User $user): void
{
/** @var Settings|null $settings */
$settings = $this->repository
->getList(['user_guid' => $user->guid])
->first();
if (!$settings) {
$settings = new Settings();
$settings
->setUserGuid($user->guid);
}
if (!$settings->getTitle()) {
$settings->setTitle($user->name ?: $user->username);
}
$this->setupRoutingDelegate
->onUpdate($settings);
$this->repository
->add($settings);
}
}
<?php
/**
* SetupRoutingDelegate
* @author edgebal
*/
namespace Minds\Core\Pro\Delegates;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Domain\EdgeRouters\EdgeRouterInterface;
use Minds\Core\Pro\Domain\EdgeRouters\TraefikDynamoDb;
use Minds\Core\Pro\Settings;
class SetupRoutingDelegate
{
/** @var Config */
protected $config;
/** @var EdgeRouterInterface */
protected $edgeRouter;
/**
* SetupRoutingDelegate constructor.
* @param Config $config
* @param EdgeRouterInterface $edgeRouter
*/
public function __construct(
$config = null,
$edgeRouter = null
) {
$this->config = $config ?: Di::_()->get('Config');
$this->edgeRouter = $edgeRouter ?: new TraefikDynamoDb();
}
/**
* @param Settings $settings
*/
public function onUpdate(Settings $settings): void
{
$userGuid = $settings->getUserGuid();
if (!$settings->getDomain()) {
$settings->setDomain(sprintf("pro-%s.%s", $userGuid, $this->config->get('pro')['subdomain_suffix'] ?? 'minds.com'));
}
$success = $this->edgeRouter
->initialize()
->putEndpoint($settings);
if (!$success) {
error_log("[MindsPro] Cannot setup endpoint.");
// TODO: Implement user-facing warning
}
}
}
<?php
/**
* Domain
* @author edgebal
*/
namespace Minds\Core\Pro;
use Exception;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Entities\User;
class Domain
{
/** @var Config */
protected $config;
/** @var Repository */
protected $repository;
/**
* Domain constructor.
* @param Config $config
* @param Repository $repository
*/
public function __construct(
$config = null,
$repository = null
) {
$this->config = $config ?: Di::_()->get('Config');
$this->repository = $repository ?: new Repository();
}
/**
* @param string $domain
* @return Settings|null
*/
public function lookup(string $domain): ?Settings
{
$rootDomains = $this->config->get('pro')['root_domains'] ?? [];
if (in_array(strtolower($domain), $rootDomains, true)) {
return null;
}
return $this->repository->getList([
'domain' => $domain,
])->first();
}
/**
* @param Settings $settings
* @param User|null $owner
* @return string
* @throws Exception
*/
public function getIcon(Settings $settings, User $owner = null): string
{
if (!$owner) {
$owner = new User();
$owner->guid = $settings->getUserGuid();
}
return $owner->getIconURL('large');
}
}
<?php
/**
* EdgeRouterInterface
* @author edgebal
*/
namespace Minds\Core\Pro\Domain\EdgeRouters;
use Minds\Core\Pro\Settings;
interface EdgeRouterInterface
{
public function initialize(): EdgeRouterInterface;
public function putEndpoint(Settings $settings): bool;
}
<?php
/**
* TraefikDynamoDb
* @author edgebal
*/
namespace Minds\Core\Pro\Domain\EdgeRouters;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Exception\DynamoDbException;
use Aws\DynamoDb\Marshaler;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Settings;
class TraefikDynamoDb implements EdgeRouterInterface
{
/** @var Config */
protected $config;
/** @var DynamoDbClient */
protected $dynamoDb;
/**
* TraefikDynamoDb constructor.
* @param Config $config
* @param DynamoDbClient $dynamoDb
*/
public function __construct(
$config = null,
$dynamoDb = null
) {
$this->config = $config ?: Di::_()->get('Config');
$this->dynamoDb = $dynamoDb;
}
/**
* @return EdgeRouterInterface
*/
public function initialize(): EdgeRouterInterface
{
$awsConfig = $this->config->get('aws');
$opts = [
'region' => $awsConfig['region']
];
if (!isset($awsConfig['useRoles']) || !$awsConfig['useRoles']) {
$opts['credentials'] = [
'key' => $awsConfig['key'],
'secret' => $awsConfig['secret'],
];
}
if (isset($awsConfig['dynamoDbEndpoint'])) {
$opts['endpoint'] = $awsConfig['dynamoDbEndpoint'];
}
$this->dynamoDb = new DynamoDbClient(array_merge([
'version' => '2012-08-10',
], $opts));
return $this;
}
/**
* @param Settings $settings
* @return bool
*/
public function putEndpoint(Settings $settings): bool
{
$domain = $settings->getDomain();
if (!$domain) {
return false;
}
$userGuid = (string) $settings->getUserGuid();
$marshaler = new Marshaler();
$entry = $marshaler->marshalJson(json_encode([
'id' => "minds-pro-frontend-{$userGuid}",
'name' => "minds-pro-{$userGuid}",
'frontend' => [
'backend' => 'minds-pro',
'routes' => [
'pro-domain' => [
'rule' => "Host: {$domain}"
]
],
'headers' => [
'SSLRedirect' => true,
'customrequestheaders' => [
'X-Minds-Pro' => '1',
],
],
'passHostHeader' => true,
],
]));
try {
$this->dynamoDb->putItem([
'TableName' => $this->config->get('pro')['dynamodb_table_name'],
'Item' => $entry,
]);
return true;
} catch (DynamoDbException $e) {
error_log($e);
return false;
}
}
}
<?php
/**
* Security
* @author edgebal
*/
namespace Minds\Core\Pro\Domain;
use Exception;
use Minds\Common\Cookie;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Zend\Diactoros\ServerRequest;
class Security
{
/** @var string */
const JWT_COOKIE_NAME = 'PRO-XSRF-JWT';
/** @var string */
const XSRF_COOKIE_NAME = 'XSRF-TOKEN';
/** @var Cookie */
protected $cookie;
/** @var Jwt */
protected $jwt;
/** @var Config */
protected $config;
/**
* Security constructor.
* @param Cookie $cookie
* @param Jwt $jwt
* @param Config $config
*/
public function __construct(
$cookie = null,
$jwt = null,
$config = null
) {
$this->cookie = $cookie ?: new Cookie();
$this->jwt = $jwt ?: new Jwt();
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @param string $domain
* @return string
* @throws Exception
*/
public function setUp($domain): string
{
$nonce = $this->jwt->randomString();
$nbf = time();
$exp = $nbf + 60;
$jwt = $this->jwt
->setKey($this->getEncryptionKey())
->encode([
'nonce' => $nonce,
], $exp, $nbf);
$this->cookie
->setName(static::JWT_COOKIE_NAME)
->setValue($jwt)
->setExpire($exp)
->setPath('/')
->setHttpOnly(false)
->create();
$this->cookie
->setName(static::XSRF_COOKIE_NAME)
->setValue($nonce)
->setExpire(0)
->setPath('/')
->setHttpOnly(false)
->create();
return $jwt;
}
/**
* @param ServerRequest $request
*/
public function syncCookies(ServerRequest $request): void
{
$jwt = $request->getServerParams()['HTTP_X_PRO_XSRF_JWT'] ?? '';
if (!$jwt) {
return;
}
try {
$data = $this->jwt
->setKey($this->getEncryptionKey())
->decode($jwt);
if (($_COOKIE[static::XSRF_COOKIE_NAME] ?? null) === $data['nonce']) {
return;
}
$this->cookie
->setName(static::XSRF_COOKIE_NAME)
->setValue($data['nonce'])
->setExpire(0)
->setPath('/')
->setHttpOnly(false)
->create();
} catch (Exception $e) {
// Invalid or expired JWT
}
}
/**
* @return string
*/
protected function getEncryptionKey(): string
{
return $this->config->get('oauth')['encryption_key'] ?? '';
}
}
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\Pro;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Util\StringValidator;
use Minds\Entities\User;
class Manager
{
/** @var Repository */
protected $repository;
/** @var Save */
protected $saveAction;
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Delegates\InitializeSettingsDelegate */
protected $initializeSettingsDelegate;
/** @var Delegates\HydrateSettingsDelegate */
protected $hydrateSettingsDelegate;
/** @var Delegates\SetupRoutingDelegate */
protected $setupRoutingDelegate;
/** @var User */
protected $user;
/** @var User */
protected $actor;
/**
* Manager constructor.
* @param Repository $repository
* @param Save $saveAction
* @param EntitiesBuilder $entitiesBuilder
* @param Delegates\InitializeSettingsDelegate $initializeSettingsDelegate
* @param Delegates\HydrateSettingsDelegate $hydrateSettingsDelegate
* @param Delegates\SetupRoutingDelegate $setupRoutingDelegate
*/
public function __construct(
$repository = null,
$saveAction = null,
$entitiesBuilder = null,
$initializeSettingsDelegate = null,
$hydrateSettingsDelegate = null,
$setupRoutingDelegate = null
) {
$this->repository = $repository ?: new Repository();
$this->saveAction = $saveAction ?: new Save();
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->initializeSettingsDelegate = $initializeSettingsDelegate ?: new Delegates\InitializeSettingsDelegate();
$this->hydrateSettingsDelegate = $hydrateSettingsDelegate ?: new Delegates\HydrateSettingsDelegate();
$this->setupRoutingDelegate = $setupRoutingDelegate ?: new Delegates\SetupRoutingDelegate();
}
/**
* @param User $user
* @return Manager
*/
public function setUser(User $user): Manager
{
$this->user = $user;
return $this;
}
/**
* @param User $actor
* @return Manager
*/
public function setActor(User $actor): Manager
{
$this->actor = $actor;
return $this;
}
/**
* @return bool
* @throws Exception
*/
public function isActive(): bool
{
if (!$this->user) {
throw new Exception('Invalid user');
}
return $this->user->isPro();
}
/**
* @param $until
* @return bool
* @throws Exception
*/
public function enable($until): bool
{
if (!$this->user) {
throw new Exception('Invalid user');
}
$this->user
->setProExpires($until);
$saved = $this->saveAction
->setEntity($this->user)
->save();
$this->initializeSettingsDelegate
->onEnable($this->user);
return (bool) $saved;
}
/**
* @return bool
* @throws Exception
*/
public function disable(): bool
{
if (!$this->user) {
throw new Exception('Invalid user');
}
// TODO: Disable subscription instead, let Pro expire itself at the end of the sub
$this->user
->setProExpires(0);
$saved = $this->saveAction
->setEntity($this->user)
->save();
return (bool) $saved;
}
/**
* @return Settings|null
* @throws Exception
*/
public function get(): ?Settings
{
if (!$this->user) {
throw new Exception('Invalid user');
}
$settings = $this->repository->getList([
'user_guid' => $this->user->guid,
])->first();
if (!$settings) {
return null;
}
return $this->hydrate($settings);
}
/**
* @param Settings $settings
* @return Settings
*/
public function hydrate(Settings $settings): Settings
{
return $this->hydrateSettingsDelegate
->onGet($this->user, $settings);
}
/**
* @param array $values
* @return bool
* @throws Exception
*/
public function set(array $values = []): bool
{
if (!$this->user) {
throw new Exception('Invalid user');
}
$settings = $this->get() ?: new Settings();
$settings
->setUserGuid($this->user->guid);
if (isset($values['domain'])) {
$domain = trim($values['domain']);
if (!StringValidator::isDomain($domain)) {
throw new \Exception('Invalid domain');
}
$settings
->setDomain($domain);
}
if (isset($values['title'])) {
$title = trim($values['title']);
if (strlen($title) > 60) {
throw new \Exception('Title must be 60 characters or less');
}
$settings
->setTitle($title);
}
if (isset($values['headline'])) {
$headline = trim($values['headline']);
if (strlen($headline) > 80) {
throw new \Exception('Headline must be 80 characters or less');
}
$settings
->setHeadline($headline);
}
if (isset($values['text_color'])) {
if (!StringValidator::isHexColor($values['text_color'])) {
throw new \Exception('Text color must be a valid hex color');
}
$settings
->setTextColor($values['text_color']);
}
if (isset($values['primary_color'])) {
if (!StringValidator::isHexColor($values['primary_color'])) {
throw new \Exception('Primary color must be a valid hex color');
}
$settings
->setPrimaryColor($values['primary_color']);
}
if (isset($values['plain_background_color'])) {
if (!StringValidator::isHexColor($values['plain_background_color'])) {
throw new \Exception('Plain background color must be a valid hex color');
}
$settings
->setPlainBackgroundColor($values['plain_background_color']);
}
if (isset($values['tile_ratio'])) {
if (!in_array($values['tile_ratio'], Settings::TILE_RATIOS, true)) {
throw new \Exception('Invalid tile ratio');
}
$settings
->setTileRatio($values['tile_ratio']);
}
if (isset($values['logo_guid']) && $values['logo_guid'] !== '') {
$image = $this->entitiesBuilder->single($values['logo_guid']);
// if the image doesn't exist or the guid doesn't correspond to an image
if (!$image || ($image->type !== 'object' || $image->subtype !== 'image')) {
throw new \Exception('logo_guid must be a valid image guid');
}
$settings
->setLogoGuid(trim($values['logo_guid']));
}
if (isset($values['footer_text'])) {
$footer_text = trim($values['footer_text']);
if (strlen($footer_text) > 80) {
throw new \Exception('Footer text must be 80 characters or less');
}
$settings
->setFooterText($footer_text);
}
if (isset($values['footer_links']) && is_array($values['footer_links'])) {
$footerLinks = array_map(function ($item) {
$href = $item['href'];
$title = ($item['title'] ?? null) ?: $item['href'];
return compact('title', 'href');
}, array_filter($values['footer_links'], function ($item) {
return $item && $item['href'] && filter_var($item['href'], FILTER_VALIDATE_URL);
}));
$settings
->setFooterLinks(array_values($footerLinks));
}
if (isset($values['tag_list']) && is_array($values['tag_list'])) {
$tagList = array_map(function ($item) {
$tag = trim($item['tag'], "#\t\n\r");
$label = ($item['label'] ?? null) ?: "#{$item['tag']}";
return compact('label', 'tag');
}, array_filter($values['tag_list'], function ($item) {
return $item && $item['tag'];
}));
$settings
->setTagList(array_values($tagList));
}
if (isset($values['scheme'])) {
if (!in_array($values['scheme'], Settings::COLOR_SCHEMES, true)) {
throw new \Exception('Invalid tile ratio');
}
$settings
->setScheme($values['scheme']);
}
if (isset($values['custom_head']) && $this->actor->isAdmin()) {
$settings
->setCustomHead($values['custom_head']);
}
$this->setupRoutingDelegate
->onUpdate($settings);
return $this->repository->update($settings);
}
}
<?php
/**
* ProProvider
* @author edgebal
*/
namespace Minds\Core\Pro;
use Minds\Core\Di\ImmutableException;
use Minds\Core\Di\Provider;
class ProProvider extends Provider
{
/**
* @throws ImmutableException
*/
public function register()
{
$this->di->bind('Pro\Manager', function ($di) {
return new Manager();
}, ['useFactory' => true]);
$this->di->bind('Pro\Domain', function ($di) {
return new Domain();
}, ['useFactory' => true]);
$this->di->bind('Pro\Domain\Security', function ($di) {
return new Domain\Security();
}, ['useFactory' => true]);
$this->di->bind('Pro\Domain\Subscription', function ($di) {
return new Domain\Subscription();
}, ['useFactory' => true]);
$this->di->bind('Pro\SEO', function ($di) {
return new SEO();
}, ['useFactory' => true]);
$this->di->bind('Pro\Channel\Manager', function ($di) {
return new Channel\Manager();
}, ['useFactory' => true]);
}
}
<?php
/**
* Repository
* @author edgebal
*/
namespace Minds\Core\Pro;
use Cassandra\Bigint;
use Cassandra\Rows;
use Exception;
use Minds\Common\Repository\Response;
use Minds\Core\Data\Cassandra\Client;
use Minds\Core\Data\Cassandra\Prepared\Custom;
use Minds\Core\Di\Di;
class Repository
{
/** @var Client */
protected $db;
/**
* Repository constructor.
* @param Client $db
*/
public function __construct(
$db = null
) {
$this->db = $db ?: Di::_()->get('Database\Cassandra\Cql');
}
/**
* @param array $opts
* @return Response
*/
public function getList(array $opts = []): Response
{
$opts = array_merge([
'user_guid' => null,
'domain' => null,
'limit' => null,
'offset' => null,
], $opts);
$cql = "SELECT * FROM pro";
$where = [];
$values = [];
$cqlOpts = [];
if ($opts['user_guid']) {
$where[] = 'user_guid = ?';
$values[] = new Bigint($opts['user_guid']);
} elseif ($opts['domain']) {
$cql = "SELECT * FROM pro_by_domain";
$where[] = 'domain = ?';
$values[] = $opts['domain'];
}
if ($where) {
$cql .= sprintf(" WHERE %s", implode(' AND ', $where));
}
if ($opts['limit']) {
$cqlOpts['page_size'] = (int) $opts['limit'];
}
if ($opts['offset']) {
$cqlOpts['paging_state_token'] = base64_decode($opts['offset'], true);
}
$prepared = new Custom();
$prepared->query($cql, $values);
$prepared->setOpts($cqlOpts);
$response = new Response();
try {
/** @var Rows $rows */
$rows = $this->db->request($prepared);
if ($rows) {
foreach ($rows as $row) {
$settings = new Settings();
$settings
->setUserGuid($row['user_guid']->toInt())
->setDomain($row['domain']);
$data = json_decode($row['json_data'] ?: '{}', true);
$settings
->setTitle($data['title'] ?? '')
->setHeadline($data['headline'] ?? '')
->setTextColor($data['text_color'] ?? '')
->setPrimaryColor($data['primary_color'] ?? '')
->setPlainBackgroundColor($data['plain_background_color'] ?? '')
->setLogoGuid($data['logo_guid'] ?? '')
->setTileRatio($data['tile_ratio'] ?? '')
->setFooterText($data['footer_text'] ?? '')
->setFooterLinks($data['footer_links'] ?? [])
->setTagList($data['tag_list'] ?? [])
->setScheme($data['scheme'] ?? '')
->setCustomHead($data['custom_head'] ?? '')
;
$response[] = $settings;
}
$response
->setLastPage($rows->isLastPage())
->setPagingToken(base64_encode($rows->pagingStateToken()));
}
} catch (Exception $e) {
error_log("[ProRepository] $e");
$response->setException($e);
}
return $response;
}
/**
* @param Settings $settings
* @return bool
* @throws Exception
*/
public function add(Settings $settings): bool
{
if (!$settings->getUserGuid()) {
throw new Exception('Invalid user GUID');
}
$cql = "INSERT INTO pro (user_guid, domain, json_data) VALUES (?, ?, ?)";
$settings = [
new Bigint($settings->getUserGuid()),
$settings->getDomain(),
json_encode([
'user_guid' => (string) $settings->getUserGuid(),
'domain' => $settings->getDomain(),
'title' => $settings->getTitle(),
'headline' => $settings->getHeadline(),
'text_color' => $settings->getTextColor(),
'primary_color' => $settings->getPrimaryColor(),
'plain_background_color' => $settings->getPlainBackgroundColor(),
'tile_ratio' => $settings->getTileRatio(),
'logo_guid' => $settings->getLogoGuid(),
'footer_text' => $settings->getFooterText(),
'footer_links' => $settings->getFooterLinks(),
'tag_list' => $settings->getTagList(),
'scheme' => $settings->getScheme(),
'custom_head' => $settings->getCustomHead(),
]),
];
$prepared = new Custom();
$prepared->query($cql, $settings);
return (bool) $this->db->request($prepared, true);
}
/**
* @param Settings $settings
* @return bool
* @throws Exception
*/
public function update(Settings $settings): bool
{
return $this->add($settings);
}
/**
* @param Settings $settingsRef
* @return bool
* @throws Exception
*/
public function delete(Settings $settingsRef): bool
{
if (!$settingsRef->getUserGuid()) {
throw new Exception('Invalid user GUID');
}
$cql = "DELETE FROM pro WHERE user_guid = ?";
$settingsRef = [
new Bigint($settingsRef->getUserGuid()),
];
$prepared = new Custom();
$prepared->query($cql, $settingsRef);
return (bool) $this->db->request($prepared, true);
}
}
<?php
/**
* @author: eiennohi.
*/
namespace Minds\Core\Pro;
use Exception;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\EntitiesBuilder;
use Minds\Core\SEO\Manager;
use Minds\Entities\Activity;
use Minds\Entities\User;
use Minds\Helpers;
class SEO
{
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Config */
protected $config;
/** @var User */
protected $user;
/**
* SEO constructor.
* @param EntitiesBuilder $entitiesBuilder
* @param Config $config
*/
public function __construct(EntitiesBuilder $entitiesBuilder = null, Config $config = null)
{
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @param User $user
* @return SEO
*/
public function setUser(User $user): SEO
{
$this->user = $user;
return $this;
}
/**
* @param Settings $proSettings
* @throws Exception
*/
public function setup(Settings $proSettings): void
{
Manager::reset();
$title = $proSettings->getTitle() ?: $this->user->name;
$tagList = array_map(function ($tag) {
return $tag['tag'];
}, $proSettings->getTagList());
Manager::setDefaults([
'title' => $title,
'description' => $proSettings->getOneLineHeadline(),
'keywords' => implode(',', $tagList),
'og:title' => $title,
'og:url' => $proSettings->getDomain(),
'og:description' => $proSettings->getOneLineHeadline(),
'og:type' => 'website',
'og:image' => $this->user->getIconURL('large'),
]);
Manager::add('/', function () {
});
Manager::add('/newsfeed', [$this, 'activityHandler']);
Manager::add('/media', [$this, 'entityHandler']);
// blog route added in Blogs\SEO
}
/**
* @param array $slugs
* @return array|null
*/
public function activityHandler($slugs = []): ?array
{
if (!isset($slugs[0]) || !is_numeric($slugs[0])) {
return null;
}
$activity = new Activity($slugs[0]);
if (!$activity->guid || Helpers\Flags::shouldFail($activity)) {
header("HTTP/1.0 404 Not Found");
return [
'robots' => 'noindex',
];
}
if ($activity->paywall) {
return null;
}
$title = $activity->title ?: $activity->message;
$description = $activity->blurb ?: "@{$activity->ownerObj['username']} on {$this->config->site_name}";
$meta = [
'title' => $title,
'description' => $description,
'og:title' => $title,
'og:description' => $description,
'og:url' => $activity->getUrl(),
'og:image' => $activity->custom_type == 'batch' ? $activity->custom_data[0]['src'] : $activity->thumbnail_src,
'og:image:width' => 2000,
'og:image:height' => 1000,
'twitter:site' => '@minds',
'twitter:card' => 'summary',
'al:ios:url' => 'minds://activity/' . $activity->guid,
'al:android:url' => 'minds://minds/activity/' . $activity->guid,
'robots' => 'all',
];
if ($activity->custom_type == 'video') {
$meta['og:type'] = "video";
$meta['og:image'] = $activity->custom_data['thumbnail_src'];
}
return $meta;
}
/**
* @param $entity
* @param $prop
* @return mixed
*/
public function getEntityProperty($entity, $prop)
{
$getter = "get${$prop}";
if (isset($entity->{$prop})) {
return $entity->{$prop};
} elseif (Helpers\MagicAttributes::getterExists($entity, $getter)) {
return $entity->{$getter}();
}
return null;
}
/**
* @param array $slugs
* @return array|null
*/
public function entityHandler($slugs = []): ?array
{
if (!isset($slugs[0]) || !is_numeric($slugs[0])) {
return null;
}
$entity = $this->entitiesBuilder->single($slugs[0]);
if (!$entity->guid || Helpers\Flags::shouldFail($entity)) {
header("HTTP/1.0 404 Not Found");
return [
'robots' => 'noindex',
];
}
if ($entity->paywall) {
return null;
}
$owner = $this->getEntityProperty($entity, 'ownerObj');
$title = $this->getEntityProperty($entity, 'title') ?: $this->getEntityProperty($entity, 'description');
$siteName = $this->config->site_name;
$description = $title ?? $this->getEntityProperty($entity, 'blurb') ?? "@{$owner['username']} on {$siteName}";
$meta = [
'title' => $title,
'description' => $description,
'og:title' => $title,
'og:description' => $description,
'og:url' => $entity->getUrl(),
'og:image:width' => 2000,
'og:image:height' => 1000,
'robots' => 'all',
];
switch ($entity->subtype) {
case 'video':
$meta['og:type'] = "video";
$meta['og:image'] = $entity->getIconUrl();
break;
case 'image':
$meta['og:type'] = "image";
$meta['og:image'] = $entity->getIconUrl();
break;
case 'blog':
$meta['og:type'] = "blog";
$meta['og:image'] = $entity->getIconUrl();
break;
case 'group':
$meta['og:type'] = "group";
$meta['og:image'] = $this->config->cdn_url . 'fs/v1/banner/' . $entity->banner;
break;
}
return $meta;
}
}
<?php
/**
* Settings
* @author edgebal
*/
namespace Minds\Core\Pro;
use JsonSerializable;
use Minds\Traits\MagicAttributes;
/**
* Class Settings
* @package Minds\Core\Pro
* @method int|string getUserGuid()
* @method Settings setUserGuid(int|string $userGuid)
* @method string getDomain()
* @method Settings setDomain(string $domain)
* @method string getTitle()
* @method Settings setTitle(string $title)
* @method string getHeadline()
* @method Settings setHeadline(string $headline)
* @method string getTextColor()
* @method Settings setTextColor(string $textColor)
* @method string getPrimaryColor()
* @method Settings setPrimaryColor(string $primaryColor)
* @method string getPlainBackgroundColor()
* @method Settings setPlainBackgroundColor(string $plainBackgroundColor)
* @method string getTileRatio()
* @method Settings setTileRatio(string $tileRatio)
* @method int|string getLogoGuid()
* @method Settings setLogoGuid(int|string $logoGuid)
* @method string getFooterText()
* @method Settings setFooterText(string $footerText)
* @method array getFooterLinks()
* @method Settings setFooterLinks(array $footerLinks)
* @method array getTagList()
* @method Settings setTagList(array $tagList)
* @method string getScheme()
* @method Settings setScheme(string $scheme)
* @method string getBackgroundImage()
* @method Settings setBackgroundImage(string $backgroundImage)
* @method string getLogoImage()
* @method Settings setLogoImage(string $logoImage)
* @method array getFeaturedContent()
* @method Settings setFeaturedContent(array $featuredContent)
* @method string getCustomHead()
* @method Settings setCustomHead(string $customHead)
*/
class Settings implements JsonSerializable
{
use MagicAttributes;
/** @var string */
const DEFAULT_TEXT_COLOR = '#000000';
/** @var string */
const DEFAULT_PRIMARY_COLOR = '#4690df';
/** @var string */
const DEFAULT_PLAIN_BACKGROUND_COLOR = '#ffffff';
/** @var string */
const DEFAULT_TILE_RATIO = '16:9';
/** @var array */
const TILE_RATIOS = ['16:9', '16:10', '4:3', '1:1'];
/** @var array */
const COLOR_SCHEMES = ['light', 'dark'];
/** @var int */
protected $userGuid;
/** @var string */
protected $domain;
/** @var string */
protected $title;
/** @var string */
protected $headline;
/** @var string */
protected $textColor;
/** @var string */
protected $primaryColor;
/** @var string */
protected $plainBackgroundColor;
/** @var int */
protected $logoGuid;
/** @var string */
protected $backgroundImage;
/** @var string */
protected $tileRatio = '16:9';
/** @var string */
protected $logoImage;
/** @var string */
protected $footerText;
/** @var array */
protected $footerLinks = [];
/** @var array */
protected $tagList = [];
/** @var string */
protected $scheme;
/** @var array */
protected $featuredContent = [];
/** @var string */
protected $customHead = '';
/**
* @return string
*/
public function getOneLineHeadline(): string
{
return preg_replace("/\\r?\\n+/", ' ', $this->headline);
}
/**
* @return array
*/
public function export(): array
{
$textColor = $this->textColor ?: static::DEFAULT_TEXT_COLOR;
$primaryColor = $this->primaryColor ?: static::DEFAULT_PRIMARY_COLOR;
$plainBackgroundColor = $this->plainBackgroundColor ?: static::DEFAULT_PLAIN_BACKGROUND_COLOR;
$tileRatio = $this->tileRatio ?: static::DEFAULT_TILE_RATIO;
return [
'user_guid' => (string) $this->userGuid,
'domain' => $this->domain,
'title' => $this->title,
'headline' => $this->headline,
'text_color' => $textColor,
'primary_color' => $primaryColor,
'plain_background_color' => $plainBackgroundColor,
'tile_ratio' => $tileRatio,
'footer_text' => $this->footerText,
'footer_links' => $this->footerLinks,
'tag_list' => $this->tagList,
'logo_guid' => (string) $this->logoGuid,
'background_image' => $this->backgroundImage,
'logo_image' => $this->logoImage,
'featured_content' => $this->featuredContent,
'scheme' => $this->scheme,
'custom_head' => $this->customHead,
'one_line_headline' => $this->getOneLineHeadline(),
'styles' => $this->buildStyles(),
];
}
/**
* @return array
*/
public function buildStyles(): array
{
$textColor = $this->textColor ?: static::DEFAULT_TEXT_COLOR;
$primaryColor = $this->primaryColor ?: static::DEFAULT_PRIMARY_COLOR;
$plainBackgroundColor = $this->plainBackgroundColor ?: static::DEFAULT_PLAIN_BACKGROUND_COLOR;
$tileRatioPercentage = $this->calcTileRatioPercentage();
return [
'text_color' => $textColor,
'primary_color' => $primaryColor,
'plain_background_color' => $plainBackgroundColor,
'transparent_background_color' => sprintf("%sa0", $plainBackgroundColor),
'more_transparent_background_color' => sprintf("%s50", $plainBackgroundColor),
'tile_ratio' => sprintf("%s%%", $tileRatioPercentage),
];
}
/**
* @return float
*/
public function calcTileRatioPercentage(): float
{
$ratioFragments = explode(':', $this->tileRatio ?: '16:9');
$percentage = $ratioFragments[1] / $ratioFragments[0] * 100;
return round($percentage, 3);
}
/**
* 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(): array
{
return $this->export();
}
}
......@@ -1446,4 +1446,18 @@ CREATE TABLE minds.email_campaign_logs (
PRIMARY KEY (receiver_guid, time_sent)
) WITH CLUSTERING ORDER BY (time_sent desc);
ALTER TABLE minds.views ADD owner_guid text;
\ No newline at end of file
ALTER TABLE minds.views ADD owner_guid text;
CREATE TABLE minds.pro (
user_guid bigint,
domain text,
json_data text,
PRIMARY KEY (user_guid)
);
CREATE MATERIALIZED VIEW minds.pro_by_domain AS
SELECT domain, user_guid, json_data
FROM minds.pro
WHERE user_guid IS NOT NULL AND domain IS NOT NULL
PRIMARY KEY (domain, user_guid)
WITH CLUSTERING ORDER BY (user_guid ASC);
<?php
namespace Minds\Core\Queue\Runners;
use Minds\Core\Di\Di;
......@@ -28,7 +27,6 @@ class Registered implements QueueRunner
//subscribe to minds channel
$subscriber = new User($user_guid);
$subscriber->subscribe('100000000000000519');
echo "[registered]: User registered $user_guid\n";
......
......@@ -118,10 +118,10 @@ class Repository
$contributions[] = $contribution;
}
$pagingStateToken = $rows ? $rows->pagingStateToken() : null;
return [
'contributions' => $contributions,
'token' => $rows->pagingStateToken()
'token' => $pagingStateToken
];
}
......
......@@ -2,12 +2,12 @@
namespace Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\I18n\I18n;
use Minds\Core\Router\Manager;
use Minds\Helpers;
use Minds\Core\Di\Di;
use Zend\Diactoros\ServerRequestFactory;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequestFactory;
/**
* Minds Core Router.
......@@ -36,6 +36,20 @@ class Router
'/checkout' => '\\Minds\\Controllers\\checkout',
];
/** @var Manager */
protected $manager;
/**
* Router constructor.
* @param Manager $manager
*/
public function __construct(
$manager = null
) {
/** @var Router\Manager $manager */
$this->manager = $manager ?: Di::_()->get('Router\Manager');
}
/**
* Route the pages
* (fallback to elgg page handler if we fail).
......@@ -70,6 +84,13 @@ class Router
$request = ServerRequestFactory::fromGlobals();
$response = new JsonResponse([]);
$result = $this->manager
->handle($request, $response);
if ($result === false) {
return null;
}
if ($request->getMethod() === 'OPTIONS') {
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
......@@ -95,8 +116,6 @@ class Router
// XSRF Cookie - may be able to remove now with OAuth flow
Security\XSRF::setCookie();
new SEO\Defaults(Di::_()->get('Config'));
if (Session::isLoggedin()) {
Helpers\Analytics::increment('active');
}
......
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\Router;
use Minds\Core\Router\Middleware;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
class Manager
{
/** @var Middleware\RouterMiddleware[] */
protected $middleware;
/**
* Manager constructor.
* @param Middleware\RouterMiddleware[] $middleware
*/
public function __construct(
$middleware = null
) {
$this->middleware = $middleware ?: [
new Middleware\SEOMiddleware(),
new Middleware\ProMiddleware(), // this needs to always be the last element in this array
];
}
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return bool|null
*/
public function handle(ServerRequest &$request, JsonResponse &$response): ?bool
{
$result = null;
foreach ($this->middleware as $middleware) {
$result = $middleware->onRequest($request, $response);
if ($result === false) {
break;
}
}
return $result;
}
}
<?php
/**
* ProMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
use Exception;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Pro\Manager;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Domain;
use Minds\Core\Pro\SEO;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
class ProMiddleware implements RouterMiddleware
{
/** @var Domain */
protected $domain;
/** @var Domain\Security */
protected $domainSecurity;
/** @var Manager */
protected $manager;
/** @var SEO */
protected $seo;
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/**
* ProMiddleware constructor.
* @param Domain $domain
* @param Domain\Security $domainSecurity
* @param Manager $manager
* @param SEO $seo
* @param EntitiesBuilder $entitiesBuilder
*/
public function __construct(
$domain = null,
$domainSecurity = null,
$manager = null,
$seo = null,
$entitiesBuilder = null
) {
$this->domain = $domain ?: Di::_()->get('Pro\Domain');
$this->domainSecurity = $domainSecurity ?: Di::_()->get('Pro\Domain\Security');
$this->manager = $manager ?: Di::_()->get('Pro\Manager');
$this->seo = $seo ?: Di::_()->get('Pro\SEO');
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
}
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return bool|null
* @throws Exception
*/
public function onRequest(ServerRequest $request, JsonResponse &$response): ?bool
{
$serverParams = $request->getServerParams() ?? [];
$originalHost = $serverParams['HTTP_HOST'];
$scheme = $request->getUri()->getScheme();
$host = parse_url($serverParams['HTTP_ORIGIN'] ?? '', PHP_URL_HOST) ?: $originalHost;
if (!$host) {
return null;
}
$settings = $this->domain->lookup($host);
if (!$settings) {
return null;
}
header(sprintf("Access-Control-Allow-Origin: %s://%s", $scheme, $host));
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With,X-No-Cache,x-xsrf-token,x-pro-xsrf-jwt,x-minds-origin,x-version');
if ($request->getMethod() === 'OPTIONS') {
return false;
}
// Get Pro channel
$user = $this->entitiesBuilder->single($settings->getUserGuid());
// Hydrate with asset URLs
$settings = $this->manager
->setUser($user)
->hydrate($settings);
// Setup SEO
$this->seo
->setUser($user)
->setup($settings);
// Initialize XRSF JWT cookie, only if we're on Pro domain's scope
// If not and within 1 minute, update XSRF cookie to match it
if ($originalHost === $settings->getDomain()) {
$this->domainSecurity
->setUp($settings->getDomain());
} else {
$this->domainSecurity
->syncCookies($request);
}
return null;
}
}
<?php
/**
* RouterMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
interface RouterMiddleware
{
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return bool|null
*/
public function onRequest(ServerRequest $request, JsonResponse &$response): ?bool;
}
<?php
namespace Minds\Core\Router\Middleware;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\SEO;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
class SEOMiddleware implements RouterMiddleware
{
/** @var Config */
protected $config;
/**
* SEOMiddleware constructor.
* @param Config $config
*/
public function __construct(
$config = null
) {
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return bool|null
*/
public function onRequest(ServerRequest $request, JsonResponse &$response): ?bool
{
new SEO\Defaults($this->config);
return null;
}
}
<?php
/**
* RouterProvider
* @author edgebal
*/
namespace Minds\Core\Router;
use Minds\Core\Di\Provider;
class RouterProvider extends Provider
{
public function register()
{
$this->di->bind('Router\Manager', function ($di) {
return new Manager();
}, [ 'useFactory' => true ]);
}
}
......@@ -2,6 +2,7 @@
/**
* SEO Manager
*/
namespace Minds\Core\SEO;
use Minds\Core\Events\Dispatcher;
......@@ -10,9 +11,15 @@ class Manager
{
public static $routes = [];
public static $defaults = [
'title' => ''
'title' => '',
];
public static function reset()
{
self::$routes = [];
self::$defaults = ['title' => ''];
}
/**
* Add a callback to provide metadata
* @param string $route
......@@ -49,9 +56,9 @@ class Manager
while ($route) {
$event = Dispatcher::trigger('seo:route', $route, [
'route' => $route,
'slugs' => array_reverse($slugs)
], false);
'route' => $route,
'slugs' => array_reverse($slugs),
], false);
if ($event !== false) {
$meta = $event;
......@@ -62,7 +69,7 @@ class Manager
$meta = call_user_func_array(self::$routes[$route], [array_reverse($slugs)]) ?: [];
break;
} else {
$slugs[] = substr($route, strrpos($route, '/')+1);
$slugs[] = substr($route, strrpos($route, '/') + 1);
if (strrpos($route, '/') === 0) {
$route = '/';
} else {
......
......@@ -2,6 +2,7 @@
/**
* Minds Session Manager
*/
namespace Minds\Core\Sessions;
use Minds\Common\Cookie;
......@@ -184,13 +185,13 @@ class Manager
$expires = time() + (60 * 60 * 24 * 30); // 30 days
$token = $this->jwtBuilder
//->issuedBy($this->config->get('site_url'))
//->canOnlyBeUsedBy($this->config->get('site_url'))
->setId($id, true)
->setExpiration($expires)
->set('user_guid', (string) $this->user->getGuid())
->sign(new Sha512, $this->config->get('sessions')['private_key'])
->getToken();
//->issuedBy($this->config->get('site_url'))
//->canOnlyBeUsedBy($this->config->get('site_url'))
->setId($id, true)
->setExpiration($expires)
->set('user_guid', (string) $this->user->getGuid())
->sign(new Sha512, $this->config->get('sessions')['private_key'])
->getToken();
$this->session = new Session();
$this->session
......@@ -198,7 +199,7 @@ class Manager
->setToken($token)
->setUserGuid($this->user->getGuid())
->setExpires($expires);
return $this;
}
......@@ -234,8 +235,10 @@ class Manager
*/
public function destroy($all = false)
{
$this->repository->delete($this->session, $all);
if ($this->session) {
$this->repository->delete($this->session, $all);
}
$this->cookie
->setName('minds_sess')
->setValue('')
......
<?php
/**
* @author: eiennohi.
*/
namespace Minds\Core\Util;
class StringValidator
{
protected const DOMAIN_REGEX = '/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/';
protected const HEX_COLOR_REGEX = '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/';
public static function isDomain(string $domain): bool
{
preg_match(self::DOMAIN_REGEX, $domain, $matches);
return $matches && $matches[0] === $domain;
}
public static function isHexColor(string $hexColor): bool
{
preg_match(self::HEX_COLOR_REGEX, $hexColor, $matches);
return $matches && count($matches) > 0;
}
}
......@@ -35,6 +35,10 @@ class Thresholds
$isPaywall = true;
}
if (!$user && $isPaywall) {
return false;
}
$threshold = $entity->getWireThreshold();
if (!$threshold && $isPaywall) {
......
......@@ -343,6 +343,15 @@ class Activity extends Entity
return $this;
}
/**
* Set the message
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* Sets the title
* @param string $title
......@@ -440,6 +449,18 @@ class Activity extends Entity
return $this;
}
/**
* Get the custom data
* @return array
*/
public function getCustom(): array
{
return [
$this->custom_type,
$this->custom_data
];
}
/**
* Set the to_guid
* @param int $guid
......
......@@ -23,5 +23,8 @@ class EntitiesProvider extends Provider
$this->di->bind('Entities\Factory', function ($di) {
return new EntitiesFactory();
}, ['useFactory' => true]);
$this->di->bind('PropagateProperties', function ($di) {
return new Entities\PropagateProperties();
}, ['useFactory' => true]);
}
}
......@@ -8,14 +8,6 @@ use Minds\Interfaces\Flaggable;
* File Entity
* @todo Do not inherit from ElggFile
* @package Minds\Entities\File
* @method array getExportableValues()
* @method mixed|null getFlag(string $flag)
* @method File setFlag(string $flag, mixed $value)
* @method void save(bool $index)
* @method array getWireTotals()
* @method mixed getWireThreshold()
* @method File setWireThreshold(int $wire_threshold)
* @method int getModeratorGUID()
*/
class File extends \ElggFile implements Flaggable
{
......
......@@ -422,6 +422,18 @@ class Group extends NormalizedEntity
$this->conversationDisabled = $value ? 1 : 0;
return $this;
}
/**
* Return the original `owner_guid` for the group.
* @return string guid
*/
public function getOwnerGuid()
{
$guids = $this->getOwnerGuids();
return $guids
? guids[0]
: $this->getOwnerObj()->guid;
}
/**
* Gets `owner_guids`
......
......@@ -31,6 +31,7 @@ class User extends \ElggUser
$this->attributes['tags'] = [];
$this->attributes['plus'] = 0; //TODO: REMOVE
$this->attributes['plus_expires'] = 0;
$this->attributes['pro_expires'] = 0;
$this->attributes['verified'] = 0;
$this->attributes['founder'] = 0;
$this->attributes['disabled_boost'] = 0;
......@@ -449,16 +450,16 @@ class User extends \ElggUser
public function addPinned($guid)
{
$pinned = $this->getPinnedPosts();
if (!$pinned) {
$pinned = [];
} elseif (count($pinned) > 2) {
array_shift($pinned);
}
if (array_search($guid, $pinned, true) === false) {
$pinned[] = (string) $guid;
$this->setPinnedPosts($pinned);
}
$this->setPinnedPosts($pinned);
}
/**
......@@ -489,10 +490,10 @@ class User extends \ElggUser
*/
public function setPinnedPosts($pinned)
{
if (count($pinned) > 3) {
$pinned = array_slice($pinned, 0, 3);
}
$this->pinned_posts = $pinned;
$maxPinnedPosts = $this->isPro() ? 12 : 3;
$this->pinned_posts = array_slice($pinned, -$maxPinnedPosts, null, false);
return $this;
}
......@@ -822,6 +823,7 @@ class User extends \ElggUser
$export['merchant'] = $this->getMerchant() ?: false;
$export['programs'] = $this->getPrograms();
$export['plus'] = (bool) $this->isPlus();
$export['pro'] = (bool) $this->isPro();
$export['verified'] = (bool) $this->verified;
$export['founder'] = (bool) $this->founder;
$export['disabled_boost'] = (bool) $this->disabled_boost;
......@@ -917,6 +919,32 @@ class User extends \ElggUser
return $this;
}
/**
* @param int $proExpires
* @return User
*/
public function setProExpires($proExpires)
{
$this->pro_expires = $proExpires;
return $this;
}
/**
* @return int
*/
public function getProExpires()
{
return $this->pro_expires ?: 0;
}
/**
* @return bool
*/
public function isPro()
{
return $this->getProExpires() >= time();
}
/**
* Gets the categories to which the user is subscribed.
*
......
......@@ -75,8 +75,8 @@ class EmailRewards
$validator = $_GET['validator'];
//$key = '.md';
//return;
if ($validator == sha1($campaign . 'gift-25-06-19.mdl' . $topic . $user->guid . Config::_()->get('emails_secret'))) {
$tokens = 5 * (10 ** 18);
if ($validator == sha1($campaign . 'gift-30-09-19.mdl' . $topic . $user->guid . Config::_()->get('emails_secret'))) {
$tokens = 2 * (10 ** 18);
$campaign = $validator; //hack
} else {
return;
......
<?php
namespace Spec\Minds\Core\Blogs\Delegates;
use Minds\Core\Blogs\Blog;
use Minds\Entities\Activity;
use Minds\Entities\Entity;
use PhpSpec\ObjectBehavior;
class PropagatePropertiesSpec extends ObjectBehavior
{
/** @var Blog */
protected $blog;
/** @var Activity */
protected $activity;
public function let(
Blog $blog,
Activity $activity
) {
$this->blog = $blog;
$this->activity = $activity;
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Blogs\Delegates\PropagateProperties');
}
public function it_should_propagate_changes_to_activity()
{
$this->blog->getTitle()->shouldBeCalled()->willReturn('New Title');
$this->activity->get('title')->shouldBeCalled()->willReturn('Old Title');
$this->activity->set('title', 'New Title')->shouldBeCalled();
$this->blog->getBody()->shouldBeCalled()->willReturn('body');
$this->activity->get('blurb')->shouldBeCalled()->willReturn('body');
$this->blog->getUrl()->shouldBeCalled()->willReturn('some url');
$this->activity->getURL()->shouldBeCalled()->willReturn('some url');
$this->blog->getIconUrl()->shouldBeCalled()->willReturn('some icon url');
$this->activity->get('thumbnail_src')->shouldBeCalled()->willReturn('some other url');
$this->activity->set('thumbnail_src', 'some icon url')->shouldBeCalled();
$this->toActivity($this->blog, $this->activity);
}
}
......@@ -5,6 +5,7 @@ namespace Spec\Minds\Core\Blogs;
use Minds\Core\Blogs\Blog;
use Minds\Core\Blogs\Delegates;
use Minds\Core\Blogs\Repository;
use Minds\Core\Entities\PropagateProperties;
use Minds\Core\Security\Spam;
use PhpSpec\ObjectBehavior;
......@@ -30,13 +31,16 @@ class ManagerSpec extends ObjectBehavior
/** @var Delegates\Search */
protected $search;
protected $propagateProperties;
public function let(
Repository $repository,
Delegates\PaywallReview $paywallReview,
Delegates\Slug $slug,
Delegates\Feeds $feeds,
Spam $spam,
Delegates\Search $search
Delegates\Search $search,
PropagateProperties $propagateProperties
) {
$this->beConstructedWith(
$repository,
......@@ -44,7 +48,8 @@ class ManagerSpec extends ObjectBehavior
$slug,
$feeds,
$spam,
$search
$search,
$propagateProperties
);
$this->repository = $repository;
......@@ -53,6 +58,7 @@ class ManagerSpec extends ObjectBehavior
$this->feeds = $feeds;
$this->spam = $spam;
$this->search = $search;
$this->propagateProperties = $propagateProperties;
}
public function it_is_initializable()
......@@ -244,6 +250,7 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(true);
$this->propagateProperties->from($blog)->shouldBeCalled();
$this
->update($blog)
->shouldReturn(true);
......
<?php
namespace Spec\Minds\Core\Entities\Delegates;
use Minds\Core\Blogs\Blog;
use Minds\Entities\Activity;
use PhpSpec\ObjectBehavior;
class PropagatePropertiesSpec extends ObjectBehavior
{
/** @var Blog */
protected $blog;
/** @var Activity */
protected $activity;
public function let(
Blog $blog,
Activity $activity
) {
$this->blog = $blog;
$this->activity = $activity;
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Entities\Delegates\PropagateProperties');
}
public function it_should_propagate_changes_to_activity()
{
$this->blog->getNsfw()->shouldBeCalled()->willReturn([1]);
$this->activity->getNsfw()->shouldBeCalled()->willReturn([]);
$this->activity->setNsfw([1])->shouldBeCalled();
$this->blog->getNsfwLock()->shouldBeCalled()->willReturn([1]);
$this->activity->getNsfwLock()->shouldBeCalled()->willReturn([]);
$this->activity->setNsfwLock([1])->shouldBeCalled();
$this->toActivity($this->blog, $this->activity);
}
public function it_should_propogate_properties_from_activity()
{
$this->activity->getNsfw()->shouldBeCalled()->willReturn([1]);
$this->blog->getNsfw()->shouldBeCalled()->willReturn([]);
$this->blog->setNsfw([1])->shouldBeCalled();
$this->activity->getNsfwLock()->shouldBeCalled()->willReturn([1]);
$this->blog->getNsfwLock()->shouldBeCalled()->willReturn([]);
$this->blog->setNsfwLock([1])->shouldBeCalled();
$this->fromActivity($this->activity, $this->blog);
}
}
<?php
namespace Spec\Minds\Core\Entities;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\EntitiesBuilder;
use Minds\Entities\Activity;
use Minds\Entities\Entity;
use PhpSpec\ObjectBehavior;
class PropagatePropertiesSpec extends ObjectBehavior
{
protected $db;
protected $save;
protected $entitiesBuilder;
protected $propagator;
protected $activity;
protected $entity;
public function let(
Call $db,
Save $save,
EntitiesBuilder $entitiesBuilder,
Activity $activity,
Entity $entity
) {
$this->beConstructedWith($db, $save, $entitiesBuilder);
$this->db = $db;
$this->save = $save;
$this->entitiesBuilder = $entitiesBuilder;
$this->activity = $activity;
$this->entity = $entity;
$this->clearPropogators();
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Entities\PropagateProperties');
}
public function it_should_call_from_activity()
{
$this->activity->get('entity_guid')->shouldBeCalled()->willReturn(1001);
$this->entitiesBuilder->single(1001)->shouldBeCalled()->willReturn($this->entity);
$this->from($this->activity);
}
public function it_should_call_to_activities()
{
$this->entity->getGUID()->shouldBeCalled()->willReturn(1002);
$this->db->getRow("activity:entitylink:1002")->shouldBeCalled()->willReturn([1001 => 12345]);
$this->entitiesBuilder->single(1001)->shouldBeCalled()->willReturn($this->activity);
$this->from($this->entity);
}
}
<?php
namespace Spec\Minds\Core\Feeds\Delegates;
use Minds\Entities\Activity;
use Minds\Entities\Entity;
use PhpSpec\ObjectBehavior;
class PropagatePropertiesSpec extends ObjectBehavior
{
/** @var Entity */
protected $entity;
/** @var Activity */
protected $activity;
public function let(
Entity $entity,
Activity $activity
) {
$this->entity = $entity;
$this->activity = $activity;
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Feeds\Delegates\PropagateProperties');
}
public function it_should_propagate_changes_to_activity()
{
$this->entity->getModeratorGuid()->shouldBeCalled()->willReturn('12345');
$this->activity->getModeratorGuid()->shouldBeCalled()->willReturn('6789');
$this->activity->setModeratorGuid('12345')->shouldBeCalled();
$this->entity->getTimeModerated()->shouldBeCalled()->willReturn(12345);
$this->activity->getTimeModerated()->shouldBeCalled()->willReturn(6789);
$this->activity->setTimeModerated(12345)->shouldBeCalled();
$this->toActivity($this->entity, $this->activity);
}
public function it_should_propogate_properties_from_activity()
{
$this->activity->getModeratorGuid()->shouldBeCalled()->willReturn('12345');
$this->entity->getModeratorGuid()->shouldBeCalled()->willReturn('6789');
$this->entity->setModeratorGuid('12345')->shouldBeCalled();
$this->activity->getTimeModerated()->shouldBeCalled()->willReturn(12345);
$this->entity->getTimeModerated()->shouldBeCalled()->willReturn(6789);
$this->entity->setTimeModerated(12345)->shouldBeCalled();
$this->entity->get('owner_guid')->shouldBeCalled()->willReturn(123);
$this->activity->get('owner_guid')->shouldBeCalled()->willReturn(123);
$this->activity->isPayWall()->shouldBeCalled()->willReturn(true);
$this->activity->get('access_id')->shouldBeCalled()->willReturn(0);
$this->entity->get('access_id')->shouldBeCalled()->willReturn(2);
$this->entity->set('access_id', 0)->shouldBeCalled();
$this->entity->getSubtype()->shouldBeCalled()->willReturn('image');
$this->entity->get('hidden')->shouldBeCalled()->willReturn(false);
$this->entity->set('hidden', true)->shouldBeCalled();
$this->fromActivity($this->activity, $this->entity);
}
}
......@@ -2,6 +2,7 @@
namespace Spec\Minds\Core\Feeds\Firehose;
use Minds\Core\Entities\PropagateProperties;
use PhpSpec\ObjectBehavior;
use Minds\Entities\User;
use Minds\Core\Feeds\Firehose\Manager;
......@@ -25,12 +26,10 @@ class ManagerSpec extends ObjectBehavior
protected $topFeedsManager;
/** @var ModerationCache */
protected $moderationCache;
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Call */
protected $db;
/** @var Save */
protected $save;
/** @var PropagateProperties */
protected $propagateProperties;
protected $guids = [
'968599624820461570', '966142563226488850', '966145446911152135',
......@@ -43,25 +42,22 @@ class ManagerSpec extends ObjectBehavior
User $user,
TopFeedsManager $topFeedsManager,
ModerationCache $moderationCache,
EntitiesBuilder $entitiesBuilder,
Call $db,
Save $save
Save $save,
PropagateProperties $propagateProperties
) {
$this->user = $user;
$this->topFeedsManager = $topFeedsManager;
$this->moderationCache = $moderationCache;
$this->entitiesBuilder = $entitiesBuilder;
$this->db = $db;
$this->save = $save;
$this->propagateProperties = $propagateProperties;
$this->user->getGUID()->willReturn(123);
$this->beConstructedWith(
$this->topFeedsManager,
$this->moderationCache,
$this->entitiesBuilder,
$this->db,
$this->save
$this->save,
$this->propagateProperties
);
}
......@@ -77,10 +73,10 @@ class ManagerSpec extends ObjectBehavior
$response = new Response($activities);
$this->topFeedsManager->getList([
'moderation_user' => $this->user,
'exclude_moderated' => true,
'moderation_reservations' => null,
])
'moderation_user' => $this->user,
'exclude_moderated' => true,
'moderation_reservations' => null,
])
->shouldBeCalled()
->willReturn($response);
......@@ -112,89 +108,22 @@ class ManagerSpec extends ObjectBehavior
'exclude_moderated' => true,
'moderation_reservations' => null,
])
->shouldBeCalled()
->willReturn($response);
->shouldBeCalled()
->willReturn($response);
$this->getList()->shouldBeLike($response->map(function ($entity) {
return $entity->getEntity();
}));
}
public function it_should_save_moderated_activites(Entity $activity)
{
$time = time();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(false);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setModeratorGuid('123')->shouldBeCalled();
$activity->setTimeModerated($time)->shouldBeCalled();
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
public function it_should_save_reported_activites(Entity $activity)
public function it_should_save_and_propogate(Entity $activity)
{
$time = time();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(false);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->setModeratorGuid('123')->shouldBeCalled();
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
public function it_should_save_an_attachment(Entity $activity, Image $image)
{
$time = time();
$image->setModeratorGuid(123)->shouldBeCalled();
$image->setTimeModerated($time)->shouldBeCalled();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$this->entitiesBuilder->single(1)->shouldBeCalled()->willReturn($image);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(1);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->setModeratorGuid(123)->shouldBeCalled();
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->setEntity($image)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
public function it_should_save_a_blog(Blog $blog)
{
$time = time();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$blog->getType()->shouldBeCalled()->willReturn('object');
$blog->getGuid()->shouldBeCalled()->willReturn(1);
$blog->setTimeModerated($time)->shouldBeCalled();
$blog->setModeratorGuid('123')->shouldBeCalled();
$this->save->save()->shouldBeCalled();
$this->save->setEntity($blog)->shouldBeCalled()->willReturn($this->save);
$this->save($blog, $this->user, $time);
}
public function it_should_save_a_linked_entity(Entity $activity, Entity $parent)
{
$time = time();
$parent->setTimeModerated($time)->shouldBeCalled();
$parent->setModeratorGuid('123')->shouldBeCalled();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()
->willReturn([2 => $parent]);
$this->entitiesBuilder->single(2)->shouldBeCalled()->willReturn($parent);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(false);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->setModeratorGuid('123')->shouldBeCalled();
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->setEntity($parent)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->propagateProperties->from($activity)->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
......
<?php
namespace Spec\Minds\Core\Media\Delegates;
use Minds\Entities\Activity;
use Minds\Entities\Image;
use PhpSpec\ObjectBehavior;
class PropagatePropertiesSpec extends ObjectBehavior
{
/** @var Image */
protected $entity;
/** @var Activity */
protected $activity;
public function let(
Image $entity,
Activity $activity
) {
$this->entity = $entity;
$this->activity = $activity;
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Media\Delegates\PropagateProperties');
}
public function it_should_propagate_changes_to_activity()
{
$this->entity->get('title')->shouldBeCalled()->willReturn('new title');
$this->activity->getMessage()->shouldBeCalled()->willReturn('old title');
$this->activity->setMessage('new title')->shouldBeCalled();
$activityParameters = [
'batch',
[
'key1' => 'value1',
'key2' => 'value2'
]
];
$this->entity->getActivityParameters()->shouldBeCalled()->willReturn($activityParameters);
$this->activity->getCustom()->shouldBeCalled()->willReturn([]);
$this->activity->setCustom($activityParameters[0], $activityParameters[1])->shouldBeCalled();
$this->toActivity($this->entity, $this->activity);
}
public function it_should_propogate_properties_from_activity()
{
$this->activity->getMessage()->shouldBeCalled()->willReturn('new title');
$this->entity->get('title')->shouldbeCalled()->willReturn('old title');
$this->entity->set('title', 'new title')->shouldBeCalled();
$this->fromActivity($this->activity, $this->entity);
}
}
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.
......@@ -476,7 +476,8 @@ $CONFIG->set('features', [
'cassandra-notifications' => true,
'dark-mode' => true,
'allow-comments-toggle' => false,
'permissions' => false
'permissions' => false,
'pro' => false,
]);
$CONFIG->set('email', [
......@@ -572,3 +573,9 @@ $CONFIG->set('gitlab', [
],
'private_key' => ''
]);
$CONFIG->set('pro', [
'root_domains' => ['minds.com', 'www.minds.com', 'localhost'],
'subdomain_suffix' => 'minds.com',
'dynamodb_table_name' => 'traefik',
]);