diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index 0259c403e5..8c7eb4a90c 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -97,12 +97,12 @@ class UserEventHandler } // get the email address $email = $event->user->email; - $address = route('index'); + $uri = route('index'); $ipAddress = $event->ipAddress; // send email. try { - Mail::to($email)->send(new RegisteredUserMail($address, $ipAddress)); + Mail::to($email)->send(new RegisteredUserMail($uri, $ipAddress)); // @codeCoverageIgnoreStart } catch (Swift_TransportException $e) { Log::error($e->getMessage()); diff --git a/app/Import/Setup/CsvSetup.php b/app/Import/Setup/CsvSetup.php index 51ccedb338..e3888e0030 100644 --- a/app/Import/Setup/CsvSetup.php +++ b/app/Import/Setup/CsvSetup.php @@ -16,15 +16,12 @@ namespace FireflyIII\Import\Setup; use ExpandedForm; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Import\Mapper\MapperInterface; -use FireflyIII\Import\MapperPreProcess\PreProcessorInterface; -use FireflyIII\Import\Specifics\SpecificInterface; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Import\CsvImportSupportTrait; use Illuminate\Http\Request; -use League\Csv\Reader; use Log; use Symfony\Component\HttpFoundation\FileBag; @@ -35,6 +32,7 @@ use Symfony\Component\HttpFoundation\FileBag; */ class CsvSetup implements SetupInterface { + use CsvImportSupportTrait; /** @var Account */ public $defaultImportAccount; /** @var ImportJob */ @@ -286,220 +284,4 @@ class CsvSetup implements SetupInterface $this->job->save(); } } - - /** - * @return bool - */ - private function doColumnMapping(): bool - { - $mapArray = $this->job->configuration['column-do-mapping'] ?? []; - $doMap = false; - foreach ($mapArray as $value) { - if ($value === true) { - $doMap = true; - break; - } - } - - return $this->job->configuration['column-mapping-complete'] === false && $doMap; - } - - /** - * @return bool - */ - private function doColumnRoles(): bool - { - return $this->job->configuration['column-roles-complete'] === false; - } - - /** - * @return array - * @throws FireflyException - */ - private function getDataForColumnMapping(): array - { - $config = $this->job->configuration; - $data = []; - $indexes = []; - - foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { - if ($mustBeMapped) { - - $column = $config['column-roles'][$index] ?? '_ignore'; - - // is valid column? - $validColumns = array_keys(config('csv.import_roles')); - if (!in_array($column, $validColumns)) { - throw new FireflyException(sprintf('"%s" is not a valid column.', $column)); - } - - $canBeMapped = config('csv.import_roles.' . $column . '.mappable'); - $preProcessMap = config('csv.import_roles.' . $column . '.pre-process-map'); - if ($canBeMapped) { - $mapperClass = config('csv.import_roles.' . $column . '.mapper'); - $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass); - /** @var MapperInterface $mapper */ - $mapper = new $mapperName; - $indexes[] = $index; - $data[$index] = [ - 'name' => $column, - 'mapper' => $mapperName, - 'index' => $index, - 'options' => $mapper->getMap(), - 'preProcessMap' => null, - 'values' => [], - ]; - if ($preProcessMap) { - $preClass = sprintf( - '\\FireflyIII\\Import\\MapperPreProcess\\%s', - config('csv.import_roles.' . $column . '.pre-process-mapper') - ); - $data[$index]['preProcessMap'] = $preClass; - } - } - - } - } - - // in order to actually map we also need all possible values from the CSV file. - $content = $this->job->uploadFileContents(); - /** @var Reader $reader */ - $reader = Reader::createFromString($content); - $reader->setDelimiter($config['delimiter']); - $results = $reader->fetch(); - $validSpecifics = array_keys(config('csv.import_specifics')); - - foreach ($results as $rowIndex => $row) { - - // skip first row? - if ($rowIndex === 0 && $config['has-headers']) { - continue; - } - - // run specifics here: - // and this is the point where the specifix go to work. - foreach ($config['specifics'] as $name => $enabled) { - - if (!in_array($name, $validSpecifics)) { - throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); - } - $class = config('csv.import_specifics.' . $name); - /** @var SpecificInterface $specific */ - $specific = app($class); - - // it returns the row, possibly modified: - $row = $specific->run($row); - } - - //do something here - foreach ($indexes as $index) { // this is simply 1, 2, 3, etc. - if (!isset($row[$index])) { - // don't really know how to handle this. Just skip, for now. - continue; - } - $value = $row[$index]; - if (strlen($value) > 0) { - - // we can do some preprocessing here, - // which is exclusively to fix the tags: - if (!is_null($data[$index]['preProcessMap'])) { - /** @var PreProcessorInterface $preProcessor */ - $preProcessor = app($data[$index]['preProcessMap']); - $result = $preProcessor->run($value); - $data[$index]['values'] = array_merge($data[$index]['values'], $result); - - Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]); - Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]); - Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $data[$index]['values']]); - - - continue; - } - - $data[$index]['values'][] = $value; - } - } - } - foreach ($data as $index => $entry) { - $data[$index]['values'] = array_unique($data[$index]['values']); - } - - return $data; - } - - /** - * This method collects the data that will enable a user to choose column content. - * - * @return array - */ - private function getDataForColumnRoles(): array - { - Log::debug('Now in getDataForColumnRoles()'); - $config = $this->job->configuration; - $data = [ - 'columns' => [], - 'columnCount' => 0, - 'columnHeaders' => [], - ]; - - // show user column role configuration. - $content = $this->job->uploadFileContents(); - - // create CSV reader. - $reader = Reader::createFromString($content); - $reader->setDelimiter($config['delimiter']); - $start = $config['has-headers'] ? 1 : 0; - $end = $start + config('csv.example_rows'); - $header = []; - if ($config['has-headers']) { - $header = $reader->fetchOne(0); - } - - - // collect example data in $data['columns'] - Log::debug(sprintf('While %s is smaller than %d', $start, $end)); - while ($start < $end) { - $row = $reader->fetchOne($start); - Log::debug(sprintf('Row %d has %d columns', $start, count($row))); - // run specifics here: - // and this is the point where the specifix go to work. - foreach ($config['specifics'] as $name => $enabled) { - /** @var SpecificInterface $specific */ - $specific = app('FireflyIII\Import\Specifics\\' . $name); - Log::debug(sprintf('Will now apply specific "%s" to row %d.', $name, $start)); - // it returns the row, possibly modified: - $row = $specific->run($row); - } - - foreach ($row as $index => $value) { - $value = trim($value); - $data['columnHeaders'][$index] = $header[$index] ?? ''; - if (strlen($value) > 0) { - $data['columns'][$index][] = $value; - } - } - $start++; - $data['columnCount'] = count($row) > $data['columnCount'] ? count($row) : $data['columnCount']; - } - - // make unique example data - foreach ($data['columns'] as $index => $values) { - $data['columns'][$index] = array_unique($values); - } - - $data['set_roles'] = []; - // collect possible column roles: - $data['available_roles'] = []; - foreach (array_keys(config('csv.import_roles')) as $role) { - $data['available_roles'][$role] = trans('csv.column_' . $role); - } - - $config['column-count'] = $data['columnCount']; - $this->job->configuration = $config; - $this->job->save(); - - return $data; - - - } } diff --git a/app/Mail/RegisteredUser.php b/app/Mail/RegisteredUser.php index a693fb13e1..d988506393 100644 --- a/app/Mail/RegisteredUser.php +++ b/app/Mail/RegisteredUser.php @@ -12,18 +12,18 @@ class RegisteredUser extends Mailable /** @var string */ public $address; /** @var string */ - public $userIp; + public $ipAddress; /** * Create a new message instance. * * @param string $address - * @param string $userIp + * @param string $ipAddress */ - public function __construct(string $address, string $userIp) + public function __construct(string $address, string $ipAddress) { $this->address = $address; - $this->userIp = $userIp; + $this->ipAddress = $ipAddress; } /** diff --git a/app/Mail/RequestedNewPassword.php b/app/Mail/RequestedNewPassword.php index cdda5b9858..bd2d9e90b0 100644 --- a/app/Mail/RequestedNewPassword.php +++ b/app/Mail/RequestedNewPassword.php @@ -10,20 +10,20 @@ class RequestedNewPassword extends Mailable { use Queueable, SerializesModels; /** @var string */ - public $url; + public $ipAddress; /** @var string */ - public $userIp; + public $url; /** * RequestedNewPassword constructor. * * @param string $url - * @param string $userIp + * @param string $ipAddress */ - public function __construct(string $url, string $userIp) + public function __construct(string $url, string $ipAddress) { - $this->url = $url; - $this->userIp = $userIp; + $this->url = $url; + $this->ipAddress = $ipAddress; } /** diff --git a/app/Support/Import/CsvImportSupportTrait.php b/app/Support/Import/CsvImportSupportTrait.php new file mode 100644 index 0000000000..efee16704a --- /dev/null +++ b/app/Support/Import/CsvImportSupportTrait.php @@ -0,0 +1,242 @@ +job->configuration['column-do-mapping'] ?? []; + $doMap = false; + foreach ($mapArray as $value) { + if ($value === true) { + $doMap = true; + break; + } + } + + return $this->job->configuration['column-mapping-complete'] === false && $doMap; + } + + /** + * @return bool + */ + protected function doColumnRoles(): bool + { + return $this->job->configuration['column-roles-complete'] === false; + } + + /** + * @return array + * @throws FireflyException + */ + protected function getDataForColumnMapping(): array + { + $config = $this->job->configuration; + $data = []; + $indexes = []; + + foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { + if ($mustBeMapped) { + + $column = $config['column-roles'][$index] ?? '_ignore'; + + // is valid column? + $validColumns = array_keys(config('csv.import_roles')); + if (!in_array($column, $validColumns)) { + throw new FireflyException(sprintf('"%s" is not a valid column.', $column)); + } + + $canBeMapped = config('csv.import_roles.' . $column . '.mappable'); + $preProcessMap = config('csv.import_roles.' . $column . '.pre-process-map'); + if ($canBeMapped) { + $mapperClass = config('csv.import_roles.' . $column . '.mapper'); + $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass); + /** @var MapperInterface $mapper */ + $mapper = new $mapperName; + $indexes[] = $index; + $data[$index] = [ + 'name' => $column, + 'mapper' => $mapperName, + 'index' => $index, + 'options' => $mapper->getMap(), + 'preProcessMap' => null, + 'values' => [], + ]; + if ($preProcessMap) { + $preClass = sprintf( + '\\FireflyIII\\Import\\MapperPreProcess\\%s', + config('csv.import_roles.' . $column . '.pre-process-mapper') + ); + $data[$index]['preProcessMap'] = $preClass; + } + } + + } + } + + // in order to actually map we also need all possible values from the CSV file. + $content = $this->job->uploadFileContents(); + /** @var Reader $reader */ + $reader = Reader::createFromString($content); + $reader->setDelimiter($config['delimiter']); + $results = $reader->fetch(); + $validSpecifics = array_keys(config('csv.import_specifics')); + + foreach ($results as $rowIndex => $row) { + + // skip first row? + if ($rowIndex === 0 && $config['has-headers']) { + continue; + } + + // run specifics here: + // and this is the point where the specifix go to work. + foreach ($config['specifics'] as $name => $enabled) { + + if (!in_array($name, $validSpecifics)) { + throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); + } + $class = config('csv.import_specifics.' . $name); + /** @var SpecificInterface $specific */ + $specific = app($class); + + // it returns the row, possibly modified: + $row = $specific->run($row); + } + + //do something here + foreach ($indexes as $index) { // this is simply 1, 2, 3, etc. + if (!isset($row[$index])) { + // don't really know how to handle this. Just skip, for now. + continue; + } + $value = $row[$index]; + if (strlen($value) > 0) { + + // we can do some preprocessing here, + // which is exclusively to fix the tags: + if (!is_null($data[$index]['preProcessMap'])) { + /** @var PreProcessorInterface $preProcessor */ + $preProcessor = app($data[$index]['preProcessMap']); + $result = $preProcessor->run($value); + $data[$index]['values'] = array_merge($data[$index]['values'], $result); + + Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]); + Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]); + Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $data[$index]['values']]); + + + continue; + } + + $data[$index]['values'][] = $value; + } + } + } + foreach ($data as $index => $entry) { + $data[$index]['values'] = array_unique($data[$index]['values']); + } + + return $data; + } + + /** + * This method collects the data that will enable a user to choose column content. + * + * @return array + */ + protected function getDataForColumnRoles(): array + { + Log::debug('Now in getDataForColumnRoles()'); + $config = $this->job->configuration; + $data = [ + 'columns' => [], + 'columnCount' => 0, + 'columnHeaders' => [], + ]; + + // show user column role configuration. + $content = $this->job->uploadFileContents(); + + // create CSV reader. + $reader = Reader::createFromString($content); + $reader->setDelimiter($config['delimiter']); + $start = $config['has-headers'] ? 1 : 0; + $end = $start + config('csv.example_rows'); + $header = []; + if ($config['has-headers']) { + $header = $reader->fetchOne(0); + } + + + // collect example data in $data['columns'] + Log::debug(sprintf('While %s is smaller than %d', $start, $end)); + while ($start < $end) { + $row = $reader->fetchOne($start); + Log::debug(sprintf('Row %d has %d columns', $start, count($row))); + // run specifics here: + // and this is the point where the specifix go to work. + foreach ($config['specifics'] as $name => $enabled) { + /** @var SpecificInterface $specific */ + $specific = app('FireflyIII\Import\Specifics\\' . $name); + Log::debug(sprintf('Will now apply specific "%s" to row %d.', $name, $start)); + // it returns the row, possibly modified: + $row = $specific->run($row); + } + + foreach ($row as $index => $value) { + $value = trim($value); + $data['columnHeaders'][$index] = $header[$index] ?? ''; + if (strlen($value) > 0) { + $data['columns'][$index][] = $value; + } + } + $start++; + $data['columnCount'] = count($row) > $data['columnCount'] ? count($row) : $data['columnCount']; + } + + // make unique example data + foreach ($data['columns'] as $index => $values) { + $data['columns'][$index] = array_unique($values); + } + + $data['set_roles'] = []; + // collect possible column roles: + $data['available_roles'] = []; + foreach (array_keys(config('csv.import_roles')) as $role) { + $data['available_roles'][$role] = trans('csv.column_' . $role); + } + + $config['column-count'] = $data['columnCount']; + $this->job->configuration = $config; + $this->job->save(); + + return $data; + } +} \ No newline at end of file diff --git a/app/User.php b/app/User.php index f5ee28f920..3c045f2412 100644 --- a/app/User.php +++ b/app/User.php @@ -214,9 +214,9 @@ class User extends Authenticatable */ public function sendPasswordResetNotification($token) { - $ip = Request::ip(); + $ipAddress = Request::ip(); - event(new RequestedNewPassword($this, $token, $ip)); + event(new RequestedNewPassword($this, $token, $ipAddress)); } /** diff --git a/resources/views/emails/footer-html.twig b/resources/views/emails/footer-html.twig index 935128c2aa..6e2ca37007 100644 --- a/resources/views/emails/footer-html.twig +++ b/resources/views/emails/footer-html.twig @@ -6,7 +6,7 @@

- PS: This message was sent because a request from IP {{ ip }} {{ userIp }} triggered it. + PS: This message was sent because a request from IP {{ ip }}{{ userIp }}{{ ipAddress }} triggered it.

diff --git a/resources/views/emails/footer-text.twig b/resources/views/emails/footer-text.twig index 2e4d10c979..e2ec3ff9d4 100644 --- a/resources/views/emails/footer-text.twig +++ b/resources/views/emails/footer-text.twig @@ -3,4 +3,4 @@ Beep boop, The Firefly III Mail Robot -PS: This message was sent because a request from IP {{ ip }} {{ userIp }} triggered it. +PS: This message was sent because a request from IP {{ ip }}{{ userIp }}{{ ipAddress }} triggered it. diff --git a/routes/web.php b/routes/web.php index d8e969e4ac..9782f9d6c6 100755 --- a/routes/web.php +++ b/routes/web.php @@ -134,7 +134,6 @@ Route::group( */ Route::group( ['middleware' => 'user-full-auth', 'prefix' => 'budgets', 'as' => 'budgets.'], function () { - Route::get('{moment?}', ['uses' => 'BudgetController@index', 'as' => 'index']); Route::get('income', ['uses' => 'BudgetController@updateIncome', 'as' => 'income']); Route::get('create', ['uses' => 'BudgetController@create', 'as' => 'create']); Route::get('edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'edit']); @@ -142,6 +141,7 @@ Route::group( Route::get('show/{budget}', ['uses' => 'BudgetController@show', 'as' => 'show']); Route::get('show/{budget}/{budgetlimit}', ['uses' => 'BudgetController@showByBudgetLimit', 'as' => 'show.limit']); Route::get('list/no-budget/{moment?}', ['uses' => 'BudgetController@noBudget', 'as' => 'no-budget']); + Route::get('{moment?}', ['uses' => 'BudgetController@index', 'as' => 'index']); Route::post('income', ['uses' => 'BudgetController@postUpdateIncome', 'as' => 'income.post']); Route::post('store', ['uses' => 'BudgetController@store', 'as' => 'store']); diff --git a/tests/Feature/Controllers/BudgetControllerTest.php b/tests/Feature/Controllers/BudgetControllerTest.php index b79a537f54..f740ec293f 100644 --- a/tests/Feature/Controllers/BudgetControllerTest.php +++ b/tests/Feature/Controllers/BudgetControllerTest.php @@ -444,14 +444,16 @@ class BudgetControllerTest extends TestCase */ public function testUpdateIncome() { + // must be in list + $this->be($this->user()); + // mock stuff $repository = $this->mock(BudgetRepositoryInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('getAvailableBudget')->andReturn('1'); + $repository->shouldReceive('cleanupBudgets'); - // must be in list - $this->be($this->user()); $response = $this->get(route('budgets.income', [1])); $response->assertStatus(200); } diff --git a/tests/Feature/Controllers/Transaction/MassControllerTest.php b/tests/Feature/Controllers/Transaction/MassControllerTest.php index 065d66a9e3..988dfaf3ac 100644 --- a/tests/Feature/Controllers/Transaction/MassControllerTest.php +++ b/tests/Feature/Controllers/Transaction/MassControllerTest.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace Tests\Feature\Controllers\Transaction; @@ -18,7 +18,6 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use Illuminate\Support\Collection; use Tests\TestCase; @@ -172,9 +171,8 @@ class MassControllerTest extends TestCase ->first(); // mock stuff $repository = $this->mock(JournalRepositoryInterface::class); - $updater = $this->mock(JournalUpdateInterface::class); $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); - $updater->shouldReceive('update')->once(); + $repository->shouldReceive('update')->once(); $repository->shouldReceive('find')->once()->andReturn($deposit); diff --git a/tests/Feature/Controllers/Transaction/SingleControllerTest.php b/tests/Feature/Controllers/Transaction/SingleControllerTest.php index b396a961db..56bd1041bf 100644 --- a/tests/Feature/Controllers/Transaction/SingleControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SingleControllerTest.php @@ -23,7 +23,6 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; @@ -270,7 +269,6 @@ class SingleControllerTest extends TestCase // mock $this->expectsEvents(UpdatedTransactionJournal::class); - $updater = $this->mock(JournalUpdateInterface::class); $repository = $this->mock(JournalRepositoryInterface::class); $journal = new TransactionJournal(); @@ -280,7 +278,7 @@ class SingleControllerTest extends TestCase $journal->transactionType()->associate($type); - $updater->shouldReceive('update')->andReturn($journal); + $repository->shouldReceive('update')->andReturn($journal); $repository->shouldReceive('first')->times(2)->andReturn(new TransactionJournal); $this->session(['transactions.edit.uri' => 'http://localhost']); diff --git a/tests/Feature/Controllers/Transaction/SplitControllerTest.php b/tests/Feature/Controllers/Transaction/SplitControllerTest.php index c6cf00eb07..578802bc8c 100644 --- a/tests/Feature/Controllers/Transaction/SplitControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SplitControllerTest.php @@ -21,7 +21,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use Tests\TestCase; @@ -135,8 +134,7 @@ class SplitControllerTest extends TestCase // mock stuff $repository = $this->mock(JournalRepositoryInterface::class); - $updater = $this->mock(JournalUpdateInterface::class); - $updater->shouldReceive('updateSplitJournal')->andReturn($deposit); + $repository->shouldReceive('updateSplitJournal')->andReturn($deposit); $repository->shouldReceive('first')->times(2)->andReturn(new TransactionJournal); $attachmentRepos = $this->mock(AttachmentHelperInterface::class); $attachmentRepos->shouldReceive('saveAttachmentsForModel'); diff --git a/tests/Unit/Handlers/Events/UserEventHandlerTest.php b/tests/Unit/Handlers/Events/UserEventHandlerTest.php index 0edb4c3938..27b44dfb72 100644 --- a/tests/Unit/Handlers/Events/UserEventHandlerTest.php +++ b/tests/Unit/Handlers/Events/UserEventHandlerTest.php @@ -56,7 +56,7 @@ class UserEventHandlerTest extends TestCase Mail::assertSent( RequestedNewPasswordMail::class, function ($mail) use ($user) { - return $mail->hasTo($user->email) && $mail->ip === '127.0.0.1'; + return $mail->hasTo($user->email) && $mail->ipAddress === '127.0.0.1'; } ); @@ -78,7 +78,7 @@ class UserEventHandlerTest extends TestCase // must send user an email: Mail::assertSent( RegisteredUserMail::class, function ($mail) use ($user) { - return $mail->hasTo($user->email) && $mail->ip === '127.0.0.1'; + return $mail->hasTo($user->email) && $mail->ipAddress === '127.0.0.1'; } );