How to change image directory in spatie media library? - laravel

I'm new to this package since I usually use the image intervention. So the problem is whenever I save an image, it is saving in storage directory instead of in public directory. I tried to review the documentation and did some research and didn't see any answer. Please see my code below for saving.
$user->addMediaFromRequest('avatar')->toMediaCollection('avatars');
Package:
https://spatie.be/docs/laravel-medialibrary/v7/introduction

you can simply define the filesystem config with $user->addMediaFromRequest('avatar')->toMediaCollection('avatars', 'disk');
here is how im using the package(simple usage), if you need it later
on my model:
use Spatie\MediaLibrary\HasMedia\HasMedia;
use Spatie\MediaLibrary\HasMedia\HasMediaTrait;
use Spatie\MediaLibrary\Models\Media;
class User extends Model implements HasMedia
{
use HasMediaTrait;
public function registerMediaCollections()
{
$this
->addMediaCollection('avatar')
->singleFile()
->useDisk('public');
->useFallbackUrl(asset('/images/default-user.png'))
->useFallbackPath(public_path('/images/default-user.png'));
}
public function registerMediaConversions(Media $media = null)
{
$this->addMediaConversion('avatar-thumb')->width(120)->height(120);
}
...
to get the file-url im using model accessor, added this function in my model:
public function getAvatarAttribute()
{
$file = $this->getMedia('avatar')->first();
if($file) {
$file->url = $file->getUrl();
$file->thumbnail = $file->getUrl('avatar-thumb'));
$file->alternate = $file->getCustomProperty('alternate') ?: $file->file_name;
}else{
$file = new \StdClass;
$file->url = $this->getFallbackMediaUrl('avatar');
$file->thumbnail = public_path('/images/default-user-thumb.png');
$file->alternate = 'avatar.png';
}
return $file;
}
also using custom path, in config\medialibrary.php set this array 'path_generator' => Path\To\Your\Class::class,
then the class :
use Spatie\MediaLibrary\Models\Media;
use Spatie\MediaLibrary\PathGenerator\PathGenerator as PathGenerators;
class PathGenerator implements PathGenerators
{
public function getPath(Media $media): string
{
return $this->getBasePath($media).'/';
}
public function getPathForConversions(Media $media): string
{
return $this->getBasePath($media).'/conversions/';
}
public function getPathForResponsiveImages(Media $media): string
{
return $this->getBasePath($media).'/responsive-images/';
}
protected function getBasePath(Media $media): string
{
//here im using trait to generate default path, e.g: path/mimes/avatar/media->id
//its up to you to define folder structure, just make sure each folder
//for conversions has unique name, or else it will be deleted
$base_folder = $this->get_base_folder($media->mime_type);
return "{$base_folder}/{$media->collection_name}/{$media->getKey()}";
}
}

Related

Laravel - Delete if no relationship exists

Below is the one of the model. I would like to delete a Telco entry only if no other model is referencing it? What is the best method?
namespace App;
use Illuminate\Database\Eloquent\Model;
class Telco extends Model
{
public function operators()
{
return $this->hasMany('App\Operator');
}
public function packages()
{
return $this->hasMany('App\Package');
}
public function topups()
{
return $this->hasMany('App\Topup');
}
public function users()
{
return $this->morphMany('App\User', 'owner');
}
public function subscribers()
{
return $this->hasManyThrough('App\Subscriber', 'App\Operator');
}
}
You can use deleting model event and check if there any related records before deletion and prevent deletion if any exists.
In your Telco model
protected static function boot()
{
parent::boot();
static::deleting(function($telco) {
$relationMethods = ['operators', 'packages', 'topups', 'users'];
foreach ($relationMethods as $relationMethod) {
if ($telco->$relationMethod()->count() > 0) {
return false;
}
}
});
}
$relationships = array('operators', 'packages', 'topups', 'users', 'subscribers');
$telco = Telco::find($id);
$should_delete = true;
foreach($relationships as $r) {
if ($telco->$r->isNotEmpty()) {
$should_delete = false;
break;
}
}
if ($should_delete == true) {
$telco->delete();
}
Well, I know this is ugly, but I think it should work. If you prefer to un-ugly this, just call every relationship attributes and check whether it returns an empty collection (meaning there is no relationship)
If all relationships are empty, then delete!
After seeing the answers here, I don't feel copy pasting the static function boot to every models that need it. So I make a trait called SecureDelete. I put #chanafdo's foreach, inside a public function in SecureDelete.
This way, I can reuse it to models that need it.
SecureDelete.php
trait SecureDelete
{
/**
* Delete only when there is no reference to other models.
*
* #param array $relations
* #return response
*/
public function secureDelete(String ...$relations)
{
$hasRelation = false;
foreach ($relations as $relation) {
if ($this->$relation()->withTrashed()->count()) {
$hasRelation = true;
break;
}
}
if ($hasRelation) {
$this->delete();
} else {
$this->forceDelete();
}
}
}
Add use SecureDelete to the model that needs it.
use Illuminate\Database\Eloquent\Model;
use App\Traits\SecureDelete;
class Telco extends Model
{
use SecureDelete;
public function operators()
{
return $this->hasMany('App\Operator');
}
// other eloquent relationships (packages, topups, etc)
}
TelcoController.php
public function destroy(Telco $telco)
{
return $telco->secureDelete('operators', 'packages', 'topups');
}
In addition, instead of Trait, you can also make a custom model e.g BaseModel.php that extends Illuminate\Database\Eloquent\Model, put the function secureDelete there, and change your models to extends BaseModel.

Laravel - how to makeVisible an attribute in a Laravel relation?

I use in my model code to get a relation
class User extends Authenticatable
{
// ...
public function extensions()
{
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
// ...
}
The Extension has field password hidden.
class Extension extends Model
{
// ...
protected $hidden = [
'password',
];
// ...
}
Under some circumstances I want to makeVisible the password field.
How can I achieve this?
->makeVisible([...]) should work:
$model = \Model::first();
$model->makeVisible(['password']);
$models = \Model::get();
$models = $models->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many
$related = $parent->relation->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many - with loading
$related = $parent->relation()->get()->each(function ($i, $k) {
$i->makeVisible(['password']);
});
Well, I got the idea from https://stackoverflow.com/a/38297876/518704
Since my relation model Extension::class is called by name in my code return $this->belongsToMany(Extension::class,... I cannot even pass parameter to it's constructor.
So to pass something to the constructor I may use static class variables.
So in my Extension model I add static variables and run makeVisible method.
Later I destruct the variables to be sure next calls and instances use default model settings.
I moved this to a trait, but here I show at my model example.
class Extension extends Model
{
public static $staticMakeVisible;
public function __construct($attributes = array())
{
parent::__construct($attributes);
if (isset(self::$staticMakeVisible)){
$this->makeVisible(self::$staticMakeVisible);
}
}
.....
public function __destruct()
{
self::$staticMakeVisible = null;
}
}
And in my relation I use something like this
class User extends Authenticatable
{
...
public function extensions()
{
$class = Extension::class;
$class::$staticMakeVisible = ['password'];
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
...
}
The highest voted answer didn't seem to work for me (the relations attribute seems to be a protected array now so can't be used as a collection in #DevK's answer), I instead used:
$parent->setRelation('child', $parent->child->first()->setVisible(['id']));

Laravel one-to-many save Carbon error

I have a class called SubjectData:
class SubjectData extends Model
{
protected $table = 'subject_datas';
protected $fillable = ['firstname','lastname','birthdate','birthcity','months'];
protected $dates = ['birthdate'];
public function setBirthdateAttribute($date)
{
// So we can add the time, not just he php date
$this->attributes['birthdate'] = Carbon::createFromFormat('d/m/Y', $date);
}
public function anamnesis() {
return $this->belongsTo('App\Anamnesis');
}
}
And I have a class called Anamnesis:
class Anamnesis extends Model
{
public function meetingTest() {
return $this->belongsTo('App\MeetingTest');
}
public function subject() {
return $this->belongsTo('App\Subject','subject_id','id');
}
public function subjectData() {
return $this->hasOne('App\SubjectData');
}
public function scholarHistory() {
return $this->hasOne('App\ScholarHistory');
}
public function familyHistory() {
return $this->hasOne('App\FamilyHistory');
}
public function psicodiagnosis() {
return $this->hasOne('App\Psicodiagnosis');
}
}
The store function of the SubjectController class works like this:
public function store(CreateSubjectRequest $request)
{
$input = $request->all();
// Let's generate the anamnesis of the subject
$anamnesis = Anamnesis::create();
$anamnesis->save();
$newSubjectData = $this->saveSubjectData($input);
$anamnesis->subjectData()->save($newSubjectData);
......
......
}
where the function called is:
public function saveSubjectData($input)
{
$subjectData['firstname'] = $input['firstname'];
$subjectData['lastname'] = $input['lastname'];
$subjectData['birthcity'] = $input['birthcity'];
$subjectData['birthdate'] = $input['birthdate'];
return SubjectData::create($subjectData);
}
The problem is with the "birthdate" property.
If i check the value of $newSubjectData (dd($newSubjectdata)) after the call $this->saveSubjectData($input) the value of the birthdate is exactly the one i set on the calendar in the frontside (and also in the db the value is correct)
If I put the dd($anamnesis->subjectData) after the call $anamnesis->subjectData()->save($newSubjectData) the result is the "today" date and also in the DB the value is not the one I set but the date of today.
I can't find the error
P.S. The calendar is inside a Vue template
I think the problem is that, the date must be an instance of Carbon or is properly formatted according to your database table. Try the following inside your saveSubjectData() method
$subjectData['birthdate'] = Carbon\Carbon::parse($input['birthdate']
I write down the answer but i thank John Aldrin that guided me in the right direction.
You have to put the timestamp('birthdate') AFTER the default timestamps of the migration table (so at the end of the migration table)
I don't know why. If someone knows please explain !

Using Doctrine in Joomla 1.5

I'm trying to use Doctrine in Joomla 1.5 but have not been able to get anything running.
According to this article: http://magazine.joomla.org/issues/issue-may-2011/item/447-using-doctrine-ORM-in-joomla
I immediately get Fatal error: Class 'Fatal error: Call to undefined method JController::getInstance() in /var/www/html/hosts/joomla/public_html/components/com_bugs/bugs.php on line 13
The bugs.php looks like this:
// no direct access
defined('_JEXEC') or die; // Include dependancies
jimport('joomla.application.component.controller');
//require_once(JPATH_LIBRARIES . '/doctrine/vendor/autoload.php');
require_once(JPATH_LIBRARIES . '/doctrine/bootstrap.php');
require_once(JPATH_LIBRARIES . '/doctrine/JoomlaDoctrineBootstrapper.php');
require_once(JPATH_COMPONENT.DS.'controller.php');
//$controller = new BugsController(JRequest::getVar('task', ''));
$controller = JController::getInstance('Bugs');
Not sure how to implement this, when trying to use the $controller = new BugsController the error is: Fatal error: Class 'JController' not found in
This because I have the autoload on in bugs.php and have /public_html/components/com_bugs/controller.php extend /public_html/libraries/doctrine/JoomlaDoctrineBootstrapper.php the JoomlaDoctrineBootstrapper exends JController but JController cannot be found anymore after composer and the autoload did something.
I'm starting to think that it's not possible to use Joomla with Doctrine since Doctrine has to be installed with composer (didn't find any other documentation on how to download and configure it) and composer seems to want everything in vendor so have to put all the Joomla classes in vendor too?
[UPDATE]
It looks like whatever composer does in /public_html/libraries/doctrine/vendor/autoload.php completely breaks jimport('joomla.application.component.controller')
Not including the autoload however gives me another problem, like none of the Doctrine classes are found: Class 'Doctrine\Common\Cache\ArrayCache' not found
Maybe I'll try and hack /public_html/libraries/doctrine/vendor/composer/autoload_real.php to try and see if that one can load Joomla classes for me.
Either jimport or composer won't work because jimport defines __autoload. Instead of __autoload I'm using spl_autoload_register that only seems to work with PHP version starting from 5.1.2.
Changed loader:
/public_html/libraries/loader.php
class JLoader
{
public static function autoload($class)
{
if(JLoader::load($class)) {
return true;
}
return false;
}
//... other code and comments
function import( $filePath, $base = null, $key = 'libraries.' )
{
static $paths;
if (!isset($paths)) {
$paths = array();
//assuming PHP 5 >= 5.1.2
spl_autoload_register(array('JLoader', 'autoload'), true, true);
}
//remove the __autoload function
The bugs.php looks like this:
/public_html/components/com_bugs/bugs.php
<?php
// no direct access
defined('_JEXEC') or die; // Include dependancies
require_once(JPATH_LIBRARIES . '/doctrine/vendor/autoload.php');
require_once(JPATH_LIBRARIES . '/doctrine/bootstrap.php');
require_once(JPATH_LIBRARIES . '/doctrine/JoomlaDoctrineBootstrapper.php');
require_once(JPATH_COMPONENT.DS.'controller.php');
//using links like /index.php?option=com_bugs&format=text&task=save
// defaults to link so above is same as: http://joomla/index.php?option=com_bugs&format=text&task=save&router=link
$route=JRequest::getVar('router', 'Link');
$controllerName = 'bugsController'.$route;
//include the controller
include_once(dirname(__FILE__) . '/controllers/'.$route.".php");
$controller = new bugsControllerlink(JRequest::getVar('task', ''));
$controller->setEntityManager(bootstrapDoctrine());
$controller->execute(JRequest::getVar('task', ''));
$controller->redirect();
/**
* Initialize doctrine by setting the entities and proxies locaties. Also define
* a default namespace for the proxies.
*/
function bootstrapDoctrine() {
$doctrineProxy = new JoomlaDoctrineBootstrapper(JoomlaDoctrineBootstrapper::APP_MODE_DEVELOPMENT);
$doctrineProxy->setEntityLibrary(dirname(__FILE__) . '/models');
$doctrineProxy->setProxyLibrary(dirname(__FILE__) . '/proxies');
$doctrineProxy->setProxyNamespace('Joomla\Proxies');
$doctrineProxy->setConnectionOptions(getConfigurationOptions());
$doctrineProxy->bootstrap();
return $doctrineProxy->getEntityManager();
}
function getConfigurationOptions() { // Define database configuration options
$joomlaConfig = JFactory::getConfig();
return array('driver' => 'pdo_mysql', 'path' => 'database.mysql'
, 'dbname' => $joomlaConfig->getValue("config.data.db")
, 'user' => $joomlaConfig->getValue("config.data.user")
, 'password' => $joomlaConfig->getValue("config.data.password"));
}
?>
The link controller looks like: (file name has start with a capital L)
/public_html/components/com_bugs/controllers/Link.php
<?php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
// import Joomla controller library (done by composer)
//jimport('joomla.application.component.controller');
class bugsControllerlink extends JoomlaDoctrineBootstrapper{
function __construct()
{
parent::__construct();
$this->registerTask( 'show','show' );
$this->registerTask( 'save','save' );
}
function save(){
$model=$this->getModel('link');
$view = $this->getView('show','text');
$view->setLayout('save');
$model->em=$this->getEntityManager();
$view->data = $model->save();
$view->display();
}
}
Didn't change the boodtrap, that still looks like this:
/public_html/libraries/doctrine/bootstrap.php
<?php
interface JoomlaDoctrineController {
public function setEntityManager(Doctrine\ORM\EntityManager $entityManager);
}
?>
The /public_html/libraries/doctrine/JoomlaDoctrineBootstrapper.php looks like:
<?php
/** * Configuration class to integrate Doctrine into Joomla. *
* #author pderaaij <removed email, check link in question> */
use Composer\Autoload\ClassLoader,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration,
Doctrine\Common\Cache\ArrayCache;
jimport( 'joomla.application.component.controller' );
class JoomlaDoctrineBootstrapper extends JController{
const APP_MODE_DEVELOPMENT = 1;
const APP_MODE_PRODUCTION = 2;
private $applicationMode;
private $cache;
private $entityLibrary;
private $proxyLibrary;
private $proxyNamespace;
private $entityManager;
private $connectionOptions;
public function __construct($applicationMode=1) {
$this->applicationMode = $applicationMode;
$this->_name="bugs";
parent::__construct();
}
public function getConnectionOptions() {
return $this->connectionOptions;
}
public function setConnectionOptions($connectionOptions) {
$this->connectionOptions = $connectionOptions;
}
public function getProxyLibrary() {
return $this->proxyLibrary;
}
public function setProxyLibrary($proxyLibrary) {
$this->proxyLibrary = $proxyLibrary;
}
public function getProxyNamespace() {
return $this->proxyNamespace;
}
public function setProxyNamespace($proxyNamespace) {
$this->proxyNamespace = $proxyNamespace;
}
public function getCache() {
return $this->cache;
}
public function setCache($cache) {
$this->cache = $cache;
}
public function getEntityLibrary() {
return $this->entityLibrary;
}
public function setEntityLibrary($entityLibrary) {
$this->entityLibrary = $entityLibrary;
}
public function getApplicationMode() {
return $this->applicationMode;
}
public function setApplicationMode($applicationMode) {
$this->applicationMode = $applicationMode;
}
public function getEntityManager() {
return $this->entityManager;
}
public function setEntityManager($entityManager) {
$this->entityManager = $entityManager;
}
/** * Bootstrap Doctrine, setting the libraries and namespaces and creating * the entitymanager */
public function bootstrap() {
$this->registerClassLoader(); // Load cache
if ($this->getApplicationMode() == self::APP_MODE_DEVELOPMENT) {
$this->cache = new ArrayCache;
} else {
$this->cache = new ApcCache;
} /** #var $config Doctrine\ORM\Configuration */ $config = new Configuration;
$config->setMetadataCacheImpl($this->cache);
$driverImpl = $config->newDefaultAnnotationDriver($this->getEntityLibrary());
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($this->cache);
$config->setProxyDir($this->getProxyLibrary());
$config->setProxyNamespace($this->getProxyNamespace());
if ($this->applicationMode == self::APP_MODE_DEVELOPMENT) {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
} $this->entityManager = EntityManager::create($this->getConnectionOptions(), $config);
}
/** * Register the different classloaders for each type. */
private function registerClassLoader() { // Autoloader for all the Doctrine library files
//Doctrine was done by public_html/libraries/doctrine/vendor/autoload.php
// $classLoader = new ClassLoader('Doctrine', dirname(__FILE__) . '/');
// $classLoader->register(); // Autoloader for all Entities
//name of ComposerAutoloader is defined in /public_html/libraries/doctrine/vendor/composer/autoload_real.php
$modelLoader = ComposerAutoloaderInit825f56ea1383e6b7fef7ea99c51fea36::getLoader();
$modelLoader->set("Entities\\",dirname(__FILE__)."/../../components/com_"
//not sure how to do the proxies yet, have to check this with production settings
// $proxiesClassLoader = new ClassLoader('Proxies', $this->getProxyLibrary());
// $proxiesClassLoader->register();
}
}
?>
The Joomla model save function looks something like this (checking received JSON should be done in a controller or helper function):
public function save() {
//a textbox having the name 'json' or xhr post
$link = JRequest::getVar('json',false,'post');
if($link==false){
return;
}
$link = json_decode($link);
$newLink = new Link();
$newLink->setId($link->id);
$newLink->setName($link->name);
foreach($link->categories as $category){
$cat = new Category();
$cat->setId($category->id);
$cat->setName($category->name);
$newLink->addCategorie($cat);
}
$this->em->persist($newLink);
$this->em->flush();
return $link;
}
I guess the code as is will break when using APP_MODE_PRODUCTION in /public_html/components/com_bugs/bugs.php

Laravel4 - Saving a model with multiple relationships/foreign keys

I've tried to understand a process of saving a model with multiple relationships but I still can't figure out how to do it "kosher" way.
To begin with - I have an Event model that belongs to a category (Eventcat) and a Location:
// Event.php
class Event extends Eloquent {
protected $table = 'events';
public function location()
{
return $this->belongsTo('Location');
}
public function eventcat()
{
return $this->belongsTo('Eventcat');
}
public function users()
{
return $this->belongsToMany('User');
}
}
// Location.php
class Location extends Eloquent
{
protected $table = 'locations';
public function events()
{
return $this->hasMany('Event');
}
}
// Eventcat.php
class Eventcat extends Eloquent
{
protected $table = 'eventcats';
public function events()
{
return $this->hasMany('Event');
}
}
I've seeded the database with a few categories and locations and now I trying to get events saving work. I thought that the $event->eventcat()->associate( $eventcat ) would work but I got a Call to undefined method eventcat() error.
public function postCreateEvent() {
$event = new Event();
$eventcat = Eventcat::find( Input::get('event-create-eventcat[]') );
$location = Location::find( Input::get('event-create-location[]') );
$event->title = Input::get('event-create-title');
$event->description = Input::get('event-create-description');
$event->price = Input::get('event-create-price');
$event->start_date = Input::get('event-create-start_date');
$event->end_date = Input::get('event-create-end_date');
$event->eventcat()->associate( $eventcat );
$event->location()->associate( $location );
$event->save();
}
I've read the documentation, API and a few threads here but I still can't figure out the best way to deal with this.
Thanks for replies!
I would actually bet that you have a conflict in your class name. Laravel contains an Event class and I wonder if that isn't what's being called in your code. As a quick test, you could rename your class FooEvent and see if it works.
The best solution is probably namespacing your model (see http://chrishayes.ca/blog/code/laravel-4-methods-staying-organized for a quick intro) so that your model can still be called Event without conflicting with the builtin class.

Resources