CakePhp TranslateBehavior, validate and save multiple locale - mysql

Context:
I Want to create a web application using CakePhp which should be translatable. I want to save multiple translations for the same field in one form.
Problem:
I've tried a dozen ways to get this to work and I did. But I ended up using two custom SQL queries which really doesn't feel like a cakePhp solution.
Question:
Does anybody know a better way to achieve the same result?
What I tried:
Giving the form fields a name like 'Model.fieldName.locale', which gives it the right format in the name attr of the input element but then my validation doesn't recognize the field name. But saving works.
Giving the form fields a name like 'modelLocale' and pass in a name attr 'data[Model][field][locale]', in this case the validation works exept for isUnique but saving to the database doesn't work.
More variations of this but not worth mentioning.
I'll add my view and model below: (if u want to see more code or need more info just ask)
/App/View/Category/add.ctp
<?php echo $this->Form->create(); ?>
<?php echo $this->Form->input('title|dut'); ?>
<?php echo $this->Form->input('title|eng'); ?>
<?php echo $this->Form->input('title|fre'); ?>
<?php echo $this->Form->input('description|dut', array('type'=>'textarea')); ?>
<?php echo $this->Form->input('description|eng', array('type'=>'textarea')); ?>
<?php echo $this->Form->input('description|fre', array('type'=>'textarea')); ?>
<?php echo $this->Form->end('add'); ?>
/App/Model/AppModel.php
<?php
App::uses('Model', 'Model');
class AppModel extends Model {
/**
* Check Unique
*
* Searches the i18n table to determine wetter a field is unique or not.
* Expects field name to be as following: "fieldname|locale".
*
* #param array $data The data of the field, automatically passed trough by cakePhp.
* #param string $field The name of the field, which should match the one in the view.
* #returns boolean
*/
public function checkUnique($data, $field) {
// Seperate the field key and locale which are seperated by "|".
$a = preg_split('/[|]/', $field, 2);
// If field key and locale are found...
if (is_array($a) || count($a) === 2) {
$q = sprintf("SELECT * FROM i18n WHERE i18n.locale = '%s' AND i18n.model = '%s' AND i18n.field = '%s' AND i18n.content = '%s' LIMIT 1",
Sanitize::escape($a[1]),
Sanitize::escape(strtolower($this->name)),
Sanitize::escape($a[0]),
Sanitize::escape($data[$field])
);
if ($this->query($q)) {
return false;
}
return true;
}
}
/**
* Setup Translation
*
* Loops trough the fields. If a field is translatable
* (which it will know by it's structure [fieldname]|[locale])
* and has the default locale. Then it's value will be stored
* in the array where cake expects it
* (data[Model][fieldname] instead of data[Model][fieldname|defaultLocale])
* so that cake will save it to the database.
*
* In the afterSave method the translations will be saved, for then we know
* the lastInsertId which is also the foreign_key of the i18n table.
*/
public function _setupTranslations() {
foreach($this->data[$this->name] as $key => $value) {
$a = preg_split('/[|]/', $key, 2);
if (is_array($a) && count($a) === 2) {
$languages = Configure::read('Config.languages');
if ($a[1] === $languages[Configure::read('Config.defaultLanguage')]['locale']) {
$this->data[$this->name][$a[0]] = $value;
}
}
}
}
/**
* Save Translations
*
* Saves the translations to the i18n database.
* Expects form fields with translations to have
* following structure: [fieldname]|[locale] (ex. title|eng, title|fre, ...).
*/
public function _saveTranslations() {
foreach($this->data[$this->name] as $key => $value) {
$a = preg_split('/[|]/', $key, 2);
if (is_array($a) && count($a) === 2) {
$q = sprintf("INSERT INTO i18n (locale, model, foreign_key, field, content) VALUES ('%s', '%s', '%s', '%s', '%s')",
Sanitize::escape($a[1]),
Sanitize::escape(strtolower($this->name)),
Sanitize::escape($this->id),
Sanitize::escape($a[0]),
Sanitize::escape($value)
);
$this->query($q);
}
}
}
/**
* Before Save
*/
public function beforeSave() {
$this->_setupTranslations();
return true;
}
/**
* After Save
*/
public function afterSave() {
$this->_saveTranslations();
return true;
}
}
/App/Model/Category.php
<?php
class Category extends AppModel {
public $name = 'Category';
public $hasMany = array(
'Item'=>array(
'className'=>'Item',
'foreignKey'=>'category_id',
'order'=>'Item.title ASC'
)
);
var $actsAs = array(
'Translate'=>array(
'title',
'description'
)
);
public $validate = array(
'title|dut'=>array(
'required'=>array(
'rule'=>'notEmpty',
'message'=>'Veld verplicht'
),
'unique'=>array(
'rule'=>array('checkUnique', 'title|dut'),
'message'=>'Titel reeds in gebruik'
),
),
'title|eng'=>array(
'required'=>array(
'rule'=>'notEmpty',
'message'=>'Veld verplicht'
),
'unique'=>array(
'rule'=>array('checkUnique', 'title|eng'),
'message'=>'Titel reeds in gebruik'
),
),
'title|fre'=>array(
'required'=>array(
'rule'=>'notEmpty',
'message'=>'Veld verplicht'
),
'unique'=>array(
'rule'=>array('checkUnique', 'title|fre'),
'message'=>'Titel reeds in gebruik'
),
),
);
}
?>
NOTE: There isn't that much information out there on this subject... I have a lot more questions about the translation behavior like getting the recursive results also in the correct locale, ... Anybody know a good tut or source of info (cookbook is quite limited)
Thanks for reading!!

It appears you may be building a CRM of sorts that allows the users to establish content that is read into the site based on the language they have set. I would use the built in i18n and l10n. It makes it really simple, but this is probably not a solution for dynamic content.
Having said that, the only other way I can think of doing this is very tedious. I would build a single screen with a language identifier drop down. So instead of trying to cram ALL languages in the same screen with a test box for each language, I would create one form and then use a drop down for the language.
Your model is using a column to define with language the row belongs to. The form you have created is expressing all languages in a single row. So if you were to view the Index page showing the records, of course you would see:
title 1 eng
title 1 dut
title 1 fre
title 2 eng
title 2 dut
title 2 fre
...
Further more, if you were ever to add a new language, you will have to modify the validation in the model and the form.
However, if you are set on doing it this way, change the | to _ and off you go. But then you will need to store all of the data in a single record. So when you look at the Index for the records, you will see:
title 1 end dut fre
title 2 end dut fre
...
My Advice:
1) Use the built in i18n / l10n using .po / .pot files.
2) If the content will be changing frequently and required to be stored in the database so it can be easily changed / updated frequently on the fly, then use a drop down.
Language: dropdown
Title: text_field

Related

how to delete multiple files (codeigniter), according to a specific id?

Hello there i am currently trying to unlink() multiple files from folder with specific category id, getting the right files (names) is no problem und how to unlink a file i know too, but i have a hard time to loop over the result array from the query and then delete not all files but those files out of the query, here is my code:
/**
* delete category by id
* #param $id category_id
* #return boolean
*/
public function delete_images($id){
$this->db->select('post_image');
$query = $this->db->get_where('posts', array('category_id' => $id));
$images = $query->result_array();
if (!empty($images)){
if(!in_array('default_image', $images)){
foreach ($images as $image) {
unlink(FCPATH . 'assets/images/posts/' . $image);
}
}
} else {
$this->db->query("DELETE FROM categories WHERE categories.id = $id");
}
}
Help is appreaciated, thanks.
The actual result is:
Message: Array to string conversion
Filename: models/Category_model.php
Line Number: 71
I think your code should like
unlink('assets/images/posts/' . $image['post_image']);
You missed the index & its become an array but string was expected there
This code Work
unlink(FCPATH . 'assets/images/posts/' . $imageName);

Symfony4.1 Doctrine ManyToMany Reduce No of Queries

I'm working on a project. Entity are Blog,Category,Tags. Blog and Tags are in ManyToMany Relation. My repository query to fetch data by Tags filter is.
CODE1:
/**
* #return BlogPost[]
*/
public function getAllActivePostsByTags($value, $order = "DESC", $currentPage = 1, $limit = 10)
{
$query = $this->createQueryBuilder('p')
// ->select('p','t')
->innerJoin('p.blogTags', 't')
->where('t.slug = :val')
->setParameter('val', $value)
->orderBy('p.id', $order)
->getQuery();
$paginator = $this->paginate($query, $currentPage, $limit);
return $paginator;
}
This code works fine. All the tags(No of tags in a post)are displayed correctly. But the No of DB Query is 14. Then When I uncomment select as this,
CODE2:
/**
* #return BlogPost[]
*/
public function getAllActivePostsByTags($value, $order = "DESC", $currentPage = 1, $limit = 10)
{
$query = $this->createQueryBuilder('p')
->select('p','t')
->innerJoin('p.blogTags', 't')
->where('t.slug = :val')
->setParameter('val', $value)
->orderBy('p.id', $order)
->getQuery();
$paginator = $this->paginate($query, $currentPage, $limit);
return $paginator;
}
No of Query is 9. But The Tags per Post is only one(Not displaying all the tags of a single post).
To be clear info:
It displays entire list of BlogPost.
But not all Tags of a Post.
Only one Tag per Post is shown.
Question: Is code1 is correct (No of DB Query = 14) or Do I have to tweak little bit to reduce no of DB Hits. Please guide me on this.
This is the expected behaviour in both cases.
Case 1) You just select the BlogPost entities. So you tell doctrine to fetch all BlogPosts that have the BlogTag that has slug = value.
The SQL query produced returns only column values from the blog_post table and so only hydrates the BlogPost entities returned, it does not hydrate the collection of BlogTags inside each BlogPost.
When you try to access the tags of a BlogPost a new query is generated to get and hydrate its collection.
That is the reason you get more queries in this case.
Case 2) You select also the filtered BlogTag entities, and doctrine hydrates(puts) only this filtered BlogTag to each BlogPost `s collection.
When you try to access the BlogTags of a BlogPost, you get the filtered one that meets the condition in the querybuilder.
To force doctrine to "reload" the data from the database, you should refresh the blogPost entity:
$em->refresh($blogPost);
and also include refrech option on cascade operations of the relation definition:
#OneToMany(targetEntity="BlogTag", mappedBy="post", cascade={"refresh"})
References:
what cascade refresh means in doctrine 2
refresh objects: different question but same solution
Thanks #Jannes Botis for refresh. But in my case the code itself is wrong. There need a slight change in it.
BlogTags.php
/**
* #ORM\ManyToMany(targetEntity="BlogPost", mappedBy="blogTags")
*/
private $blogPosts;
BlogPost.php
/**
* #var Collection|BlogTags[]
*
* #ORM\ManyToMany(targetEntity="BlogTags", inversedBy="blogPosts", cascade={"refresh"})
* #ORM\JoinTable(
* name="fz__blog_n_tag",
* joinColumns={
* #ORM\JoinColumn(name="blog_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* }
* )
* #ORM\OrderBy({"name": "ASC"})
*/
private $blogTags;
This created the join_table. Allready I have a join_table. Although This code is for reference to someone.
Controller.php
// This is my old Code
$bp = $em->getRepository('App:BlogPost')->getAllActivePostsByTags($slug, "DESC", $page, self::PAGE_LIMIT);
// This is my New Code
$bp = $em->getRepository('App:BlogTags')->getAllActivePostsByTags($slug, "DESC", $page, self::PAGE_LIMIT);
Repository.php
public function getAllActivePostsByTags($value, $order = "DESC", $currentPage = 1, $limit = 10)
{
$query = $this->createQueryBuilder('t')
->select('t','p','tx')
->innerJoin('t.blogPosts', 'p')
->innerJoin('p.blogTags', 'tx')
->where('p.isActive = :val1')
->andWhere('t.slug = :val2')
->setParameter('val1', true)
->setParameter('val2', $value)
->orderBy('p.id', $order)
->getQuery();
$paginator = $this->paginate($query, $currentPage, $limit);
return $paginator;
}
I not changed my old twig file completely. As it throws error at many places. Because now i'm using tags repo instead of blog. So i modified the twig with
{% include 'frontend/page/blog_home.html.twig' with { 'bp':bp|first.blogPosts } %}
Help me on this (twig file): There is only one tag, that's why |first twig filter
Clarify me with this twig filter. Do I'm doing right. Give me suggestion to improve on it. I tried bp[0] This trows error.
Finally: By using old code in controller it returns 14 db hits. Now it returns only 8. Even there are more tags in a post (but old one returns more).

PyroCMS(Laravel) where clause within the translations not working correctly

I have been struggling with this for quite a while. I use PyroCMS and it has a Posts module that has all the fields in the database and all that and if you want to find a specific post, you can just use a normal WHERE clause and find a post by a date and so on.
But if a field is checked in CMS as translatable, I can't access that field and use it to find a post, because the CMS creates another field in another table that is called posts_translations, and it contains all the fields that are translatable. Usually that is a simple $posts->where("field","value"), but the field doesn't exist if it's translatable.
So I tried to use whereHas, but it doesn't really return anything.
public function meklet(PostRepositoryInterface $posts, $q)
{
$postss = $posts->all()->whereHas('translations', function($query) use($q) {
$query = $query->where(function($query) use($q) {
$query->where('title', 'like', '%'.$q.'%');
});
});
die(var_dump($q));
return $this->view->make("mendo.module.report::reports/search");
}
As you can see I use PostRepositoryInterface maybe I need to use some other class to access what I want? Im very confused, I know its a laravel base, but I can't really wrap my head around this simple problem.
You shouldn't use one letter variables and too much nested functions there:
/**
* Searches for all matches.
*
* #param PostRepositoryInterface $posts The posts
* #param string $search The search
* #return View
*/
public function search(PostRepositoryInterface $posts, $search)
{
/* #var PostCollection $results */
$results = $posts->all()->filter(
function (PostInterface $post) use ($search) {
return str_contains(
strtolower($post->getFieldValue('title')),
strtolower($search)
);
}
);
dd($results);
return $this->view->make('mendo.module.report::reports/search', [
'posts' => $results,
]);
}
And route should be like:
'posts/search/{search}' => [
'as' => 'anomaly.module.posts::posts.search',
'uses' => 'Anomaly\PostsModule\Http\Controller\PostsController#search',
],
To use a DB query directly you need to write translations join self. It is not so difficult.

Symfony2 - how to insert values into two different tables with one form in doctrine?

I'm sure that this question has been asked before but I'm finding it difficult to find a solution that pertains to my exact issue so I am hoping someone can help me out.
Basically, I have two tables, one called "pet" and one called "customer_pet". These two tables are linked so that I can assign pets to specific people (customers). However I find that if I add the customer id into the form (which is not a field in the pet table) that it does not persist anything. I'm having difficulties with the different types of table and column association in doctrine I think.
In my Pet entity, I have the following:
/**
* #var integer
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Oc49Customer", inversedBy="pet", cascade={"persist"})
* #ORM\JoinTable(name="customer_pet",
* joinColumns={
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="pet_id", referencedColumnName="id")
* }
* )
*/
private $customer_id;
But I am unsure how to map it back in my customer_pet entity. The customer_pet table simply contains pet_id and customer_id. The pet_id is generated when a pet is added and the customer_id is passed through the form in a hidden field.
I'm not overly familiar with table associations in doctrine so any help is appreciated. If anyone needs any other code snippets please ask.
Here is my addPet() method:
/**
* Add pet
*
* #param \AppBundle\Entity\Pet $pet
* #return Customer
*/
public function addPet(\AppBundle\Entity\Pet $pet)
{
$this->pet[] = $pet;
return $this;
}
Thank you in advance
Michael
1)Declare all the tables.
2)Create the form.
3)Send to multiple tables.
4)Persist data.
use AppBundle\Entity\site;
use AppBundle\Entity\nba;
1)Declare all the tables.
$site = new site;
$nba = new nba;
2)Create form
$form = $this->createFormBuilder($site)
->add('site_id', IntegerType::class, array('attr' => array('class' => 'form-control', 'style' => 'margin-bottom:15px')))
->add('category', ChoiceType::class, array('attr' => array('class' => 'form-control', 'style' => 'margin-bottom:15px'), 'choices' => $output))
->add('team', ChoiceType::class, array('attr' => array('class' => 'form-control', 'style' => 'margin-bottom:15px'), 'choices' => $nbat))
3)Insert into multiple tables.
$site_id = $form['site_id']->getData();
$category = $form['category']->getData();
$team = $form['team']->getData();
$site->setSiteId($site_id);
$site->setCategory($category);
$nba->setWinner($team);
4)Persist data
$em = $this->getDoctrine()->getManager();
$em->persist($site);
$em->persist($nba);
$em->flush();
You only need to configure the two entities: Pet and Customer. The other table - customer_pet - is a so-called join-table. Doctrine does not need an Entity mapping for this table. You need to configure an association mapping to make Doctrine aware of the relationship between Customers and Pets. In this specific case you can use the Many-To-Many, Bidirectional association, meaning one Customer can have many Pets, and one Pet can have many Customers.
More or less:
in Customer entity
/**
* #ORM\ManyToMany(targetEntity="Pet", inversedBy="customers")
* #ORM\JoinTable(name="customer_pet")
**/
private $pets;
public function __construct() {
$this->pets = new \Doctrine\Common\Collections\ArrayCollection();
}
in Pet entity:
/**
* #ORM\ManyToMany(targetEntity="Customer", mappedBy="pets")
**/
private $customers;
public function __construct() {
$this->customers = new \Doctrine\Common\Collections\ArrayCollection();
}
Then your createPetAndSetCustomerAction could look like this:
public function createPetAndSetCustomerAction($idCustomer)
{
$pet = new Pet();
$pet ->setName('Fluffy');
$customer = $this->getDoctrine()
->getRepository('MyBundle:Customer')
->find($idCustomer);
$pet->getCustomers()->add($customer);
$em = $this->getDoctrine()->getManager();
$em->persist($pet);
$em->flush();
return new Response('Created pet ' . $pet->getName() . ' and set customer ' . $customer->getDescription());
}
We're speaking about some basic concepts here and I really recommend you read the Databases and Doctrine chapter of the Symfony Book two or three times to fully understand these concepts.

Retrieving widget data with MySQL query in WordPress

I've built up multiple dynamic sidebars for front page item manipulation. Each sidebar contains a Text widget, and I want to retrieve each widget's content (according to widget ID) from wp_options.
Basically, the structure is dbName -> wp_options -> option_id #92 contains the following:
a:9:{i:2;a:0:{}i:3;a:3:
{s:5:"title";s:0:"";s:4:"text";s:2:"mainItem";s:6:"filter";b:0;}i:4;a:3:
{s:5:"title";s:0:"";s:4:"text";s:9:"leftThree";s:6:"filter";b:0;}i:5;a:3:
{s:5:"title";s:0:"";s:4:"text";s:10:"rightThree";s:6:"filter";b:0;}i:6;a:3:
{s:5:"title";s:0:"";s:4:"text";s:8:"rightTwo";s:6:"filter";b:0;}i:7;a:3:
{s:5:"title";s:0:"";s:4:"text";s:8:"rightOne";s:6:"filter";b:0;}i:8;a:3:
{s:5:"title";s:0:"";s:4:"text";s:7:"leftOne";s:6:"filter";b:0;}i:9;a:3:
{s:5:"title";s:0:"";s:4:"text";s:7:"leftTwo";s:6:"filter";b:0;}
s:12:"_multiwidget";i:1;}
[Actually all on one line.]
I want to retrieve the following strings:
mainItem
leftOne/leftTwo/leftThree
rightOne/rightTwo/rightThree
What's the syntax for such a query? And how can I add it to the PHP template?
You can pull all of the information about a type of widget from the database like so:
$text_widgets = get_option( 'widget_text' );
There's no need to use mySQL to get this. This will return an array of all the stored widgets of the type 'text'. Then you can loop through this array and do stuff with the internal properties of each like so:
foreach ( $text_widgets as $widget ) {
extract( $widget );
// now you have variables: $mainItem, $leftOne, $leftTwo, etc.
// do something with variables
}
Or, if you already know the ID's of the widgets you want to interact with, you can access the properties like this:
$mainItem = $text_widgets[17]['mainItem'];
Try below code snippet. It return the array of all widgets stored data.
// 1. Initialize variables
$data = '';
$all_stored_widgets = array();
// 2. Get all widgets using - `$GLOBALS['wp_widget_factory']`
$all_widgets = $GLOBALS['wp_widget_factory'];
foreach ($all_widgets->widgets as $w => $value) {
$widget_data = get_option( 'widget_' . $value->id_base );
foreach ($widget_data as $k => $v) {
if( is_numeric( $k ) ) {
$data['id'] = "{$value->id_base}-{$k}";
$data['options'] = $v;
$all_widgets_css[$value->id_base][] = $data;
}
}
}
// 3. Output:
echo '<pre>';
print_r( $all_stored_widgets );
echo '</pre>';
Output: