yii2 - Send email by console use transport - php

I want send email by console with Swift_SmtpTransport.
The same transport settings work in common/config/main-local.php and don't work in console/config/main-local.php.
In console in config/main-local.php I have:
<?php
return [
'components' => [
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '#common/mail',
'htmlLayout' => '#common/mail/layouts/html',
'textLayout' => '#common/mail/layouts/text', // custome layout
'transport' => [
'class' => 'Swift_SmtpTransport',
'host' => 'gator.hostgator.com',
'username' => 'test#pix.com',
'password' => '*******',
'port' => '465',
'encryption' => 'ssl',
],
],
],
];
With this configuration (and in common the settings are the same and work) I load the script by command and no email send and no error.
With this (I delete the transport settings) I run the same script by command and the email send ok:
<?php
return [
'components' => [
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '#common/mail',
'htmlLayout' => '#common/mail/layouts/html',
'textLayout' => '#common/mail/layouts/text', // custome layout
],
],
];
In console/controllers/CronController I have this:
<?php
namespace console\controllers;
use yii\console\Controller;
use backend\models\Definicoes;
use common\models\Acordo;
use Yii;
/**
* Cron controller
*/
class CronController extends Controller {
public function actionIndex() {
$data_hoje = date('Y-m-d');
$model_definicoes = Definicoes::find()->one();
Yii::$app->mail->compose('#common/mail/cron_acordo', ['model_definicoes' => $model_definicoes])
->setFrom([Yii::$app->params['adminEmail'] => Yii::$app->params['nome']])
->setSubject('Alert')
->setTo('ideinto#gmail.com')
->send();
}
}
Why this happen? I canĀ“t use transport in console?
Thanks!

another way to check if your transport settings are correct
you could set the transport directly in CronController.php
\Yii::$app->mail->setTransport( [
'class' => 'Swift_SmtpTransport',
'host' => 'gator.hostgator.com',
'username' => 'test#pix.com',
'password' => '*******',
'port' => '465',
'encryption' => 'ssl',
]);
before this line
Yii::$app->mail->compose('#common/mail/cron_acordo', ['model_definicoes' => $model_definicoes])

This is because yii merge config in common/main.php with yours at console/main.php.
Reset your config to send directly:
'mailer' => [
'useFileTransport' => false,
//other configs
]
See file: yii\mail\BaseMailer.php, have this comment at line 77-78:
/**
* #var boolean whether to save email messages as files under [[fileTransportPath]] instead of sending them
* to the actual recipients. This is usually used during development for debugging purpose.
* #see fileTransportPath
*/
public $useFileTransport = false;

You should make sure your configurations in console.php are right, make sure the key mailer is configured.
Good luck !

So, I came across this when I was looking for an answer to a problem I had. I was able to find the answer to my problem by enabling the logging for the SwiftMailer which ended up finding "Sender address rejected: User unknown in relay recipient table." Essentially, the mail that I was using in the "from" address was different in the console portion than it was from the frontend portion, and that was the issue, it didn't even have to do with the config.
I was only able to figure this out by looking at the logging, so to enable logging, in your Yii configuration under the 'mail' component (or whatever you're calling it) add the key=>value pair 'enableSwiftMailerLogging' => true (example of where is in this question: Config mailer parameters from model - Yii2). Then, in your log component config, under the 'targets' you need to add
[
'class' => 'yii\log\FileTarget',
'categories' => ['yii\swiftmailer\Logger::add'],
]
It's slightly documented here: http://www.yiiframework.com/doc-2.0/yii-swiftmailer-logger.html
By doing this I was able to look into the logs (console/runtime/app.log for me) and find out why it wasn't sending out correctly from the console but was from the other areas of my app.

Related

Laravel log file specific to a package

I'm writing a couple of laravel packages and I'm wondering if it is possible to have the package write to a specific log file but only for messages related to the package?
I tried making a logging.php file in the packages/myorg/mypackage/config (below) but it doesn't seem to do anything.
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
'default' => env('LOG_CHANNEL', 'stack'),
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/mypackage.log'),
'level' => env('LOG_LEVEL', 'debug'),
]
]
];
I am using "jeroen-g/laravel-packager" to set up the packages. It appears to manually load the mypackage.config in the ServiceProvider bootForConsole
protected function bootForConsole(): void
{
// Publishing the configuration file.
$this->publishes([
mypackage.'/../config/mypackage.php' => config_path('mypackage.php'),
], 'mypackage.config');
}
I'm not sure how to add custom logging to that though. I'm still learning Laravel and I'm not quite sure what or how the main applications config/logging.php is read so I'm not quite sure how to inject a custom version for an add-on package.
EDIT:
I found a post that suggested using the following in the ServiceManager boot() method:
$this->app->make('config')->set('logging.channels.mychannel', [
/* settings */
]);
I used the package config to set a 'logging' => [ 'channels' => [ 'mychannel' => [ /* settings */ ] ] ] and could then do the same thing as above with:
$this->app->make('config')->set('logging.channels.mychannel', config('mypackage.logging.channels.mychannel');
But that still required something in the code. The next best thing I have found thus far is to change my config/logging.php to config/logging.channels.php and include something like:
return [
'mychannel' => [
'driver' => 'single',
'path' => storage_path('logs/mypackage.log'),
'level' => env('LOG_LEVEL', 'debug'),
]
];
Then in the service provider register() method add:
$this->mergeConfigFrom(__DIR__ . '/../config/logging.channels.php', 'logging.channels');
I tried doing it from the original 'logging.php' with channels array nested in a 'logging' key, but array_merge doesn't appear to merge the nested elements so my channel never showed up in logging.channels.
I'm not sure if this is ideal, however. I'd still like to know if there is a 'better' or best practices way of adding custom package logging parameters and whether there is a need to publish it in any way (and how).

CakePHP DC Users 8.5.1 customising to use email

I have a CakePHP application running on Cake PHP 3.8.13 and CakeDC Users 8.5.1.
I am currently able to log on using the username field, but I would like to use the email field for authentication. I have followed the instructions on https://github.com/CakeDC/users/blob/master/Docs/Documentation/Configuration.md#using-the-users-email-to-login but the system is still trying to use the username field. If I change email to username in the src/Template/Plugin/CakeDC/Users/Users/login.ctp I can log in using the username.
How can I get it to use the email field instead?
src/Application.php
<?php
namespace App;
use Cake\Core\Configure;
use Cake\Core\Exception\MissingPluginException;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\BaseApplication;
use Cake\Http\Middleware\SecurityHeadersMiddleware;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Cake\Http\Middleware\EncryptedCookieMiddleware;
class Application extends BaseApplication
{
/**
* {#inheritDoc}
*/
public function bootstrap()
{
// Call parent to load bootstrap from files.
parent::bootstrap();
$this->addPlugin('AuditStash');
if (PHP_SAPI === 'cli') {
try {
$this->addPlugin('Bake');
} catch (MissingPluginException $e) {
// Do not halt if the plugin is missing
}
$this->addPlugin('Migrations');
}
/*
* Only try to load DebugKit in development mode
* Debug Kit should not be installed on a production system
*/
if (Configure::read('debug')) {
$this->addPlugin(\DebugKit\Plugin::class);
}
$this->addPlugin(\CakeDC\Users\Plugin::class);
Configure::write('Users.config', ['users']);
$identifiers = Configure::read('Auth.Identifiers');
$identifiers['Authentication.Password']['fields']['username'] = 'email';
Configure::write('Auth.Identifiers', $identifiers);
}
/**
* Setup the middleware queue your application will use.
*
* #param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
* #return \Cake\Http\MiddlewareQueue The updated middleware queue.
*/
public function middleware($middlewareQueue)
{
$securityHeaders = new SecurityHeadersMiddleware();
$securityHeaders
->setCrossDomainPolicy()
->setReferrerPolicy()
->setXFrameOptions()
->setXssProtection()
->noOpen()
->noSniff();
$middlewareQueue
// Add security headers
->add($securityHeaders)
// Catch any exceptions in the lower layers,
// and make an error page/response
->add(ErrorHandlerMiddleware::class)
// Handle plugin/theme assets like CakePHP normally does.
->add(new AssetMiddleware([
'cacheTime' => Configure::read('Asset.cacheTime')
]))
// Add routing middleware.
// Routes collection cache enabled by default, to disable route caching
// pass null as cacheConfig, example: `new RoutingMiddleware($this)`
// you might want to disable this cache in case your routing is extremely simple
->add(new RoutingMiddleware($this, '_cake_routes_'));
$cookies = new EncryptedCookieMiddleware(
// Names of cookies to protect
['remember_me', 'csrfToken'],
Configure::read('Security.cookieKey')
);
$middlewareQueue->add($cookies);
return $middlewareQueue;
}
}
src/config/users.php
<?php
return [
// Table used to manage users
'table' => 'Users',
// Controller used to manage users plugin features & actions
'controller' => 'Users',
// configure Auth component
'auth' => true,
// Password Hasher
'passwordHasher' => '\Cake\Auth\DefaultPasswordHasher',
// token expiration, 1 hour
'Token' => ['expiration' => 3600],
'Email' => [
// determines if the user should include email
'required' => true,
// determines if registration workflow includes email validation
'validate' => true,
],
'Registration' => [
// determines if the register is enabled
'active' => false,
// determines if the reCaptcha is enabled for registration
'reCaptcha' => true,
// allow a logged in user to access the registration form
'allowLoggedIn' => false,
//ensure user is active (confirmed email) to reset his password
'ensureActive' => false,
// default role name used in registration
'defaultRole' => 'user',
],
'reCaptcha' => [
// reCaptcha key goes here
'key' => null,
// reCaptcha secret
'secret' => null,
// use reCaptcha in registration
'registration' => false,
// use reCaptcha in login, valid values are false, true
'login' => false,
],
'Tos' => [
// determines if the user should include tos accepted
'required' => true,
],
'Social' => [
// enable social login
'login' => false,
// enable social login
'authenticator' => 'CakeDC/Users.Social',
],
'GoogleAuthenticator' => [
// enable Google Authenticator
'login' => false,
'issuer' => null,
// The number of digits the resulting codes will be
'digits' => 6,
// The number of seconds a code will be valid
'period' => 30,
// The algorithm used
'algorithm' => 'sha1',
// QR-code provider (more on this later)
'qrcodeprovider' => null,
// Random Number Generator provider (more on this later)
'rngprovider' => null
],
'Profile' => [
// Allow view other users profiles
'viewOthers' => false,
'route' => ['plugin' => 'CakeDC/Users', 'controller' => '\Users', 'action' => 'profile'],
],
'Key' => [
'Session' => [
// session key to store the social auth data
'social' => 'Users.social',
// userId key used in reset password workflow
'resetPasswordUserId' => 'Users.resetPasswordUserId',
],
// form key to store the social auth data
'Form' => [
'social' => 'social'
],
'Data' => [
// data key to store the users email
'email' => 'email',
// data key to store email coming from social networks
'socialEmail' => 'info.email',
// data key to check if the remember me option is enabled
'rememberMe' => 'remember_me',
],
],
// Avatar placeholder
'Avatar' => ['placeholder' => 'CakeDC/Users.avatar_placeholder.png'],
'RememberMe' => [
// configure Remember Me component
'active' => false,
'checked' => true,
'Cookie' => [
'name' => 'remember_me',
'Config' => [
'expires' => '1 month',
'httpOnly' => true,
]
]
],
];
src/Template/Plugin/CakeDC/Users/Users/login.ctp
<?php
use Cake\Core\Configure;
?>
<div class="users form">
<?= $this->Flash->render('auth') ?>
<?= $this->Form->create() ?>
<fieldset>
<legend><?= __d('CakeDC/Users', 'Please enter your username and password') ?></legend>
<?= $this->Form->control('email', ['required' => true]) ?>
<?= $this->Form->control('password', ['required' => true]) ?>
</fieldset>
<?= $this->Form->button(__d('CakeDC/Users', 'Login')); ?>
<?= $this->Form->end() ?>
</div>
Add this code
$identifiers = Configure::read('Auth.Identifiers');
$identifiers['Authentication.Password']['fields']['username'] = 'email';
Configure::write('Auth.Identifiers', $identifiers);
to your pluginBootstrap() function in Application, or ensure the identifier configuration is correctly updated in the config/users.php file, so the plugin will be able to read it and configure Authentication internally.

Unable to retrieve a cached piece of information that was saved with Laravel app, but trying to get within a phalcon app

I am trying to save a string with one application to memcached. And then after an http redirect, trying to retrieve that information from a different application on the same server. I am able to save, but retrieving the information is not working.
I have made sure that both applications are not applying a prefix to the cache key. I have run 'memcached-tool localhost:11211 dump | less' on the memcache server to ensure the data exists, which it does. I can access the data from the same application that saves the data. So if I save from the Laravel app, I can retrieve from the laravel app, and vice versa for the phalcon app.
The environment variables used are the same in both apps.
Setup in Laravel (cache.php):
'memcached' => [
'driver' => 'memcached',
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 0,
],
],
],
How I am saving in Laravel:
Cache::put($sha, $viewData, 60);
return redirect("someUrl/{$sha}", 302);
Setup in Phalcon:
$di->setShared('memcached', function(){
// Cache data for one hour
$frontCache = new \Phalcon\Cache\Frontend\Data(
[
'lifetime' => 60,
'prefix' => ''
]
);
// Create the component that will cache 'Data' to a 'Memcached' backend
// Memcached connection settings
return new \Phalcon\Cache\Backend\Libmemcached(
$frontCache,
[
'prefix' => '',
'servers' => [
[
'host' => MEMCACHED_SERVER,
'port' => MEMCACHED_PORT,
'weight' => 0,
]
]
]
);
});
How I am trying to retrieve info in Phalcon:
$cache = $this->di->getShared('memcached');
$info = $cache->get($cacheKey);
Here is the output of the $cache var from xdebug:
Phalcon\Cache\Backend\Libmemcached::__set_state(array(
'_frontend' =>
Phalcon\Cache\Frontend\Data::__set_state(array(
'_frontendOptions' =>
array (
'lifetime' => 60,
'prefix' => '',
),
)),
'_options' =>
array (
'prefix' => '',
'servers' =>
array (
0 =>
array (
'host' => 'servername',
'port' => port,
'weight' => 0,
),
),
'statsKey' => '',
),
'_prefix' => '',
'_lastKey' => '38404efbc4fb72ca9cd2963d1e811e95fe76960b',
'_lastLifetime' => NULL,
'_fresh' => false,
'_started' => false,
'_memcache' =>
Memcached::__set_state(array(
)),
))
And here is the dump from memcached-tool:
Dumping memcache contents
add 38404efbc4fb72ca9cd2963d1e811e95fe76960b 4 1562346522 144
a:5:{s:3:"zip";s:0:"";s:10:"first_name";s:5:"Clint";s:9:"last_name";s:9:"Broadhead";s:5:"phone";s:0:"";s:5:"email";s:20:"blah#blah.com";}
I expected to be able to save to memcache from any application and then retrieve that cache from anywhere else that has access to the same server. But when I try to retrieve in the phalcon app I receive 'null'. Even though I can see the key/value pair on the server. Thanks in advance for your assistance.
I bypassed using Phalcons built-in backend and frontend classes and just went with using the PHP ext-memcached on the phalcon app.
$di->setShared('memcached', function(){
if( !($this->_memcache instanceof \Memcached) ){
if( !class_exists('Memcached') ){
throw new CacheException('Unable to connect to cache');
}
$this->_memcache = new \Memcached();
$this->_memcache->addServer(MEMCACHE_SERVER, MEMCACHE_PORT)
return $this->_memcache;
}
});
I started to go down the path of phalcons use of '_PHCM', but once the above solution worked I abandoned that research. Not the best, but works well for my temporary situation.
Phalcon uses prefixes for all cache keys by default. For the Libmemcached adapter
For instance the get for the Libmemcached adapter has:
let prefixedKey = this->_prefix . keyName;
let this->_lastKey = prefixedKey;
let cachedContent = memcache->get(prefixedKey);
https://github.com/phalcon/cphalcon/blob/3.4.x/phalcon/cache/backend/libmemcached.zep#L160
Just make sure that there are no prefixes by setting the prefix option in your options
$di->setShared('memcached', function(){
// Cache data for one hour
$frontCache = new \Phalcon\Cache\Frontend\Data(
[
'lifetime' => 60,
]
);
// Create the component that will cache 'Data' to a 'Memcached' backend
// Memcached connection settings
return new \Phalcon\Cache\Backend\Libmemcached(
$frontCache,
[
'prefix' => '',
'servers' => [
[
'host' => MEMCACHED_SERVER,
'port' => MEMCACHED_PORT,
'weight' => 0,
]
]
]
);
});
https://github.com/phalcon/cphalcon/blob/3.4.x/phalcon/cache/backend.zep#L59
Finally if the above does not work, install a utility that will allow you to query your Memcached instance and see what keys are stored there. Check it before you store data with Laravel and then afterwards. This way you will know whether you check the correct thing or not.
Alternatively you can use good old telnet if you are comfortable with the command line to check the keys.

The "access_key" option must be provided to use fixer.io

For the currency conversion i am using "florianv/laravel-swap": "^1.1" library. Florianv/Laravel-swap.
As Fixer.io has changed its implementation, it is necessary to pass the access_key with the request, and because of that i am getting this error: "InvalidArgumentException: The "access_key" option must be provided to use fixer.io in /var/www/project/project-files/vendor/florianv/exchanger/src/Service/Fixer.php:51".
I registered and got the access_key.
I updated the library using composer and now i can see three constants in the vendor/florianv/exchanger/src/Service/Fixer.php.
const ACCESS_KEY_OPTION = 'access_key';
const LATEST_URL = 'http://data.fixer.io/api/latest?base=%s&access_key=%s';
const HISTORICAL_URL = 'http://data.fixer.io/api/%s?base=%s&access_key=%s';
To pass the access key i tried this:
I have a swap.php in config folder which looks something like this:
return [
'options' => [
'cache_ttl' => 86400, // 24 hours.
'cache_key_prefix' => 'currency_rate'
],
'services' => [
'fixer' => true,
],
'currency_layer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa', // Your app id
'enterprise' => true, // True if your AppId is an enterprise one
],
'cache' => env('CACHE_DRIVER', 'file'),
'http_client' => null,
'request_factory' => null,
'cache_item_pool' => null,
];
This had one more option which was commented, i enabled and passed the access_key in it but it doesn't work.
I also added it in services block below 'fixer => true'.
'currency_layer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa'
]
Also in options block:
'options' => [
'cache_ttl' => 86400, // 24 hours.
'cache_key_prefix' => 'currency_rate',
'access_key'=>'7ca208e9136c5e140d6a14427bf9ed21'
],
I tried with adding access_key in config/services.php file but it also didn't work.
'fixer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa'
],
Even i tried, adding to env file and calling from there, but no success. How do i pass the access_key, can anyone help me on this, what should be the approach.
vendor/florianv/exchanger/src/Service/Fixer.php -> don't touch the constant (that was my own error).
Pass the options-array by creating the Builder:
$options = ['access_key' => 'YourGeneratedAPIKeyAtCurrencyLayer'];
$this->exchangeSwap = (new Builder($options))
->add('fixer', $options )
->build();
I hope I could help ;-)

Setting/retrieving mode in Slim Framework V3

I am new to Slim Framework (PHP). I was going through tutorials and testing some code and tried the following:
$app=new App([
'mode'=>file_get_contents(INC_ROOT . '/mode.php')
]);
echo $app->config('mode');
The output was blank.
It seems the function config() works with V2, however I am using V3. What am I doing wrong?
Slim 3 no longer has a config method. Instead, you must add configuration settings through the dependency injection container:
$app = new \Slim\App([
'settings' => [
'mode' => true
]
]);
$container = $app->getContainer();
echo $container->get('settings')['mode'];
A few things worth noting:
Slim 3 no longer handles managing different versions of configuration settings through a mode setting. So, you can set a value for a setting variable called mode as I've demonstrated here, but it won't actually do anything (i.e., Slim won't use it to determine your environment).
As an alternative, you can check out userfrosting/Config, a library we've been working on that can search multiple directories and different environment configuration files, merging together their contents:
/path/to/config/default.php
return [
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#cleansthetoilet.com'
]
]
];
/path/to/config/production.php
return [
'contacts' => [
'housekeeper' => [
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
];
index.php
$app = new \Slim\App();
$container = $app->getContainer();
// Site config object (separate from Slim settings)
$container['config'] = function ($c) {
// Create and inject new config item
$config = new \UserFrosting\Config\Config();
$config->setPaths([
'/path/to/config'
]);
$config->loadConfigurationFiles('production');
return $config;
};
This will recursively merge in the settings from development.php with those in default.php, updating settings with the same name and scope as necessary:
Running print_r($container['config']); returns:
[
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
]
Notice that the value of contacts.housekeeper.email has been updated to 'alex#istheboss.com', and that the database config info has been merged in. Incidentally, you can also access config settings using the more convenient "dot syntax":
$config = $container->get('config');
echo $config['contacts.housekeeper.email'];
// Easier to type instead of $config['contacts']['housekeeper']['email'];
We recommend injecting this as a separate config service in Slim, rather than using their settings array.
You can combine this with phpdotenv to load settings from your system environment, or any .env files you create:
/path/to/config/production.php
return [
'database' => [
'password' => getenv('DB_PASSWORD')
]
];

Categories