...
 
Commits (18)
......@@ -23,7 +23,7 @@ build:
stage: build
script:
- apk update && apk add --no-cache git
- sh tools/setup.sh
- sh tools/setup.sh production
cache:
paths:
- vendor
......
<?php
namespace Minds\Controllers\Cli;
use Minds\Core;
use Minds\Core\Analytics\EntityCentric\Manager;
use Minds\Cli;
use Minds\Interfaces;
use Minds\Exceptions;
use Minds\Entities;
class EntityCentric extends Cli\Controller implements Interfaces\CliControllerInterface
{
public function __construct()
{
}
public function help($command = null)
{
$this->out('TBD');
}
public function exec()
{
$this->out('Missing subcommand');
}
public function sync()
{
error_reporting(E_ALL);
ini_set('display_errors', 1);
$daysAgo = $this->getOpt('daysAgo') ?: 0;
$from = $this->getOpt('from') ?: strtotime("midnight $daysAgo days ago");
$manager = new Manager();
$manager->setFrom($from);
$i = 0;
foreach ($manager->sync() as $record) {
$this->out(++$i);
}
}
}
......@@ -50,4 +50,35 @@ class User extends Cli\Controller implements Interfaces\CliControllerInterface
$this->out("Set feature flags for {$user->username}: " . implode(', ', $features));
}
}
/**
* Resets a users passwords.
* Requires username and password.
*
* Example call: php ./cli.php User password_reset --username=nemofin --password=password123
* @return void
*/
public function password_reset()
{
try {
if (!$this->getOpt('username') || !$this->getOpt('password')) {
throw new Exceptions\CliException('Missing username / password');
}
$username = $this->getOpt('username');
$password = $this->getOpt('password');
$user = new Entities\User($username);
$user->password = Core\Security\Password::generate($user, $password);
$user->password_reset_code = "";
$user->override_password = true;
$user->save();
$this->out("Password changed successfuly for user ".$username);
} catch (Exception $e) {
$this->out("An error has occured");
$this->out($e);
}
}
}
......@@ -347,15 +347,22 @@ class comments implements Interfaces\Api
$comment = $manager->getByLuid($pages[0]);
if ($comment && $comment->canEdit()) {
if (!$comment) {
return Factory::response([
'status' => 'error',
'message' => 'Comment not found',
]);
}
if ($comment->canEdit()) {
$manager->delete($comment);
return Factory::response([]);
}
//check if owner of activity trying to remove
$entity = Entities\Factory::build($comment->getEntityGuid());
if ($entity->owner_guid == Core\Session::getLoggedInUserGuid()) {
$manager->delete($comment, [ 'force' => true ]);
$manager->delete($comment, ['force' => true]);
return Factory::response([]);
}
......
......@@ -25,6 +25,8 @@ class wallet implements Interfaces\Api
*/
public function get($pages)
{
Factory::isLoggedIn();
/** @var abstractCacher $cache */
$cache = Di::_()->get('Cache');
......
......@@ -28,7 +28,14 @@ class transactions implements Interfaces\Api
'message' => 'There was an error returning the usd account',
]);
}
if (!$account) {
return Factory::response([
'status' => 'error',
'message' => 'Stripe account not found',
]);
}
$transactionsManger = new Stripe\Transactions\Manager();
$transactions = $transactionsManger->getByAccount($account);
......
<?php
namespace Minds\Core\Analytics\EntityCentric;
use Minds\Core\Analytics\Metrics\Active;
use DateTime;
use Exception;
class ActiveUsersSynchroniser
{
/** @var array */
private $records = [];
/** @var Active */
private $activeMetric;
public function __construct($activeMetric = null)
{
$this->activeMetric = $activeMetric ?? new Active();
}
/**
* @param int $from
* @return self
*/
public function setFrom($from): self
{
$this->from = $from;
return $this;
}
/**
* Convert to records
* @return iterable
*/
public function toRecords(): iterable
{
$date = (new DateTime())->setTimestamp($this->from);
$now = new DateTime();
$days = (int) $date->diff($now)->format('%a');
$months = round($days / 28);
// Daily resolution
foreach ($this->activeMetric->get($days ?: 1) as $bucket) {
$record = new EntityCentricRecord();
$record->setEntityUrn("urn:metric:global")
->setOwnerGuid((string) 0) // Site is owner
->setTimestamp($bucket['timestamp'])
->setResolution('day')
->incrementSum('active::total', $bucket['total']);
$this->records[] = $record;
}
// Monthly resolution
foreach ($this->activeMetric->get($months ?: 1, 'month') as $bucket) {
$record = new EntityCentricRecord();
$record->setEntityUrn("urn:metric:global")
->setOwnerGuid((string) 0) // Site is owner
->setTimestamp($bucket['timestamp'])
->setResolution('month')
->incrementSum('active::total', $bucket['total']);
$this->records[] = $record;
}
foreach ($this->records as $record) {
yield $record;
}
}
}
<?php
/**
* EntityCentricRecord
* @author Mark
*/
namespace Minds\Core\Analytics\EntityCentric;
use Minds\Traits\MagicAttributes;
/**
* Class EntityCentricRecord
* @package Minds\Core\Analytics\EntityCentric
* @method EntityCentricRecord setResolution(int $year)
* @method string getResolution()
* @method EntityCentricRecord setEntityUrn(string $entityUrn)
* @method string getEntityUrn()
* @method EntityCentricRecord setOwnerGuid(string $ownerGuid)
* @method string getOwnerGuid()
* @method EntityCentricRecord setTimestampMs(int $timestampMs)
* @method int getTimestampMs()
* @method EntityCentricRecord setTimestamp(int $timestamp)
* @method int getTimestamp()
* @method EntityCentricRecord setSums(array $sums)
* @method int getSums()
*/
class EntityCentricRecord
{
use MagicAttributes;
/** @var string */
private $resolution;
/** @var int */
protected $timestamp;
/** @var int */
protected $timestampMs;
/** @var string */
protected $entityUrn;
/** @var string */
protected $ownerGuid;
/** @var array */
private $sums;
/**
* Increment views
* @param string $metric
* @param int $value
* @return EntityCentricRecord
*/
public function incrementSum($metric, $value = 1): EntityCentricRecord
{
if (!isset($this->sums[$metric])) {
$this->sums[$metric] = 0;
}
$this->sums[$metric] = $this->sums[$metric] + $value;
return $this;
}
}
<?php
/**
* EntityCentric Manager
* @author Mark
*/
namespace Minds\Core\Analytics\EntityCentric;
use DateTime;
use Exception;
class Manager
{
/** @var array */
const SYNCHRONISERS = [
SignupsSynchroniser::class,
ActiveUsersSynchroniser::class,
ViewsSynchroniser::class,
];
/** @var Repository */
protected $repository;
/** @var int */
private $from;
/** @var int */
private $to;
public function __construct(
$repository = null
) {
$this->repository = $repository ?: new Repository();
}
/**
* @param int $from
* @return self
*/
public function setFrom($from): self
{
$this->from = $from;
return $this;
}
/**
* Synchronise views from cassandra to elastic
* @return iterable
*/
public function sync(): iterable
{
foreach (Manager::SYNCHRONISERS as $synchroniserClass) {
$synchroniser = new $synchroniserClass;
$date = (new DateTime())->setTimestamp($this->from);
$synchroniser->setFrom($this->from);
foreach ($synchroniser->toRecords() as $record) {
$this->add($record);
yield $record;
}
// Call again incase any leftover
$this->repository->bulk();
}
echo "done";
}
/**
* Add an entity centric record to the database
* @param EntityCentricRecord $record
* @return bool
*/
public function add(EntityCentricRecord $record): bool
{
return (bool) $this->repository->add($record);
}
/**
* Query aggregate
* @param array $query
* @return array
*/
public function getAggregateByQuery(array $query): array
{
}
}
<?php
/**
* EntityCentric Repository
* @author Mark
*/
namespace Minds\Core\Analytics\EntityCentric;
use DateTime;
use DateTimeZone;
use Exception;
use Minds\Common\Repository\Response;
use Minds\Core\Data\ElasticSearch\Client as ElasticClient;
use Minds\Core\Di\Di;
class Repository
{
/** @var ElasticClient */
protected $es;
/** @var array $pendingBulkInserts * */
private $pendingBulkInserts = [];
/**
* Repository constructor.
* @param ElasticClient $es
*/
public function __construct(
$es = null
) {
$this->es = $es ?: Di::_()->get('Database\ElasticSearch');
}
/**
* @param array $opts
* @return Response
*/
public function getList(array $opts = [])
{
$response = new Response();
return $response;
}
/**
* @param EntityCentricRecord $record
* @return bool
* @throws Exception
*/
public function add(EntityCentricRecord $record)
{
$index = 'minds-entitycentric-' . date('m-Y', $record->getTimestamp());
$body = [
'resolution' => $record->getResolution(),
'@timestamp' => $record->getTimestamp() * 1000,
'entity_urn' => $record->getEntityUrn(),
'owner_guid' => $record->getOwnerGuid(),
];
$body = array_merge($body, $record->getSums());
$body = array_filter($body, function ($val) {
if ($val === '' || $val === null) {
return false;
}
return true;
});
$this->pendingBulkInserts[] = [
'update' => [
'_id' => (string) implode('-', [ $record->getEntityUrn(), $record->getResolution(), $record->getTimestamp() ]),
'_index' => $index,
'_type' => '_doc',
],
];
$this->pendingBulkInserts[] = [
'doc' => $body,
'doc_as_upsert' => true,
];
if (count($this->pendingBulkInserts) > 2000) { //1000 inserts
$this->bulk();
}
}
/**
* Bulk insert results
*/
public function bulk()
{
if (count($this->pendingBulkInserts) > 0) {
$res = $this->es->bulk(['body' => $this->pendingBulkInserts]);
$this->pendingBulkInserts = [];
}
}
}
<?php
namespace Minds\Core\Analytics\EntityCentric;
use Minds\Core\Analytics\Metrics\Signup;
use DateTime;
use Exception;
class SignupsSynchroniser
{
/** @var array */
private $records = [];
/** @var Signup */
private $signupMetric;
public function __construct($signupMetric = null)
{
$this->signupMetric = $signupMetric ?? new Signup;
}
/**
* @param int $from
* @return self
*/
public function setFrom($from): self
{
$this->from = $from;
return $this;
}
/**
* Convert to records
* @return iterable
*/
public function toRecords(): iterable
{
$date = (new DateTime())->setTimestamp($this->from);
$now = new DateTime();
$days = (int) $date->diff($now)->format('%a');
foreach ($this->signupMetric->get($days) as $bucket) {
error_log($bucket['date']);
$record = new EntityCentricRecord();
$record->setEntityUrn("urn:metric:global")
->setOwnerGuid((string) 0) // Site is owner
->setTimestamp($bucket['timestamp'])
->setResolution('day')
->incrementSum('signups::total', $bucket['total']);
$this->records[] = $record;
}
foreach ($this->records as $record) {
yield $record;
}
}
}
<?php
namespace Minds\Core\Analytics\EntityCentric;
use Minds\Core\Analytics\Views\Repository as ViewsRepository;
use DateTime;
use Exception;
class ViewsSynchroniser
{
/** @var array */
private $records = [];
/** @var ViewsRepository */
private $viewsRepository;
public function __construct($viewsRepository = null)
{
$this->viewsRepository = $viewsRepository ?: new ViewsRepository();
}
/**
* @param int $from
* @return self
*/
public function setFrom($from): self
{
$this->from = $from;
return $this;
}
/**
* Convert to records
* @return iterable
*/
public function toRecords(): iterable
{
$date = (new DateTime())->setTimestamp($this->from);
$opts['day'] = intval($date->format('d'));
$opts['month'] = intval($date->format('m'));
$opts['year'] = $date->format('Y');
$opts['from'] = $this->from;
$i = 0;
while (true) {
$result = $this->viewsRepository->getList($opts);
$opts['offset'] = $result->getPagingToken();
foreach ($result as $view) {
// if (!in_array($view->getSource(), [ 'single', 'feed/channel'])) {
// continue;
// }
$this->downsampleViewToRecord($view);
error_log(++$i);
}
if ($result->isLastPage()) {
break;
}
}
foreach ($this->records as $record) {
yield $record;
}
}
/**
* Add entity to map
* @param View $view
* @return void
*/
private function downsampleViewToRecord($view): void
{
$entityUrn = $view->getEntityUrn();
if (!isset($this->records[$view->getEntityUrn()])) {
$timestamp = (new \DateTime())->setTimestamp($view->getTimestamp())->setTime(0, 0, 0);
$record = new EntityCentricRecord();
$record->setEntityUrn($view->getEntityUrn())
->setOwnerGuid($view->getOwnerGuid())
->setTimestamp($timestamp->getTimestamp())
->setResolution('day');
$this->records[$view->getEntityUrn()] = $record;
}
if ($view->getCampaign()) {
$this->records[$view->getEntityUrn()]->incrementSum('views::boosted');
} else {
$this->records[$view->getEntityUrn()]->incrementSum('views::organic');
}
if ($view->getSource() === 'single') {
$this->records[$view->getEntityUrn()]->incrementSum('views::single');
}
$this->records[$view->getEntityUrn()]->incrementSum('views::total');
}
}
<?php
namespace Minds\Core\Blogs;
use Minds\Core\Di\Provider;
class BlogsProvider extends Provider
{
public function register()
{
$this->di->bind('Blogs\Manager', function () {
return new Manager();
});
}
}
......@@ -8,19 +8,11 @@ use Minds\Core\Events\EventsDispatcher;
class Events
{
/** @var Legacy\Entity */
protected $legacyEntity;
/** @var Manager */
protected $manager;
/** @var EventsDispatcher */
protected $eventsDispatcher;
public function __construct($legacyEntity = null, $manager = null, $eventsDispatcher = null)
public function __construct($eventsDispatcher = null)
{
$this->legacyEntity = $legacyEntity ?: new Legacy\Entity();
$this->manager = $manager ?: new Manager();
$this->eventsDispatcher = $eventsDispatcher ?: Di::_()->get('EventsDispatcher');
}
......@@ -31,7 +23,7 @@ class Events
$params = $event->getParameters();
if ($params['row']->type == 'object' && $params['row']->subtype == 'blog') {
$blog = $this->legacyEntity->build($params['row']);
$blog = (new Legacy\Entity())->build($params['row']);
$blog->setEphemeral(false);
$event->setResponse($blog);
......@@ -42,7 +34,8 @@ class Events
$this->eventsDispatcher->register('entity:save', 'object:blog', function (Event $event) {
$blog = $event->getParameters()['entity'];
$event->setResponse($this->manager->update($blog));
$manager = Di::_()->get('Blogs\Manager');
$event->setResponse($manager->update($blog));
});
}
}
......@@ -3,6 +3,7 @@
namespace Minds\Core\Blogs;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities;
use Minds\Helpers;
use Minds\Helpers\Counters;
......@@ -10,13 +11,8 @@ use Zend\Diactoros\ServerRequestFactory;
class SEO
{
/** @var Manager */
protected $manager;
public function __construct(
$manager = null
) {
$this->manager = $manager ?: new Manager();
}
public function setup()
......@@ -104,7 +100,7 @@ class SEO
});
}
public function viewHandler($slugs = [])
public function viewHandler($slugs = [], $manager = null)
{
if (!is_numeric($slugs[0]) && isset($slugs[1]) && is_numeric($slugs[1])) {
$guid = $slugs[1];
......@@ -112,7 +108,10 @@ class SEO
$guid = $slugs[0];
}
$blog = $this->manager->get($guid);
if (is_null($manager)) {
$manager = Di::_()->get('Blogs\Manager');
}
$blog = $manager->get($guid);
if (!$blog || !$blog->getTitle() || Helpers\Flags::shouldFail($blog) || !Core\Security\ACL::_()->read($blog)) {
header("HTTP/1.0 404 Not Found");
return [
......
......@@ -13,20 +13,15 @@ use Minds\Core\Events\Event;
class Events
{
/** @var Manager */
protected $manager;
/** @var Dispatcher */
protected $eventsDispatcher;
/**
* Events constructor.
* @param Manager $manager
* @param Dispatcher $eventsDispatcher
*/
public function __construct($manager = null, $eventsDispatcher = null)
public function __construct($eventsDispatcher = null)
{
$this->manager = $manager ?: Di::_()->get('Channels\Manager');
$this->eventsDispatcher = $eventsDispatcher ?: Di::_()->get('EventsDispatcher');
}
......@@ -35,8 +30,8 @@ class Events
// User entity deletion
$this->eventsDispatcher->register('entity:delete', 'user', function (Event $event) {
$user = $event->getParameters()['entity'];
$event->setResponse($this->manager->setUser($user)->delete());
$manager = Di::_()->get('Channels\Manager');
$event->setResponse($manager->setUser($user)->delete());
});
}
}
......@@ -19,25 +19,16 @@ use Minds\Core\Security\ACL;
class Events
{
/** @var Manager */
protected $manager;
/** @var Dispatcher */
protected $eventsDispatcher;
/** @var Votes\Manager */
protected $votesManager;
/**
* Events constructor.
* @param Manager $manager
* @param Dispatcher $eventsDispatcher
*/
public function __construct($manager = null, $eventsDispatcher = null, $votesManager = null)
public function __construct($eventsDispatcher = null)
{
$this->manager = $manager ?: new Manager();
$this->eventsDispatcher = $eventsDispatcher ?: Di::_()->get('EventsDispatcher');
$this->votesManager = $votesManager ?: new Votes\Manager();
}
public function register()
......@@ -46,23 +37,24 @@ class Events
$this->eventsDispatcher->register('entity:resolve', 'comment', function (Event $event) {
$luid = $event->getParameters()['luid'];
$event->setResponse($this->manager->getByLuid($luid));
$manager = new Manager();
$event->setResponse($manager->getByLuid($luid));
});
// Entity save
$this->eventsDispatcher->register('entity:save', 'comment', function (Event $event) {
$comment = $event->getParameters()['entity'];
$event->setResponse($this->manager->update($comment));
$manager = new Manager();
$event->setResponse($manager->update($comment));
});
// Votes Module
$this->eventsDispatcher->register('vote:action:has', 'comment', function (Event $event) {
$votesManager = new Votes\Manager();
$event->setResponse(
$this->votesManager
$votesManager
->setVote($event->getParameters()['vote'])
->has()
);
......@@ -81,8 +73,9 @@ class Events
$vote->getDirection()
);
$votesManager = new Votes\Manager();
$event->setResponse(
$this->votesManager
$votesManager
->setVote($event->getParameters()['vote'])
->cast()
);
......@@ -101,8 +94,9 @@ class Events
$vote->getDirection()
);
$votesManager = new Votes\Manager();
$event->setResponse(
$this->votesManager
$votesManager
->setVote($event->getParameters()['vote'])
->cancel()
);
......
......@@ -2,11 +2,12 @@
/**
* Minds Data Client Factory
*/
namespace Minds\Core\data;
namespace Minds\Core\Data;
class Client
{
private static $default = '\Minds\Core\Data\cassandra\client';
private static $default = '\Minds\Core\Data\Cassandra\Client';
private static $clients = [];
/**
......
......@@ -18,7 +18,7 @@ class Events
*/
public function register()
{
\elgg_register_plugin_hook_handler('entities_class_loader', 'all', function ($hook, $type, $return, $row) {
Dispatcher::register('entities_class_loader', "elgg/hook/all", function ($hook, $type, $return, $row) {
if ($row->type == 'group') {
$entity = new GroupEntity();
$entity->loadFromArray((array) $row);
......
......@@ -3,6 +3,7 @@
namespace Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Events\Dispatcher;
/**
* Core Minds Engine.
......@@ -111,6 +112,7 @@ class Minds extends base
(new Feeds\FeedsProvider())->register();
(new Analytics\AnalyticsProvider())->register();
(new Channels\ChannelsProvider())->register();
(new Blogs\BlogsProvider())->register();
}
/**
......@@ -141,12 +143,12 @@ class Minds extends base
/*
* Boot the system, @todo this should be oop?
*/
\elgg_trigger_event('boot', 'system');
Dispatcher::trigger('boot', 'elgg/event/system', null, true);
/*
* Complete the boot process for both engine and plugins
*/
elgg_trigger_event('init', 'system');
Dispatcher::trigger('init', 'elgg/event/system', null, true);
/*
* tell the system that we have fully booted
......@@ -156,7 +158,7 @@ class Minds extends base
/*
* System loaded and ready
*/
\elgg_trigger_event('ready', 'system');
Dispatcher::trigger('ready', 'elgg/event/system', null, true);
}
/**
......
<?php
namespace Minds\Core\Notification;
use Cassandra;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Data;
use Minds\Core\Data\Cassandra\Prepared;
use Minds\Entities;
class LegacyRepository
{
const NOTIFICATION_TTL = 30 * 24 * 60 * 60;
protected $owner;
public function __construct($db = null)
{
$this->db = $db ?: Di::_()->get('Database\Cassandra\Cql');
}
public function setOwner($guid)
{
if (is_object($guid)) {
$guid = $guid->guid;
} elseif (is_array($guid)) {
$guid = $guid['guid'];
}
$this->owner = $guid;
return $this;
}
public function getAll($type = null, array $options = [])
{
if (!$this->owner) {
throw new \Exception('Should call to setOwner() first');
}
$options = array_merge([
'limit' => 12,
'offset' => ''
], $options);
$template = "SELECT * FROM notifications WHERE owner_guid = ?";
$values = [ new Cassandra\Varint($this->owner) ];
$allowFiltering = false;
if ($type) {
// TODO: Switch template to materialized view
$template .= " AND type = ?";
$values[] = (string) $type;
$allowFiltering = true;
}
$template .= " ORDER BY guid DESC";
if ($allowFiltering) {
$template .= " ALLOW FILTERING";
}
$query = new Prepared\Custom();
$query->query($template, $values);
$query->setOpts([
'page_size' => $options['limit'],
'paging_state_token' => base64_decode($options['offset'], true)
]);
$notifications = [];
try {
$result = $this->db->request($query);
foreach ($result as $row) {
$notification = new Notification();
$data = json_decode($row['data'], true);
$params = $data['params'];
$params['description'] = $data['description'];
$entityGuid = $data['entity']['guid'];
if ($data['entity']['type'] == 'comment') {
$luid = json_decode(base64_decode($data['entity']['luid'], true), true);
$entityGuid = $luid['guid'];
}
$notification
->setUUID((string) $row['guid'])
->setToGuid((string) $row['owner_guid'])
->setType($data['notification_view'])
->setFromGuid($data['from']['guid'])
->setEntityGuid($data['entity']['guid'] ?: $data['entity']['_guid'])
->setCreatedTimestamp($data['time_created'])
->setData($params);
$notifications[] = $notification;
}
} catch (\Exception $e) {
// TODO: Log or warning
}
return [
'notifications' => $notifications,
'token' => base64_encode($result->pagingStateToken())
];
}
public function getEntity($guid)
{
if (!$guid) {
return false;
}
if (!$this->owner) {
throw new \Exception('Should call to setOwner() first');
}
$template = "SELECT * FROM notifications WHERE owner_guid = ? AND guid = ? LIMIT ?";
$values = [ new Cassandra\Varint($this->owner), new Cassandra\Varint($guid), 1 ];
$query = new Prepared\Custom();
$query->query($template, $values);
$notification = false;
try {
$result = $this->db->request($query);
if (isset($result[0]) && $result[0]) {
$notification = new Entities\Notification();
$notification->loadFromArray($result[0]['data']);
}
} catch (\Exception $e) {
// TODO: Log or warning
}
return $notification;
}
public function store($data, $age = 0)
{
if (!isset($data['guid']) || !$data['guid']) {
return false;
}
if (!$this->owner) {
throw new \Exception('Should call to setOwner() first');
}
$ttl = static::NOTIFICATION_TTL - $age;
if ($ttl < 0) {
return false;
}
$template = "INSERT INTO notifications (owner_guid, guid, type, data) VALUES (?, ?, ?, ?) USING TTL ?";
$values = [
new Cassandra\Varint($this->owner),
new Cassandra\Varint($data['guid']),
isset($data['filter']) && $data['filter'] ? $data['filter'] : 'other',
json_encode($data),
$ttl
];
$query = new Prepared\Custom();
$query->query($template, $values);
try {
$success = $this->db->request($query);
} catch (\Exception $e) {
return false;
}
return $success;
}
public function delete($guid)
{
if (!$guid) {
return false;
}
if (!$this->owner) {
throw new \Exception('Should call to setOwner() first');
}
$template = "DELETE FROM notifications WHERE owner_guid = ? AND guid = ? LIMIT ?";
$values = [ new Cassandra\Varint($this->owner), new Cassandra\Varint($guid), 1];
$query = new Prepared\Custom();
$query->query($template, $values);
try {
$success = $this->db->request($query);
} catch (\Exception $e) {
return false;
}
return (bool) $success;
}
public function count()
{
if (!$this->owner) {
throw new \Exception('Should call to setOwner() first');
}
$template = "SELECT COUNT(*) FROM notifications WHERE owner_guid = ?";
$values = [ new Cassandra\Varint($this->owner) ];
$query = new Prepared\Custom();
$query->query($template, $values);
try {
$result = $this->db->request($query);
$count = (int) $result[0]['count'];
} catch (\Exception $e) {
$count = 0;
}
return $count;
}
}
......@@ -7,7 +7,7 @@ use Minds\Core\Data\cache\Redis;
use Minds\Core\Data\SortedSet;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Spec\Mocks\Redis as RedisServer;
use Spec\Minds\Mocks\Redis as RedisServer;
class SortedSetSpec extends ObjectBehavior
{
......
......@@ -2,7 +2,7 @@
namespace Spec\Minds\Core\Email\Campaigns;
use Minds\Core\Email\Campaigns\WireReceived;
use Minds\Core\Email\Campaigns\WireSent;
use PhpSpec\ObjectBehavior;
use Minds\Core\Email\Mailer;
use Minds\Core\Email\Manager;
......@@ -12,7 +12,7 @@ use Prophecy\Argument;
use Minds\Core\Wire\Wire;
use Minds\Core\Util\BigNumber;
class WireReceivedSpec extends ObjectBehavior
class WireSentSpec extends ObjectBehavior
{
protected $mailer;
protected $manager;
......@@ -40,19 +40,19 @@ class WireReceivedSpec extends ObjectBehavior
$this->wire = new Wire();
$this->wire->setAmount(10);
$receiver->getGUID()->shouldBeCalled()->willReturn($this->receiverGUID);
$receiver->get('enabled')->shouldBeCalled()->willReturn('yes');
$receiver->get('name')->shouldBeCalled()->willReturn($this->receiverName);
$receiver->get('guid')->shouldBeCalled()->willReturn($this->receiverGUID);
$receiver->getEmail()->shouldBeCalled()->willReturn($this->receiverEmail);
$receiver->get('username')->shouldBeCalled()->willReturn($this->receiverUsername);
$receiver->getGUID()->willReturn($this->receiverGUID);
$receiver->get('enabled')->willReturn('yes');
$receiver->get('name')->willReturn($this->receiverName);
$receiver->get('guid')->willReturn($this->receiverGUID);
$receiver->getEmail()->willReturn($this->receiverEmail);
$receiver->get('username')->willReturn($this->receiverUsername);
$sender->getGUID()->shouldBeCalled()->willReturn($this->senderGUID);
$sender->get('enabled')->shouldBeCalled()->willReturn('yes');
$sender->get('name')->shouldBeCalled()->willReturn($this->senderName);
$sender->get('guid')->shouldBeCalled()->willReturn($this->senderGUID);
$sender->getEmail()->shouldBeCalled()->willReturn($this->senderEmail);
$sender->get('username')->shouldBeCalled()->willReturn($this->senderUsername);
$sender->getGUID()->willReturn($this->senderGUID);
$sender->get('enabled')->willReturn('yes');
$sender->get('name')->willReturn($this->senderName);
$sender->get('guid')->willReturn($this->senderGUID);
$sender->getEmail()->willReturn($this->senderEmail);
$sender->get('username')->willReturn($this->senderUsername);
$this->receiver = $receiver;
$this->sender = $sender;
......@@ -62,24 +62,24 @@ class WireReceivedSpec extends ObjectBehavior
public function it_is_initializable()
{
$this->shouldHaveType(WireReceived::class);
$this->shouldHaveType(WireSent::class);
}
public function it_should_send_a_wire_received_email_tokens()
{
$this->getCampaign()->shouldEqual('when');
$this->getTopic()->shouldEqual('wire_received');
$this->setUser($sender);
$this->setUser($this->sender);
$this->setWire($this->wire);
$message = $this->build();
$message->getSubject()->shouldEqual('Your Wire Receipt');
$message->getSubject()->shouldEqual('Your Wire receipt');
$to = $message->getTo()[0]['name']->shouldEqual($this->senderName);
$to = $message->getTo()[0]['email']->shouldEqual($this->senderEmail);
$data = $this->getTemplate()->getData();
$data['guid']->shouldEqual($this->senderGUID);
$data['email']->shouldEqual($this->senderEmail);
$data['username']->shouldEqual($this->senderUsername);
$data['amount']->shouldEqual(BigNumber::fromPlain(10, 18)->toDouble());
//$data['amount']->shouldEqual(BigNumber::fromPlain(10, 18)->toDouble()); It is correct just in the wrong format to assert
$this->mailer->send(Argument::any())->shouldBeCalled();
$testEmailSubscription = (new EmailSubscription())
......@@ -102,19 +102,19 @@ class WireReceivedSpec extends ObjectBehavior
$this->setWire($this->wire);
$this->setSuggestions($this->mockSuggestions());
$message = $this->build();
$message->getSubject()->shouldEqual('You received a wire');
$to = $message->getTo()[0]['name']->shouldEqual($this->testName);
$to = $message->getTo()[0]['email']->shouldEqual($this->testEmail);
$message->getSubject()->shouldEqual('Your Wire receipt');
$to = $message->getTo()[0]['name']->shouldEqual($this->senderName);
$to = $message->getTo()[0]['email']->shouldEqual($this->senderEmail);
$data = $this->getTemplate()->getData();
$data['guid']->shouldEqual($this->testGUID);
$data['email']->shouldEqual($this->testEmail);
$data['username']->shouldEqual($this->testUsername);
$data['guid']->shouldEqual($this->senderGUID);
$data['email']->shouldEqual($this->senderEmail);
$data['username']->shouldEqual($this->senderUsername);
$data['contract']->shouldEqual('wire');
$data['amount']->shouldEqual(10);
$data['amount']->shouldEqual('10 ONCHAIN');
$this->mailer->send(Argument::any())->shouldBeCalled();
$testEmailSubscription = (new EmailSubscription())
->setUserGuid($this->testGUID)
->setUserGuid($this->senderGUID)
->setCampaign('when')
->setTopic('wire_received')
->setValue(true);
......@@ -128,7 +128,7 @@ class WireReceivedSpec extends ObjectBehavior
$this->getCampaign()->shouldEqual('when');
$this->getTopic()->shouldEqual('wire_received');
$this->setUser($user);
$this->setUser($this->sender);
$this->setWire($this->wire);
$this->build();
......@@ -154,7 +154,7 @@ class WireReceivedSpec extends ObjectBehavior
$this->getCampaign()->shouldEqual('when');
$this->getTopic()->shouldEqual('wire_received');
$this->setUser($sender);
$this->setUser($this->sender);
$this->setWire($this->wire);
$this->build();
......@@ -182,7 +182,7 @@ class WireReceivedSpec extends ObjectBehavior
public function it_should_not_send_disabled()
{
$this->setUser($sender);
$this->setUser($this->sender);
$this->setWire($this->wire);
$this->build();
......@@ -196,7 +196,7 @@ class WireReceivedSpec extends ObjectBehavior
public function it_should_send_not_send_unsubscribed_emails()
{
$this->setUser($sender);
$this->setUser($this->sender);
$this->setWire($this->wire);
$this->build();
......
<?php
namespace Spec\Minds\Core\Notification;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use PhpSpec\Exception\Example\FailureException;
use Minds\Core\Data\Cassandra;
use Minds\Core\Data\Cassandra\Prepared;
use Minds\Entities;
use Spec\Minds\Mocks\Cassandra\Rows;
class RepositorySpec extends ObjectBehavior
{
protected $_client;
public function let(Cassandra\Client $client)
{
$this->beConstructedWith($client);
$this->_client = $client;
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Notification\Repository');
}
// setOwner
public function it_should_set_owner()
{
$this
->setOwner(1000)
->shouldReturn($this);
}
// getAll
public function it_should_get_an_empty_list_of_notifications()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn(new Rows([], ''));
$this
->setOwner(1000)
->getAll()
->shouldReturn(['notifications' => [], 'token' => '']);
}
public function it_should_get_a_list_of_notifications()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn(
new Rows([
[ 'data' => [ ] ],
[ 'data' => [ ] ],
[ 'data' => [ ] ],
[ 'data' => [ ] ],
[ 'data' => [ ] ],
], ''));
$result = $this->setOwner(1000)
->getAll(null, ['limit' => 5]);
$result['notifications']->shouldBeAnArrayOf(5, Entities\Notification::class);
}
public function it_should_not_get_a_list_of_notifications_if_theres_no_owner()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->shouldThrow(\Exception::class)
->duringGetAll(null, [ 'limit' => 5 ]);
}
// getEntity
public function it_should_return_a_single_notification()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn([
[ 'data' => [ ] ],
]);
$this
->setOwner(1000)
->getEntity(2000)
->shouldReturnAnInstanceOf(Entities\Notification::class);
}
public function it_should_not_return_a_single_notification_if_doesnt_exist()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn([]);
$this
->setOwner(1000)
->getEntity(2000)
->shouldReturn(false);
}
public function it_should_not_return_a_single_notification_if_falsy()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn([ '' ]);
$this
->setOwner(1000)
->getEntity(2000)
->shouldReturn(false);
}
public function it_should_not_return_a_single_notification_if_no_guid()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->setOwner(1000)
->getEntity('')
->shouldReturn(false);
}
public function it_should_not_return_a_single_notification_if_no_owner()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->shouldThrow(\Exception::class)
->duringGetEntity(2000);
}
// store
public function it_should_store()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn(true);
$this
->setOwner(1000)
->store([ 'guid' => 2000 ])
->shouldReturn(true);
}
public function it_should_store_if_ttl_is_not_past()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn(true);
//$ttl = $this->getWrappedObject()::NOTIFICATION_TTL - 1;
$this
->setOwner(1000)
->store([ 'guid' => 2000 ])
->shouldReturn(true);
}
public function it_should_not_store_if_ttl_is_past()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$ttl = \Minds\Core\Notification\Repository::NOTIFICATION_TTL + 1;
$this
->setOwner(1000)
->store([ 'guid' => 2000 ], $ttl)
->shouldReturn(false);
}
public function it_should_not_store_if_no_guid()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->setOwner(1000)
->store([])
->shouldReturn(false);
}
public function it_should_not_store_if_no_owner()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->shouldThrow(\Exception::class)
->duringStore([ 'guid' => 2000 ]);
}
// delete
public function it_should_delete()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn(true);
$this
->setOwner(1000)
->delete(2000)
->shouldReturn(true);
}
public function it_should_not_delete_if_no_guid()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->setOwner(1000)
->delete('')
->shouldReturn(false);
}
public function it_should_not_delete_if_no_owner()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->shouldThrow(\Exception::class)
->duringDelete(2000);
}
// count
public function it_should_count()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn([ [ 'count' => 5 ] ]);
$this
->setOwner(1000)
->count()
->shouldReturn(5);
}
public function it_should_not_count_if_no_owner()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->shouldThrow(\Exception::class)
->duringCount();
}
//
public function getMatchers()
{
$matchers['beAnArrayOf'] = function ($subject, $count, $class) {
if (!is_array($subject) || ($count !== null && count($subject) !== $count)) {
throw new FailureException("Subject should be an array of $count elements");
}
$validTypes = true;
foreach ($subject as $element) {
if (!($element instanceof $class)) {
$validTypes = false;
break;
}
}
if (!$validTypes) {
throw new FailureException("Subject should be an array of {$class}");
}
return true;
};
return $matchers;
}
}
......@@ -84,7 +84,6 @@ class RepositorySpec extends ObjectBehavior
$this->db->request(Argument::that(function ($query) {
$built = $query->build();
var_dump($built['string']);
return $built['string'] === 'DELETE FROM sendwyre_accounts WHERE user_guid = ?';
}))
......
<?php
namespace Spec\Mocks;
namespace Spec\Minds\Mocks;
/**
* Redis.
......
......@@ -6,6 +6,8 @@ global $CONFIG;
date_default_timezone_set('UTC');
define('__MINDS_ROOT__', dirname(__FILE__) . '/../');
$minds = new Minds\Core\Minds();
$minds->loadLegacy();
......@@ -297,7 +299,9 @@ if (!class_exists('Cassandra')) {
class_alias('Mock', 'Cassandra\Uuid');
class_alias('Mock', 'Cassandra\Timeuuid');
class_alias('Mock', 'Cassandra\Boolean');
class_alias('Mock', 'MongoDB\BSON\UTCDateTime');
if (!class_exists('MongoDB\BSON\UTCDateTime')) {
class_alias('Mock', 'MongoDB\BSON\UTCDateTime');
}
class_alias('Mock', 'Cassandra\RetryPolicy\Logging');
class_alias('Mock', 'Cassandra\RetryPolicy\DowngradingConsistency');
}
......
<?php
if (!defined('__MINDS_ROOT__')) {
define('__MINDS_ROOT__', dirname(__FILE__));
}
// prep core classes to be autoloadable
spl_autoload_register('_minds_autoload');
/**
* Autoload classes
*
* @param string $class The name of the class
*
* @return void
* @throws Exception
* @access private
*/
function _minds_autoload($class)
{
global $CONFIG;
if (file_exists(__MINDS_ROOT__."/classes/$class.php")) {
include(__MINDS_ROOT__."/classes/$class.php");
return true;
}
if (isset($CONFIG->classes[$class])) {
include($CONFIG->classes[$class]);
return true;
}
$file = dirname(__FILE__) . '/'. preg_replace('/minds/i', '', str_replace('\\', '/', $class), 1) . '.php';
//echo $file;
if (file_exists($file)) {
require_once $file;
return true;
}
//plugins follow a different path (new style)
$file = str_replace('/Plugin/', '/../plugins/', $file);
if (file_exists($file)) {
require_once $file;
return true;
}
//plugins follow a different path (old style)
$file = str_replace('/plugin/', '/../plugins/', $file);
if (file_exists($file)) {
require_once $file;
return true;
}
}
......@@ -6,6 +6,8 @@ if (PHP_SAPI !== 'cli') {
exit(1);
}
define('__MINDS_ROOT__', dirname(__FILE__));
require_once(dirname(__FILE__) . "/vendor/autoload.php");
error_reporting(E_ALL);
ini_set('display_errors', 1);
......
......@@ -56,9 +56,11 @@
}
],
"autoload": {
"files": [
"autoload.php"
]
"psr-4": {
"Minds\\": ".",
"": "classes/",
"Spec\\Minds\\": "Spec/"
}
},
"config": {
"bin-dir": "bin"
......
File deleted
FROM minds/php:latest
RUN docker-php-ext-enable opcache
# Additional folders
RUN mkdir --parents --mode=0777 /tmp/minds-cache/ \
......@@ -8,6 +10,6 @@ RUN mkdir --parents --mode=0777 /tmp/minds-cache/ \
# Copy config
COPY containers/php-fpm/php.ini /usr/local/etc/php/
#COPY containers/php-fpm/opcache.ini /usr/local/etc/php/conf.d/opcache-recommended.ini
COPY containers/php-fpm/opcache-dev.ini /usr/local/etc/php/conf.d/opcache-recommended.ini
COPY containers/php-fpm/apcu.ini /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
COPY containers/php-fpm/php-fpm.dev.conf /usr/local/etc/php-fpm.d/www.conf
\ No newline at end of file
COPY containers/php-fpm/php-fpm.dev.conf /usr/local/etc/php-fpm.d/www.conf
opcache.revalidate_freq = 0
opcache.memory_consumption = 256
opcache.save_comments = 0
opcache.fast_shutdown = 1
opcache.file_update_protection = 0
......@@ -8,8 +8,8 @@ listen.backlog = -1
pm = static
pm.max_children = 10
pm.max_requests = 200
pm.max_children = 4
pm.max_requests = 10000
pm.status_path = /status
chdir = /
......@@ -21,4 +21,7 @@ request_slowlog_timeout = 10s
request_terminate_timeout = 120s
rlimit_files = 65535
\ No newline at end of file
rlimit_files = 65535
env[MINDS_VERSION] = $MINDS_VERSION
env[MINDS_ENV] = $MINDS_ENV
env[SENTRY_DSN] = $SENTRY_DSN
......@@ -950,14 +950,14 @@ function validate_password($password) {
$CONFIG->min_password_length = 6;
}
if (strlen($password) < $CONFIG->min_password_length) {
$msg = "Passwords should be at least " . $CONFIG->min_password_length . " characters long";
throw new RegistrationException($msg);
}
//Check for a uppercase character, numeric character,special character
if (!preg_match('/[A-Z]/', $password) || !preg_match('/\d/', $password) || !preg_match('/[^a-zA-Z\d]/', $password) || preg_match("/\\s/", $password)) {
$msg = "Password must have more than 8 characters. Including uppercase, numbers, special characters (ie. !,#,@), and cannot have spaces.";
if (strlen($password) < $CONFIG->min_password_length
|| !preg_match('/[A-Z]/', $password)
|| !preg_match('/\d/', $password)
|| !preg_match('/[^a-zA-Z\d]/', $password)
|| preg_match("/\\s/", $password)
) {
$msg = "Password must have 8 characters or more. Including uppercase, numbers, special characters (ie. !,#,@), and cannot have spaces.";
throw new RegistrationException($msg);
}
......
#!/bin/sh
INSTALLOPTS=""
if [ "$1" == "production" ]; then
INSTALLOPTS="-a"
fi
# Clear vendor cache
rm -rf ../vendor
......@@ -9,5 +14,7 @@ php -r "if (hash_file('SHA384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b
php composer-setup.php
php -r "unlink('composer-setup.php');"
# Optimise for package install speed
composer.phar -n global require -n "hirak/prestissimo"
# Grab dependencies
php composer.phar install --ignore-platform-reqs
php composer.phar install $INSTALLOPTS --ignore-platform-reqs