Browse Source

Code review

Apply PHP fixes
Add model scopes
Split document analyzer class
master
Richard Dern 4 months ago
parent
commit
b27710d7d6
86 changed files with 1597 additions and 1672 deletions
  1. +15
    -11
      .php_cs
  2. +4
    -5
      app/Actions/Fortify/CreateNewUser.php
  3. +1
    -1
      app/Actions/Fortify/PasswordValidationRules.php
  4. +1
    -3
      app/Actions/Fortify/ResetUserPassword.php
  5. +3
    -5
      app/Actions/Fortify/UpdateUserPassword.php
  6. +6
    -10
      app/Actions/Fortify/UpdateUserProfileInformation.php
  7. +20
    -0
      app/Analyzers/Analyzer.php
  8. +520
    -0
      app/Analyzers/HtmlAnalyzer.php
  9. +5
    -1
      app/Analyzers/PdfAnalyzer.php
  10. +4
    -11
      app/Console/Commands/PurgeReadFeedItems.php
  11. +2
    -9
      app/Console/Commands/UpdateDocuments.php
  12. +1
    -4
      app/Console/Commands/UpdateFeeds.php
  13. +0
    -6
      app/Console/Kernel.php
  14. +3
    -5
      app/Contracts/ImportAdapter.php
  15. +3
    -7
      app/Exceptions/Handler.php
  16. +0
    -1
      app/Exceptions/UserDoesNotExistsException.php
  17. +3
    -1
      app/Http/Controllers/Controller.php
  18. +8
    -14
      app/Http/Controllers/DocumentController.php
  19. +3
    -7
      app/Http/Controllers/FeedController.php
  20. +20
    -20
      app/Http/Controllers/FeedItemController.php
  21. +23
    -33
      app/Http/Controllers/FolderController.php
  22. +14
    -35
      app/Http/Controllers/GroupController.php
  23. +11
    -8
      app/Http/Controllers/HighlightController.php
  24. +7
    -9
      app/Http/Controllers/HomeController.php
  25. +10
    -10
      app/Http/Kernel.php
  26. +4
    -3
      app/Http/Middleware/Authenticate.php
  27. +0
    -1
      app/Http/Middleware/CheckForMaintenanceMode.php
  28. +0
    -1
      app/Http/Middleware/EncryptCookies.php
  29. +3
    -4
      app/Http/Middleware/RedirectIfAuthenticated.php
  30. +1
    -3
      app/Http/Middleware/SetLang.php
  31. +1
    -1
      app/Http/Middleware/TrustProxies.php
  32. +0
    -1
      app/Http/Middleware/VerifyCsrfToken.php
  33. +10
    -12
      app/Http/Requests/Documents/StoreRequest.php
  34. +6
    -6
      app/Http/Requests/Folders/SetPermissionsRequest.php
  35. +10
    -11
      app/Http/Requests/Folders/UpdateRequest.php
  36. +2
    -2
      app/Http/Requests/Groups/InviteUserRequest.php
  37. +0
    -1
      app/Http/Requests/StoreHighlightRequest.php
  38. +0
    -3
      app/ImportAdapters/Cyca.php
  39. +11
    -12
      app/Jobs/EnqueueDocumentUpdate.php
  40. +11
    -13
      app/Jobs/EnqueueFeedUpdate.php
  41. +14
    -12
      app/Models/Bookmark.php
  42. +74
    -33
      app/Models/Document.php
  43. +82
    -41
      app/Models/Feed.php
  44. +93
    -16
      app/Models/FeedItem.php
  45. +14
    -14
      app/Models/FeedItemState.php
  46. +46
    -45
      app/Models/Folder.php
  47. +47
    -47
      app/Models/Group.php
  48. +2
    -1
      app/Models/Highlight.php
  49. +5
    -5
      app/Models/IgnoredFeed.php
  50. +0
    -7
      app/Models/Observers/BookmarkObserver.php
  51. +0
    -17
      app/Models/Observers/DocumentObserver.php
  52. +1
    -4
      app/Models/Observers/FeedItemObserver.php
  53. +1
    -20
      app/Models/Observers/FeedObserver.php
  54. +0
    -11
      app/Models/Observers/FolderObserver.php
  55. +0
    -3
      app/Models/Observers/GroupObserver.php
  56. +2
    -11
      app/Models/Observers/IgnoredFeedObserver.php
  57. +1
    -5
      app/Models/Observers/UserObserver.php
  58. +10
    -10
      app/Models/Permission.php
  59. +0
    -17
      app/Models/Policies/DocumentPolicy.php
  60. +43
    -95
      app/Models/Policies/FolderPolicy.php
  61. +7
    -25
      app/Models/Policies/GroupPolicy.php
  62. +49
    -530
      app/Models/Traits/Document/AnalysesDocument.php
  63. +30
    -31
      app/Models/Traits/Feed/AnalysesFeed.php
  64. +5
    -4
      app/Models/Traits/Folder/BuildsTree.php
  65. +11
    -13
      app/Models/Traits/Folder/CreatesDefaultFolders.php
  66. +24
    -25
      app/Models/Traits/User/HasFeeds.php
  67. +98
    -104
      app/Models/Traits/User/HasFolders.php
  68. +62
    -66
      app/Models/Traits/User/HasGroups.php
  69. +18
    -19
      app/Models/User.php
  70. +11
    -11
      app/Notifications/AsksToJoinGroup.php
  71. +7
    -10
      app/Notifications/DocumentUpdated.php
  72. +4
    -8
      app/Notifications/FeedUpdated.php
  73. +11
    -11
      app/Notifications/InvitedToJoinGroup.php
  74. +9
    -6
      app/Notifications/UnreadItemsChanged.php
  75. +1
    -7
      app/Providers/AppServiceProvider.php
  76. +0
    -5
      app/Providers/AuthServiceProvider.php
  77. +1
    -6
      app/Providers/BladeServiceProvider.php
  78. +0
    -2
      app/Providers/BroadcastServiceProvider.php
  79. +1
    -5
      app/Providers/EventServiceProvider.php
  80. +0
    -5
      app/Providers/FortifyServiceProvider.php
  81. +3
    -8
      app/Providers/LangServiceProvider.php
  82. +2
    -4
      app/Providers/ObserversServiceProvider.php
  83. +6
    -17
      app/Providers/RouteServiceProvider.php
  84. +17
    -13
      app/Services/Exporter.php
  85. +36
    -31
      app/Services/Importer.php
  86. +3
    -2
      config/analyzers.php

+ 15
- 11
.php_cs View File

@ -1,16 +1,20 @@
<?php
return PhpCsFixer\Config::create()
->setRules(array(
'@PSR2' => true,
'array_indentation' => true,
'array_syntax' => array('syntax' => 'short'),
'combine_consecutive_unsets' => true,
'method_separation' => true,
->setRules([
'@PhpCsFixer' => true,
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'combine_consecutive_unsets' => true,
'method_separation' => true,
'no_multiline_whitespace_before_semicolons' => true,
'single_quote' => true,
'binary_operator_spaces' => array(
'single_quote' => true,
'binary_operator_spaces' => [
'align_double_arrow' => true,
'align_equals' => true
)
));
'align_equals' => true,
],
'declare_equal_normalize' => [
'space' => 'single',
],
'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false]
]);

+ 4
- 5
app/Actions/Fortify/CreateNewUser.php View File

@ -14,20 +14,19 @@ class CreateNewUser implements CreatesNewUsers
/**
* Validate and create a newly registered user.
*
* @param array $input
* @return \App\Models\User
*/
public function create(array $input)
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => $this->passwordRules(),
])->validate();
return User::create([
'name' => $input['name'],
'email' => $input['email'],
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]);
}


+ 1
- 1
app/Actions/Fortify/PasswordValidationRules.php View File

@ -13,6 +13,6 @@ trait PasswordValidationRules
*/
protected function passwordRules()
{
return ['required', 'string', new Password, 'confirmed'];
return ['required', 'string', new Password(), 'confirmed'];
}
}

+ 1
- 3
app/Actions/Fortify/ResetUserPassword.php View File

@ -13,9 +13,7 @@ class ResetUserPassword implements ResetsUserPasswords
/**
* Validate and reset the user's forgotten password.
*
* @param mixed $user
* @param array $input
* @return void
* @param mixed $user
*/
public function reset($user, array $input)
{


+ 3
- 5
app/Actions/Fortify/UpdateUserPassword.php View File

@ -13,17 +13,15 @@ class UpdateUserPassword implements UpdatesUserPasswords
/**
* Validate and update the user's password.
*
* @param mixed $user
* @param array $input
* @return void
* @param mixed $user
*/
public function update($user, array $input)
{
Validator::make($input, [
'current_password' => ['required', 'string'],
'password' => $this->passwordRules(),
'password' => $this->passwordRules(),
])->after(function ($validator) use ($user, $input) {
if (! Hash::check($input['current_password'], $user->password)) {
if (!Hash::check($input['current_password'], $user->password)) {
$validator->errors()->add('current_password', __('The provided password does not match your current password.'));
}
})->validateWithBag('updatePassword');


+ 6
- 10
app/Actions/Fortify/UpdateUserProfileInformation.php View File

@ -12,9 +12,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/**
* Validate and update the given user's profile information.
*
* @param mixed $user
* @param array $input
* @return void
* @param mixed $user
*/
public function update($user, array $input)
{
@ -31,13 +29,13 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
'lang' => [
'required',
Rule::in(array_keys(config('lang')))
Rule::in(array_keys(config('lang'))),
],
'theme' => [
'nullable',
Rule::in(['light', 'dark', 'auto'])
]
Rule::in(['light', 'dark', 'auto']),
],
])->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email &&
@ -48,7 +46,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
'name' => $input['name'],
'email' => $input['email'],
'lang' => $input['lang'],
'theme' => $input['theme']
'theme' => $input['theme'],
])->save();
}
}
@ -56,9 +54,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/**
* Update the given verified user's profile information.
*
* @param mixed $user
* @param array $input
* @return void
* @param mixed $user
*/
protected function updateVerifiedUser($user, array $input)
{


+ 20
- 0
app/Analyzers/Analyzer.php View File

@ -3,6 +3,7 @@
namespace App\Analyzers;
use App\Models\Document;
use Illuminate\Http\Client\Response;
abstract class Analyzer
{
@ -27,6 +28,13 @@ abstract class Analyzer
*/
protected $details;
/**
* Provides temporary access to response to analyzers.
*
* @var \Illuminate\Http\Client\Response
*/
protected $response;
/**
* Associate Cyca's document being analized.
*
@ -53,6 +61,18 @@ abstract class Analyzer
return $this;
}
/**
* HTTP response when fetching document.
*
* @return self
*/
public function setResponse(Response $response)
{
$this->response = $response;
return $this;
}
/**
* Store details on disk.
*/


+ 520
- 0
app/Analyzers/HtmlAnalyzer.php View File

@ -0,0 +1,520 @@
<?php
namespace App\Analyzers;
use App\Models\Feed;
use DomDocument;
use DOMElement;
use DOMXPath;
use Elphin\IcoFileLoader\IcoFileService;
use ForceUTF8\Encoding as UTF8;
use Illuminate\Support\Facades\Http;
use League\Uri\Http as UriHttp;
use League\Uri\UriResolver;
use SimplePie;
use Storage;
use Str;
/**
* Extract information from a HTML file.
*/
class HtmlAnalyzer extends Analyzer
{
/**
* Provides temporary access to DOM document to analyzers.
*
* @var DOMDocument
*/
private $domDocument;
/**
* Provides temporary access to <meta> tags to analyzers.
*
* @var array
*/
private $metaTags = [];
/**
* Provides temporary access to <link> tags to analyzers.
*
* @var array
*/
private $linkTags = [];
/**
* Analyzes document.
*/
public function analyze()
{
if (empty($this->body)) {
return;
}
$this->createDomDocument();
$this->findTitle();
$this->findMetaTags();
$this->findLinkTags();
$this->findBestFavicon();
$this->discoverFeeds();
}
/**
* Ensure specified url is absolute by using a base URL defined earlier.
*
* @param string $source
*
* @return string
*/
protected function makeUrlAbsolute($source)
{
$baseUri = UriHttp::createFromString((string) $this->response->effectiveUri());
$relativeUri = UriHttp::createFromString($source);
$newUri = UriResolver::resolve($relativeUri, $baseUri);
return (string) $newUri;
}
/**
* Ensures string doesn't contain any "undesirable" characters, such as
* extra-spaces or line-breaks. This is not a purifying method. Only basic
* cleanup is done here.
*
* @param string $string
* @param mixed $stripTags
*
* @return string
*/
protected function cleanupString($string, $stripTags = true)
{
if (empty($string)) {
return null;
}
$string = UTF8::toUTF8($string, UTF8::ICONV_TRANSLIT);
$string = preg_replace('#[\s\t\r\n]+#', ' ', $string);
$string = html_entity_decode($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$string = str_replace('&apos;', "'", $string);
if ($stripTags) {
$string = strip_tags(trim($string));
}
return trim($string);
}
/**
* Create a DOM document from document's body.
*/
protected function createDomDocument()
{
$this->body = mb_convert_encoding($this->body, 'HTML-ENTITIES', 'UTF-8');
libxml_use_internal_errors(true);
$this->domDocument = new DomDocument('1.0', 'UTF-8');
$this->domDocument->loadHtml($this->body);
libxml_clear_errors();
}
/**
* Find nodes corresponding to specified XPath query.
*
* @param string $xpathQuery
*
* @return DomNodeList
*/
protected function findNodes($xpathQuery)
{
$xpath = new DOMXPath($this->domDocument);
return $xpath->query($xpathQuery);
}
/**
* Find first node corresponding to specified XPath query.
*
* @param string $xpathQuery
*
* @return DomNode
*/
protected function findFirstNode($xpathQuery)
{
$xpath = new DOMXPath($this->domDocument);
$nodes = $xpath->query($xpathQuery);
if ($nodes->length === 0) {
return null;
}
return $nodes->item(0);
}
/**
* Discover feeds for this document, store them and link them.
*/
protected function discoverFeeds()
{
$toSync = $this->document->feeds()->get()->pluck('id')->all();
$alternateLinks = data_get($this->linkTags, 'Alternate', []);
// Hard guessing some paths (.rss, ./rss. ./.rss for instance)
$potentialNames = ['feed', 'rss', 'atom'];
foreach ($potentialNames as $potentialName) {
$alternateLinks[] = [
'type' => 'application/xml',
'href' => sprintf('.%s', $potentialName),
];
$alternateLinks[] = [
'type' => 'application/xml',
'href' => sprintf('./%s', $potentialName),
];
$alternateLinks[] = [
'type' => 'application/xml',
'href' => sprintf('./.%s', $potentialName),
];
}
foreach ($alternateLinks as $alternateLink) {
if (empty($alternateLink['type']) || !in_array($alternateLink['type'], config('cyca.feedTypes'))) {
continue;
}
try {
$url = $this->makeUrlAbsolute($alternateLink['href']);
} catch (\Exception $ex) {
// Malformed URL
continue;
}
$client = new SimplePie();
$client->force_feed(true);
$client->set_feed_url($url);
$client->set_stupidly_fast(true);
$client->enable_cache(false);
if (!$client->init()) {
continue;
}
$feed = Feed::firstOrCreate(['url' => $url]);
if (!in_array($feed->id, $toSync)) {
$toSync[] = $feed->id;
}
}
$this->document->feeds()->sync($toSync);
}
/**
* Place in an array all attributes of a specific DOMElement.
*
* @return array
*/
private function domElementToArray(DOMElement $node)
{
$data = [];
foreach ($node->attributes as $attribute) {
$key = Str::slug($attribute->localName);
$value = $this->cleanupString($attribute->nodeValue);
$data[$key] = $value;
}
return $data;
}
/**
* Find document's title.
*/
private function findTitle()
{
$node = $this->findFirstNode('//head/title');
if (empty($node)) {
return null;
}
$this->document->title = $this->cleanupString($node->nodeValue);
}
/**
* Find and parse meta tags.
*/
private function findMetaTags()
{
$nodes = $this->findNodes('//head/meta');
$this->metaTags = [];
foreach ($nodes as $node) {
$this->parseMetaTag($node);
}
$this->metaTags = collect($this->metaTags)->sortKeys()->all();
//TODO: Format description
$this->document->description = data_get($this->metaTags, 'meta.Description.content');
}
/**
* Parse a meta tag and return a formated array.
*/
private function parseMetaTag(DOMElement $node)
{
$data = $this->domElementToArray($node);
if (empty($data)) {
return;
}
$group = 'nonStandard';
$name = null;
if (!empty($data['charset'])) {
$group = 'charset';
$name = 'charset';
} elseif (!empty($data['name'])) {
$group = 'meta';
$name = $data['name'];
$data['originalName'] = $data['name'];
unset($data['name']);
} elseif (!empty($data['property'])) {
$group = 'properties';
$name = $data['property'];
$data['originalName'] = $data['property'];
unset($data['property']);
} elseif (!empty($data['http-equiv'])) {
$group = 'pragma';
$name = $data['http-equiv'];
$data['originalName'] = $data['http-equiv'];
unset($data['http-equiv']);
}
$name = Str::studly(str_replace(':', '_', $name));
if (!empty($name)) {
switch ($name) {
// Handle specific meta tag formatting here
default:
$this->metaTags[$group][$name] = $data;
break;
}
} else {
$this->metaTags[$group][] = $data;
}
}
/**
* Find and parse link tags.
*/
private function findLinkTags()
{
$nodes = $this->findNodes('//head/link');
$this->linkTags = [];
foreach ($nodes as $node) {
$this->parseLinkTag($node);
}
$this->linkTags = collect($this->linkTags)->sortKeys()->all();
}
/**
* Parse a link tag and return a formated array.
*
* @return array
*/
private function parseLinkTag(DOMElement $node)
{
$data = $this->domElementToArray($node);
if (empty($data)) {
return;
}
$group = 'Others';
if (!empty($data['rel'])) {
$group = Str::studly($data['rel']);
}
$this->linkTags[$group][] = $data;
}
/**
* Fetch all link tags marked as being a favicon, then determine which one
* is best suited to be the one.
*/
private function findBestFavicon()
{
$defaultFaviconUrl = $this->makeUrlAbsolute('/favicon.ico');
$potentialIcons = [];
$links = $this->linkTags;
foreach ($links as $group => $tags) {
foreach ($tags as $tag) {
if (!empty($tag['rel']) && in_array($tag['rel'], config('cyca.faviconRels'))) {
$potentialIcons[] = $tag['href'];
}
}
}
$potentialIcons[] = $defaultFaviconUrl;
$topWidth = 0;
$selectedIcon = null;
foreach ($potentialIcons as $potentialIcon) {
$url = $this->makeUrlAbsolute($potentialIcon);
try {
$response = Http::timeout(10)->get($url);
} catch (\Exception $ex) {
report($ex);
continue;
}
if (!$response->ok()) {
continue;
}
$body = $response->body();
$filePath = sprintf('%s/favicon_%s', $this->document->getStoragePath(), md5($body));
Storage::put($filePath, $body);
$mimetype = Storage::mimetype($filePath);
if (!$this->isValidFavicon($body, $mimetype)) {
Storage::delete($filePath);
continue;
}
$width = $this->getImageWidth($body, $mimetype);
if ($width >= $topWidth) {
$topWidth = $width;
$selectedIcon = $filePath;
} else {
Storage::delete($filePath);
}
}
if (!empty($selectedIcon)) {
$this->favicon_path = $selectedIcon;
}
}
/**
* Determine if favicon has a valid mime type.
*
* @param string $mimetype
* @param mixed $body
*
* @return bool
*/
private function isValidFavicon($body, $mimetype)
{
if (!in_array($mimetype, config('cyca.faviconTypes'))) {
return false;
}
return $this->isValidImage($body, $mimetype);
}
/**
* Determine if favicon is a valid image. Mime type is used to adjust tests.
*
* @param string $mimeType
* @param mixed $body
*
* @return bool
*/
private function isValidImage($body, $mimeType)
{
switch ($mimeType) {
case 'image/x-icon':
case 'image/vnd.microsoft.icon':
$loader = new IcoFileService();
try {
$loader->extractIcon($body, 16, 16);
} catch (\Exception $ex) {
return false;
}
return true;
case 'image/svg':
case 'image/svg+xml':
$im = new Imagick();
try {
$im->readImageBlob($body);
} catch (\Exception $ex) {
$im->destroy();
report($ex);
return false;
}
return true;
default:
$res = @imagecreatefromstring($body);
if (!$res) {
return false;
}
return true;
}
}
/**
* Obtain width of image.
*
* @param string $mimeType
* @param mixed $body
*
* @return int
*/
private function getImageWidth($body, $mimeType)
{
switch ($mimeType) {
case 'image/x-icon':
case 'image/vnd.microsoft.icon':
return 16;
case 'image/svg':
case 'image/svg+xml':
return 1024;
default:
$infos = @getimagesizefromstring($body);
if (!$infos) {
return 0;
}
return $infos[0];
}
}
}

app/Analyzers/PDF.php → app/Analyzers/PdfAnalyzer.php View File


+ 4
- 11
app/Console/Commands/PurgeReadFeedItems.php View File

@ -2,8 +2,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\FeedItem;
use Illuminate\Console\Command;
class PurgeReadFeedItems extends Command
{
@ -23,8 +23,6 @@ class PurgeReadFeedItems extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@ -38,18 +36,13 @@ class PurgeReadFeedItems extends Command
*/
public function handle()
{
$oldest = now()->subDays(config('cyca.maxOrphanAge.feeditems'));
$oldFeedItems = FeedItem::whereDoesntHave('feedItemStates', function($query) {
$query->where('is_read', false);
})->where('published_at', '<', $oldest)
->orWhereNull('published_at')
->get();
$oldest = now()->subDays(config('cyca.maxOrphanAge.feeditems'));
$oldFeedItems = FeedItem::allRead()->olderThan($oldest)->get();
// We need to do this individually to take advantage of the
// FeedItemObserver and automatically delete associated files that may
// have been locally stored
foreach($oldFeedItems as $item) {
foreach ($oldFeedItems as $item) {
$item->delete();
}


+ 2
- 9
app/Console/Commands/UpdateDocuments.php View File

@ -24,8 +24,6 @@ class UpdateDocuments extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@ -39,18 +37,13 @@ class UpdateDocuments extends Command
*/
public function handle()
{
$oldest = now()->subMinute(config('cyca.maxAge.document'));
$documents = Document::where('checked_at', '<', $oldest)->orWhereNull('checked_at')->get();
$count = 0;
$oldest = now()->subMinute(config('cyca.maxAge.document'));
$documents = Document::needingUpdate($oldest)->get();
foreach ($documents as $document) {
EnqueueDocumentUpdate::dispatch($document);
$count++;
}
$this->line(sprintf('%s document(s) queued for update', $count));
return 0;
}
}

+ 1
- 4
app/Console/Commands/UpdateFeeds.php View File

@ -24,8 +24,6 @@ class UpdateFeeds extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@ -40,8 +38,7 @@ class UpdateFeeds extends Command
public function handle()
{
$oldest = now()->subMinute(config('cyca.maxAge.feed'));
$feeds = Feed::where('checked_at', '<', $oldest)->orWhereNull('checked_at')->get();
$feeds = Feed::needingUpdate($oldest)->get();
foreach ($feeds as $feed) {
EnqueueFeedUpdate::dispatch($feed);


+ 0
- 6
app/Console/Kernel.php View File

@ -13,14 +13,10 @@ class Kernel extends ConsoleKernel
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
@ -31,8 +27,6 @@ class Kernel extends ConsoleKernel
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{


+ 3
- 5
app/Contracts/ImportAdapter.php View File

@ -5,16 +5,14 @@ namespace App\Contracts;
use Illuminate\Http\Request;
/**
* Interface for data importers
* Interface for data importers.
*/
interface ImportAdapter {
interface ImportAdapter
{
/**
* Transforms data from specified request into an importable array. Data
* collected from the request could be an uploaded file, credentials for
* remote connection, anything the adapter could support.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function importFromRequest(Request $request): array;
}

+ 3
- 7
app/Exceptions/Handler.php View File

@ -13,7 +13,6 @@ class Handler extends ExceptionHandler
* @var array
*/
protected $dontReport = [
//
];
/**
@ -29,9 +28,6 @@ class Handler extends ExceptionHandler
/**
* Report or log an exception.
*
* @param \Throwable $exception
* @return void
*
* @throws \Exception
*/
public function report(Throwable $exception)
@ -42,11 +38,11 @@ class Handler extends ExceptionHandler
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Throwable $exception
* @return \Symfony\Component\HttpFoundation\Response
* @param \Illuminate\Http\Request $request
*
* @throws \Throwable
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function render($request, Throwable $exception)
{


+ 0
- 1
app/Exceptions/UserDoesNotExistsException.php View File

@ -6,5 +6,4 @@ use Exception;
class UserDoesNotExistsException extends Exception
{
//
}

+ 3
- 1
app/Http/Controllers/Controller.php View File

@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
}

+ 8
- 14
app/Http/Controllers/DocumentController.php View File

@ -4,7 +4,6 @@ namespace App\Http\Controllers;
use App\Http\Requests\Documents\StoreRequest;
use App\Models\Document;
use App\Models\FeedItemState;
use App\Models\Folder;
use App\Notifications\UnreadItemsChanged;
use Illuminate\Http\Request;
@ -20,7 +19,8 @@ class DocumentController extends Controller
/**
* Store a newly created resource in storage.
*
* @param App\Http\Requests\Documents\StoreRequest $request
* @param App\Http\Requests\Documents\StoreRequest $request
*
* @return \Illuminate\Http\Response
*/
public function store(StoreRequest $request)
@ -41,8 +41,6 @@ class DocumentController extends Controller
/**
* Display the specified resource.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Document $document
* @return \Illuminate\Http\Response
*/
public function show(Request $request, Document $document)
@ -59,10 +57,10 @@ class DocumentController extends Controller
}
/**
* Move document into specified folder
* Move document into specified folder.
*
* @param Folder $folder
*
* @param \Illuminate\Http\Request $request
* @param Folder $folder
* @return \Illuminate\Http\Response
*/
public function move(Request $request, Folder $sourceFolder, Folder $targetFolder)
@ -94,10 +92,8 @@ class DocumentController extends Controller
}
/**
* Remove documents from specified folder
* Remove documents from specified folder.
*
* @param \Illuminate\Http\Request $request
* @param Folder $folder
* @return \Illuminate\Http\Response
*/
public function destroyBookmarks(Request $request, Folder $folder)
@ -117,15 +113,13 @@ class DocumentController extends Controller
}
/**
* Increment visits for specified document in specified folder
* Increment visits for specified document in specified folder.
*
* @param \Illuminate\Http\Request $request
* @param Document $document
* @return \Illuminate\Http\Response
*/
public function visit(Request $request, Document $document)
{
$document->visits++;
++$document->visits;
$document->save();
return $this->show($request, $document);


+ 3
- 7
app/Http/Controllers/FeedController.php View File

@ -3,16 +3,14 @@
namespace App\Http\Controllers;
use App\Models\Feed;
use Illuminate\Http\Request;
use App\Models\IgnoredFeed;
use Illuminate\Http\Request;
class FeedController extends Controller
{
/**
* Ignore specified feed
* Ignore specified feed.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Feed $feed
* @return \Illuminate\Http\Response
*/
public function ignore(Request $request, Feed $feed)
@ -30,10 +28,8 @@ class FeedController extends Controller
}
/**
* Follow specified feed
* Follow specified feed.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Feed $feed
* @return \Illuminate\Http\Response
*/
public function follow(Request $request, Feed $feed)


+ 20
- 20
app/Http/Controllers/FeedItemController.php View File

@ -21,31 +21,30 @@ class FeedItemController extends Controller
return [];
}
$user = $request->user();
$queryBuilder = FeedItem::with('feeds:feeds.id,title', 'feeds.documents:documents.id')->whereHas('feeds', function ($query) use ($feedIds) {
$query->whereIn('feeds.id', $feedIds);
});
$user = $request->user();
$folder = $user->selectedFolder();
$queryBuilder->select(['feed_items.id', 'url', 'title', 'published_at', 'created_at', 'updated_at']);
$queryBuilder = FeedItem::with('feeds:feeds.id,title', 'feeds.documents:documents.id')->inFeeds($feedIds);
$folder = $user->selectedFolder();
$queryBuilder->select([
'feed_items.id',
'url',
'title',
'published_at',
'created_at',
'updated_at',
]);
if ($folder->type === 'unread_items') {
$queryBuilder->whereHas('feedItemStates', function ($query) use ($user) {
$query->where('user_id', $user->id)->where('is_read', false);
});
$queryBuilder->unreadFor($user);
}
return $queryBuilder->withCount(['feedItemStates' => function ($query) use ($user) {
$query->where('user_id', $user->id)->where('is_read', false);
}])->orderBy('published_at', 'desc')->simplePaginate(15);
return $queryBuilder->countStates($user)->orderBy('published_at', 'desc')->simplePaginate(15);
}
/**
* Display the specified resource.
*
* @param \App\Models\FeedItem $feedItem
* @return \Illuminate\Http\Response
*/
public function show(Request $request, FeedItem $feedItem)
@ -60,9 +59,7 @@ class FeedItemController extends Controller
}
/**
* Mark feed items as read
*
* @param \Illuminate\Http\Request $request
* Mark feed items as read.
*/
public function markAsRead(Request $request)
{
@ -70,11 +67,14 @@ class FeedItemController extends Controller
if ($request->has('folders')) {
return $user->markFeedItemsReadInFolders($request->input('folders'));
} elseif ($request->has('documents')) {
}
if ($request->has('documents')) {
return $user->markFeedItemsReadInDocuments($request->input('documents'));
} elseif ($request->has('feeds')) {
}
if ($request->has('feeds')) {
return $user->markFeedItemsReadInFeeds($request->input('feeds'));
} elseif ($request->has('feed_items')) {
}
if ($request->has('feed_items')) {
return $user->markFeedItemsRead($request->input('feed_items'));
}
}


+ 23
- 33
app/Http/Controllers/FolderController.php View File

@ -2,13 +2,13 @@
namespace App\Http\Controllers;
use App\Http\Requests\Folders\SetPermissionsRequest;
use App\Http\Requests\Folders\StoreRequest;
use App\Http\Requests\Folders\UpdateRequest;
use App\Models\Folder;
use App\Models\Group;
use App\Http\Requests\Folders\SetPermissionsRequest;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Http\Request;
class FolderController extends Controller
{
@ -32,7 +32,8 @@ class FolderController extends Controller
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Folder\StoreRequest $request
* @param \App\Http\Requests\Folder\StoreRequest $request
*
* @return \Illuminate\Http\Response
*/
public function store(StoreRequest $request)
@ -56,7 +57,6 @@ class FolderController extends Controller
/**
* Display the specified resource.
*
* @param \App\Models\Folder $folder
* @return \Illuminate\Http\Response
*/
public function show(Request $request, Folder $folder)
@ -69,15 +69,14 @@ class FolderController extends Controller
}
/**
* Load every details for specified folder
* Load every details of specified folder.
*
* @param \App\Models\Folder $folder
* @return \Illuminate\Http\Response
*/
public function details(Request $request, Folder $folder)
{
$user = $request->user();
if (!$user->can('view', $folder)) {
abort(404);
}
@ -94,14 +93,13 @@ class FolderController extends Controller
$folder->group->loadCount('activeUsers');
}
return $folder;
}
/**
* Load per-user permissions for specified folder
* Load per-user permissions for specified folder.
*
* @param \App\Models\Folder $folder
* @return \Illuminate\Http\Response
*/
public function perUserPermissions(Request $request, Folder $folder)
@ -124,9 +122,8 @@ class FolderController extends Controller
}
/**
* Load list of users with no expicit permissions for specified folder
* Load list of users with no expicit permissions for specified folder.
*
* @param \App\Models\Folder $folder
* @return \Illuminate\Http\Response
*/
public function usersWithoutPermissions(Request $request, Folder $folder)
@ -148,8 +145,8 @@ class FolderController extends Controller
/**
* Update the specified resource in storage.
*
* @param App\Http\Requests\Folder\UpdateRequest $request
* @param \App\Models\Folder $folder
* @param App\Http\Requests\Folder\UpdateRequest $request
*
* @return \Illuminate\Http\Response
*/
public function update(UpdateRequest $request, Folder $folder)
@ -172,15 +169,12 @@ class FolderController extends Controller
$user->setFolderExpandedState(true, $folder->parent);
}
//TODO: Send a "folder updated" notification to other users in the group
return $folder;
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Folder $folder
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request, Folder $folder)
@ -191,15 +185,12 @@ class FolderController extends Controller
$folder->delete();
//TODO: Send a "folder deleted" notification to other users in the group
return $user->getFlatTree();
}
/**
* Toggle expanded/collapsed a whole folder's branch
* Toggle expanded/collapsed a whole folder's branch.
*
* @param \App\Models\Folder $folder
* @return \Illuminate\Http\Response
*/
public function toggleBranch(Request $request, Folder $folder)
@ -212,10 +203,10 @@ class FolderController extends Controller
}
/**
* Set permissions for specified folder, optionally for specified user
* Set permissions for specified folder, optionally for specified user.
*
* @param App\Http\Requests\Folder\SetPermissionsRequest $request
*
* @param App\Http\Requests\Folder\SetPermissionsRequest $request
* @param \App\Models\Folder $folder
* @return \Illuminate\Http\Response
*/
public function setPermission(SetPermissionsRequest $request, Folder $folder)
@ -233,21 +224,20 @@ class FolderController extends Controller
$folder->setDefaultPermission($ability, $granted);
return $this->details($request, $folder);
} else {
$user = $folder->group->activeUsers()->findOrFail($validated['user_id']);
}
$user->setFolderPermissions($folder, $ability, $granted);
$user = $folder->group->activeUsers()->findOrFail($validated['user_id']);
return $this->perUserPermissions($request, $folder);
}
$user->setFolderPermissions($folder, $ability, $granted);
return $this->perUserPermissions($request, $folder);
}
/**
* Remove permissions for specified user in specified folder
* Remove permissions for specified user in specified folder.
*
* @param App\Http\Requests\Folder\SetPermissionsRequest $request
*
* @param App\Http\Requests\Folder\SetPermissionsRequest $request
* @param \App\Models\Folder $folder
* @param \App\Models\User $user
* @return \Illuminate\Http\Response
*/
public function removePermissions(Request $request, Folder $folder, User $user)


+ 14
- 35
app/Http/Controllers/GroupController.php View File

@ -2,15 +2,15 @@
namespace App\Http\Controllers;
use App\Http\Requests\Groups\InviteUserRequest;
use App\Http\Requests\Groups\StoreRequest;
use App\Http\Requests\Groups\UpdateRequest;
use App\Models\Group;
use App\Models\User;
use App\Notifications\AsksToJoinGroup;
use App\Notifications\InvitedToJoinGroup;
use Illuminate\Http\Request;
use App\Http\Requests\Groups\InviteUserRequest;
use Notification;
use App\Notifications\InvitedToJoinGroup;
use App\Notifications\AsksToJoinGroup;
class GroupController extends Controller
{
@ -22,7 +22,6 @@ class GroupController extends Controller
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
@ -36,7 +35,7 @@ class GroupController extends Controller
->withCount('activeUsers');
if (!empty($search)) {
$query = $query->where('groups.name', 'like', '%' . $search . '%');
$query = $query->where('groups.name', 'like', '%'.$search.'%');
}
return $query
@ -45,9 +44,8 @@ class GroupController extends Controller
}
/**
* Display a listing of the resource (active groups)
* Display a listing of the resource (active groups).
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function indexActive(Request $request)
@ -58,9 +56,8 @@ class GroupController extends Controller
}
/**
* Display a listing of the resource (my groups)
* Display a listing of the resource (my groups).
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function indexMyGroups(Request $request)
@ -70,7 +67,7 @@ class GroupController extends Controller
return $user->groups()->withCount('activeUsers', 'pendingUsers')
->whereNotIn('status', [
Group::$STATUS_REJECTED,
Group::$STATUS_LEFT
Group::$STATUS_LEFT,
])->orderBy('position')->orderBy('id')->get();
}
@ -78,6 +75,7 @@ class GroupController extends Controller
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\StoreRequest $request
*
* @return \Illuminate\Http\Response
*/
public function store(StoreRequest $request)
@ -97,8 +95,6 @@ class GroupController extends Controller
/**
* Display the specified resource.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Group $group
* @return \Illuminate\Http\Response
*/
public function show(Request $request, Group $group)
@ -113,8 +109,6 @@ class GroupController extends Controller
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Groups\UpdateRequest $request
* @param \App\Models\Group $group
* @return \Illuminate\Http\Response
*/
public function update(UpdateRequest $request, Group $group)
@ -134,8 +128,6 @@ class GroupController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Group $group
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request, Group $group)
@ -148,9 +140,8 @@ class GroupController extends Controller
}
/**
* Update my groups positions
* Update my groups positions.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function updatePositions(Request $request)
@ -181,10 +172,10 @@ class GroupController extends Controller
}
/**
* Invite user to join specified group
* Invite user to join specified group.
*
* @param \App\Requests\Groups\InviteUserRequest $request
*
* @param \App\Requests\Groups\InviteUserRequest $request
* @param \App\Models\Group $group
* @return \Illuminate\Http\Response
*/
public function inviteUser(InviteUserRequest $request, Group $group)
@ -205,8 +196,6 @@ class GroupController extends Controller
Notification::route('mail', $validated['email'])
->notify(new InvitedToJoinGroup($request->user(), $group));
//TODO: Add history entry
return $request->user()->groups()->withCount('activeUsers', 'pendingUsers')->find($group->id);
}
@ -215,13 +204,11 @@ class GroupController extends Controller
$user = $request->user();
$user->updateGroupStatus($group, Group::$STATUS_ACCEPTED);
//TODO: Add history entry
if ($request->ajax()) {
return $user->groups()->withCount('activeUsers', 'pendingUsers')->find($group->id);
} else {
return redirect()->route('account.groups');
}
return redirect()->route('account.groups');
}
public function approveUser(Request $request, Group $group, User $user)
@ -234,8 +221,6 @@ class GroupController extends Controller
$user->updateGroupStatus($group, Group::$STATUS_ACCEPTED);
//TODO: Add history entry
return redirect()->route('account.groups');
}
@ -244,8 +229,6 @@ class GroupController extends Controller
$user = $request->user();
$user->updateGroupStatus($group, Group::$STATUS_REJECTED);
//TODO: Add history entry
return $user->groups()->withCount('activeUsers', 'pendingUsers')->find($group->id);
}
@ -253,8 +236,6 @@ class GroupController extends Controller
{
$user = $request->user();
$user->groups()->detach($group);
//TODO: Add history entry
}
public function join(Request $request, Group $group)
@ -269,7 +250,5 @@ class GroupController extends Controller
Notification::route('mail', $group->creator->email)
->notify(new AsksToJoinGroup($request->user(), $group));
}
//TODO: Add history entry
}
}

+ 11
- 8
app/Http/Controllers/HighlightController.php View File

@ -2,8 +2,8 @@
namespace App\Http\Controllers;
use App\Models\Highlight;
use App\Http\Requests\StoreHighlightRequest;
use App\Models\Highlight;
use Illuminate\Http\Request;
class HighlightController extends Controller
@ -11,17 +11,18 @@ class HighlightController extends Controller
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\StoreHighlightRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreHighlightRequest $request)
{
$data = $request->validated();
$highlight = new Highlight();
$highlight = new Highlight();
$highlight->user_id = $request->user()->id;
$highlight->expression = $data['expression'];
$highlight->color = $data['color'];
$highlight->save();
return $request->user()->highlights()->get();
@ -30,8 +31,8 @@ class HighlightController extends Controller
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\StoreHighlightRequest $request
* @param \App\Models\Models\Highlight $highlight
* @param \App\Models\Models\Highlight $highlight
*
* @return \Illuminate\Http\Response
*/
public function update(StoreHighlightRequest $request, Highlight $highlight)
@ -44,6 +45,7 @@ class HighlightController extends Controller