Yii: ID getting overwritten on load - php

I need help with tracing this web application. I'm very new to Yii, and I'm trying to dissect an existing app to understand it better. I'm trying to create an edit function, which video tutorials lead me to believe has the exact same process as an add function [save()], except you specify the ID to be overwritten (and I very well could be wrong about this).
Near as I can tell, the following files are in play:
views/forum/view.php
views/forum/_commentform.php
views/forum/_comments.php
controllers/ForumController.php
models/Forum.php
models/Comment.php
I can't really change much of the existing, though I can add my own. It starts with view.php, where much of the stuff is displayed. At the bottom of it is this:
<?php
$this->widget('zii.widgets.CListView',
array('dataProvider'=>$dataProvider, 'itemView'=>'_comments', 'summaryText'=>'',));
?>
_comments.php displays all the usual elements of a comment, like say, from Facebook. There's an edit button there that I made, code here:
<?php echo CHtml::link(CHtml::encode('Edit'),array('forum/editcomment','reply'=>$data->id,'topic'=>$data->content_id)); ?>
That edit button gets the ID of the current comment from the database. Near as the application logs can tell me, this does work.
That calls this particular function in ForumController.php:
public function actionEditComment() {
if(isset($_GET['reply'])) {
$comment=Comment::model()->findByAttributes(array('id'=>$_GET['reply']));
$topic=Forum::model()->findByAttributes(array('id'=>$comment->content_id));
$this->renderPartial('_commentform', array('forum'=>$topic, 'model'=>$comment, 'view'=>'view',));
}
}
Next is the _commentform.php. Nothing much, just a textbox, though it does check if an ID is present; if it is, it is editing an existing comment, otherwise, it is creating a new one. A submit button also changes from Reply to Update, depending on the value of isNewRecord.
EDIT: There's also a CActiveForm, in case that of any help. Might have something to do with routing?
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'comment-form',
'action'=>Yii::app()->createUrl('forum/view/id/'.$forum->id),
'enableAjaxValidation'=>false,
)); ?>
<?php
if ($view == 'view') {
echo CHtml::submitButton($model->isNewRecord ? 'Reply' : 'Update', array('id'=>'comment'.$model->id));
}?>
Again, confirmed via application logs, the ID of the comment is being passed through, albeit as id => comment<commentID>. Then this is where things get hazy. I assume the flow goes back to ForumController.php, where, per my logging, the ID is lost.
Here's the parts of the ForumController.php that I deem responsible:
public function actionView() {
$post=$this->loadModel();
$comment=$this->newComment($post);
$viewcount=$post->view_count+1;
$post->view_count=$viewcount;
$post->save();
$this->render('view',array('model'=>$post, 'dataProvider'=>$dataProvider,));
}
private $_model;
public function loadModel() {
if($this->_model===null) {
if(isset($_GET['id'])) {
$this->_model=Forum::model()->findByPk($_GET['id'], $condition);
}
if($this->_model===null)
throw new CHttpException(404,'The requested page does not exist.');
}
return $this->_model;
}
protected function newComment($post) {
$comment=new Comment;
if(isset($_POST['Comment'])) {
$comment->attributes=$_POST['Comment'];
$post->addComment($comment);
}
return $comment;
}
Interestingly, if I write out $comment from newComment() out to the log, it does print out the edited comment (i.e., it prints out "john cena" if I edited the existing comment "who is champ?"), but $comment->id yields a null, which I assume is why instead of updating, the edited comment is saved as a new one.
As for the models, Forum.php and Comment.php strangely point to the same database table, because for some reason they decided to put Forums and Comments into one table. Forum.php also contains the actual addComment function (a placement I find weird), though by the time the flow gets there, the Comment ID is of course null, though the edited comment itself is there.
Where did I go wrong? Did I miss anything?
EDIT: Here's the attributes and rules for the Comment model:
public function attributeLabels() {
return array(
'id' => 'ID',
'node_type' => 'Node Type',
'party_id' => 'Party',
'category' => 'Category',
'title' => 'Title',
'content' => 'Content',
'date_created' => 'Create Time',
'date_modified' => 'Update Time',
'status' => 'Status',);
}
public function rules()
{
/* combine parent and own rules */
$parentRules = parent::rules();
$myRules = array(
array('node_type_id', 'default', 'value'=>'7'), /* set type to Person */
array('node_type_id', 'in', 'range'=>array('7')), /* allow only Person type */
array('party_id, date_created, date_modified, status', 'numerical', 'integerOnly'=>true),
array('category, title, content', 'safe'),
);
/* you want to apply parent rules last, delete them here if necessary */
return array_merge($myRules);
}

Could you post Comment class defenition here?
I think you don't have rule for "id" in Comment::rules(),
When rule for attribute is not defined attribute will be unsafe and you can't set it value by $comment->attributes, or you can change you code to something like:
if(isset($_POST['Comment']) && isset($_POST['Comment']['id'])) {
$comment = Comment::model()->findByPk($_POST['Comment']['id']);
$comment->attributes=$_POST['Comment'];
$post->addComment($comment);
}

Related

CakePHP: Check for line item in iframe before submitting parent form

The app I'm working on (an order form) allows the user to enter multiple sub-records within an iframe. These sub-records are joined to the main record via a foreign key.
main_records line_items
----------- ----------
id int(11) PK etc. id int(11) PK etc.
main_record_id (FK)
I need the app to check whether at least one line item exists within this iframe before form submission. I would like to take advantage of the $validate functionality within the model, but I'm unsure how to proceed. Here's what I've tried in the Main model:
App::uses('AppModel', 'Model', 'LineItem');
public $hasMany = array(
'LineItem' => array(
'className' => 'LineItem',
'foreignKey' => 'main_record_id',
'dependent' => false
)
);
public $validate = array(
'main_record_id' = array(
'allowEmpty' => false,
'rule' => 'checkForLineItem',
'message' => 'You must enter at least one line item!'
)
);
//Check to make sure there is at least one line item before saving changes/submitting for approval
function checkForLineItem($id) {
$lines = $this->LineItem->find('all', array(
'fields' => array('LineItem.main_record_id'),
'conditions' => array('LineItem.main_record_id'=>$id, 'LineItem.deleted_record'=>0))
);
if(!empty($lines)) {
return true;
} else {
return false;
}
}
I also track whether the line item has been deleted. If it has, then it is not added to $lines.
I know I can accomplish this in the Controller, but as far as I know, that would require the form to post, and the user would lose any changes upon postback (I haven't yet implemented jQuery on this form). Am I on the right track with how to do this? What changes should I make to get this to work?
Your code looks about right, but validation indeed happens in form submit. If you want to check it prior to that you have to do in JavaScript (jquery). E.g. create a controller action that return if there are existing line items for given main record id and call it via AJAX.

Update two models using yii x-editable

So, I'm using this extension: x-editable for yii.
And I'm currently trying to update two models in update() function.
I have two models:
Realisasi.php
RealisasiDeadline.php.
So when a cell is updated on table Realisasi.php (one value in column t1701 in this case), I want the function to update the corresponding value in column t1701 of table RealisasiDeadline, using column no as the foreign key.
Since I haven't found any example on Google, I made it up myself:
public function actionSimpanEdit($kode) {
Yii::import('editable.EditableSaver');
$es = new EditableSaver($_GET['model']); // 'modelName' is classname of model to be updated
$es->update();
$es2 = RealisasiDeadline::model()->findByPk($kode);//this is where I'm stuck
$es2['t1701'] = '1991-11-19';//this too
$es->update();//and this
}
This is the view.php:
array(
'name' => 't1701',
'value' => 'CHtml::value($data,"hubtarget.t1701")=== "0"?"Target Nol":$data->t1701',
'header' => 'Bkl Selatan',
'class' => 'editable.EditableColumn',
'editable' => array(
'url' => $this->createUrl('simpanEdit', array('model' => 'Realisasi', 'kode'=>'$data->no')),
)
),
What have I missed? Is it possible at all to do? If not, is there another solution?
UPDATE
It's not showing any error. But the value in table RealisasiDeadline doesn't change, only the one in Realisasi does.
Added some comments to original function so you can improve upon it. Biggest issue with this code is that looking at it I have no idea what it does.
public function actionSimpanEdit($kode) {
Yii::import('editable.EditableSaver'); // Should be at the top of the file
// For the love of god use descriptive variable names
$es = new EditableSaver($_GET['model']); // Would prefer to have model as actions argument
$es->update();
$es2 = RealisasiDeadline::model()->findByPk($kode); // no idea what this model is responsible for
$es2['t1701'] = '1991-11-19'; // no idea what attribute t1701 is, use descriptive names
$es->update();
}
I have refactored it a bit. Still have no idea what it does ;/
public function actionSimpanEdit($id, $model) {
$editableSaver = new EditableSaver($model);
$editableSaver->update();
$deadline = RealisasiDeadline::model()->findByPk($id);
if($deadline instanceof RealisasiDeadline) {
$deadline->t1701 = '1991-11-19';
if(!$deadline->update()) {
// something went wrong
}
} else {
// not found
}
}
Going back to your problem. It is probably caused by RealisasiDeadline model being not found or some behavior or event preventing it from update.

Displaying search results on a separate page

I have a SilverStripe site with some code to display a search form. The for allows you to search for something based on 3 things. Problem is, I'm not sure how to get the results to display correctly on a separate page.
My code:
class InstitutionSearchPage extends Page {
}
class InstitutionSearchPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'Search'
);
public function Form() {
$fields = new FieldList(array(
DropdownField::create('DegreeType', 'Degree', QualificationType::get()->filter('ParentID', 0)->map()),
DropdownField::create('Course', 'Course', Qualification::get()->map()),
DropdownField::create('City', 'City', City::get()->map()),
));
$actions = new FieldList(array(
FormAction::create('Search')->setTitle('Find a College')
));
$validator = ZenValidator::create();
$validator->addRequiredFields(array(
'DegreeType' => 'Please select a Degree',
'Course' => 'Please select a course',
'City' => 'Please select a city',
));
$form = new Form($this, 'Search', $fields, $actions, $validator);
$form->setLegend('Criteria')->addExtraClass('form-horizontal')->setAttribute('data-toggle', 'validator');
// Load the form with previously sent data
$form->loadDataFrom($this->request->postVars());
return $form;
}
public function Search() {
return array('Content' => print_r($this->getRequest()->postVars(), true));
}
}
It seems to be displaying results on the same page but gives me a bunch of weird data. For example, I got this when I tested the form: Array ( [DegreeType] => 53 [Course] => 1 [City] => 1 [SecurityID] => 02718d0283e27eeb539eff19616e0b23eadd6b94 [action_Search] => Find a College )
The result is supposed to be an organized list of colleges (along with other data).
I guess that what you are seeing is expected behavior, as the form will post to the Search function, and that one is just returning a print_r of an array with the post vars which will be picked up by the template.
Otherwise, there are a lot of things not corresponding with the Silverstripe default way to handle forms. Please take a look here and change your form accordingly: https://docs.silverstripe.org/en/3.4/tutorials/forms/
For example, give the form the same name as your function (or in your case, change the function name to the form name). Then implement the action function.

Symfony, forms and many to one

We're running into a small code-design smell with symfony and our forms. It is not a problem per se, but makes me wonder if we could attain our goals any other way.
For the sake of simplicity, let me briefly explain a setup: let "Product" be an entity that represents a product in a database, meant to be sold in an online store. Since the online store is designed to have several languages in it, every single bit of information that could be related to a language is in the entity "Product_descriptions" that is related in a manyToOne fashion to the "Product". Finally we have designed a "Language" entity, representing every single language the user can see the store in.
As you can imagine, the code is pretty standard stuff:
class Language
{
private $language_id;
private $language_name;
private $language_code;
//Some other stuff.
};
class Product
{
private $product_id;
private $product_reference;
private $product_weight;
private $product_descriptions; //As an arrayCollection of "Product_description" objects.
//Some other stuff.
};
class Product_description
{
private $product_description_id;
private $product_name;
private $product_long_description;
private $product_short_description;
private $product; //A reference to the Product itself.
private $language; //A reference to the language this is meant to be seen in.
};
Okay, now for the problem itself. The setup, as expected, works wonderfully. It is in the backend where the smell resides.
To create new products we have designed a symfony form Type. In the same form we would like to be able to set all the product information as well as the information for every possible language. The smell comes in when we need to feed all possible "Language"s to the form type, check if a "Product_description" exists for a "Language" and "Product", show the empty text field (in case it does not exist) or the filled field... Our solution requests that a repository for all languages is injected into the form . Let me show you how it goes (please, take into consideration that this is not the real code... something may be missing):
class ProductType extends AbstractType
{
private $language_repo;
public function __construct($r)
{
$this->language_repo=$r;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('product_name', 'text')
->add('product_code', 'text');
$product=$builder->getData();
//We retrieve all languages here, to check if an entry for that
//language exists and show its data.
$languages=$this->language_repo->findAll();
foreach($languages as $key => &$lan)
{
//Here we look for existing data... This will return null if there's none.
$product_description=$product->get_description_for_language($lan);
$default_name=$product_description ? $product_description->getProductName() : '';
$default_long=$product_description ? $product_description->getProductLongDescription() : '';
$default_short=$product_description ? $product_description->getProductShortDescription() : '';
//Here we manually create the name_#language_id# form data... That we will retrieve later.
$builder->add('name_'.$lan->getLanguageId(), 'text', array(
'label' => 'Name for '.$lan->getName(),
'mapped' => false,
'data' => $default_name))
->add('long_'.$lan->getLanguageId(), 'text', array(
'label' => 'Name for '.$lan->getName(),
'mapped' => false,
'data' => $default_long))
->add('short_'.$lan->getLanguageId(), 'text', array(
'label' => 'Name for '.$lan->getName(),
'mapped' => false,
'data' => $default_short));
}
$builder->add('save', 'submit', array('label' => 'Save data'));
}
//And some other stuff here.
}
As you can see, we are manually setting some data keys that we need to retrieve later in the controller. The setup works, of course. Any new language will yield an empty form field. Any existing language shows the related information.
Now for the controller, this gets messier even... When we're submitting the form we go like this:
private function process_form_data(Form &$f, Product &$item, Request &$request)
{
//Find all languages...
$em=$this->getDoctrine()->getManager();
$languages=$em->getRepository("MyBundle:Language")->findAll();
//Get submitted data for that language..
foreach($languages as $key => &$lan)
{
$name_language=$f->get('name_'.$lan->getLanguageId())->getData();
$long_language=$f->get('long_'.$lan->getLanguageId())->getData();
$short_language=$f->get('short_'.$lan->getLanguageId())->getData();
//Check if the language entry exists... Create it, if it doesn't. Feed the data.
$product_description=$product->get_description_for_language($lan);
if(!$product_description)
{
$product_description=new Product_description();
$product_description->setLanguage($lan);
$product_description->setProduct($product);
}
$product_description->setName($name_language);
$product_description->setLongDescription($long_language);
$product_description->setShortDescription($short_language);
$em->persist($product_description);
}
//Do the product stuff, persist, flush, generate a redirect...Not shown.
}
It works, but seems to me that is not the "symfony" way of doing things. How would you do this?. Have you found a more elegant approach?.
Thanks a lot.
I think you should revisit the way you translate the entities...
An existing way is to use the DoctrineExtensionBundle, translatable to be precise...
You'll find more info here :
https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/translatable.md
Here is an extract to see how it can work :
<?php
// first load the article
$article = $em->find('Entity\Article', 1 /*article id*/);
$article->setTitle('my title in de');
$article->setContent('my content in de');
$article->setTranslatableLocale('de_de'); // change locale
$em->persist($article);
$em->flush();
( now the article has a german translation )

Yiiframework First time login

I'm currently busy with a project that needs users to go to a specific page to create a profile when they log in for the first time (and haven't created one yet). Honestly, I don't know where to start. I would like to do it in a good way.
So in short:
User signs up -> logs in -> needs to fill in form before anything else is allowed -> continue to rest of application
Question: What is a neat way to do this? A solution that isn't going to give me problems in the future development of the application.
I suggest you to use filters. In every controller where the completed profile is neeeded add this code:
public function filters() {
return array(
'completedProfile + method1, method2, method3', // Replace your actions here
);
}
In your base controller (if you don't use base controller, in any controllers) you need to create the filter named completedProfile with the simular code:
public function filterCompletedProfile($filterChain) {
$criteria = new CDBCriteria(array(
'condition' => 'id = :id AND firstname IS NOT NULL AND lastname IS NOT NULL',
'params' => array(':id' => Yii::app()->user->getId())
));
$count = User::model()->count($criteria);
if ($count == 1) {
$filterChain->run();
} else {
$this->redirect(array('user/profile'));
}
}
Possibly add a field to the user profile database table which denotes if they have filled out their profile information. Something like profile_complete. Then you can do a test on pages to see if profile_complete is true and display the page if so, and display the profile page if not.

Categories