Is there a way to generate views from Zend_Form ? (read-only) - zend-framework

I was wondering if is there an easy way to generate views from form objects when dealing with CRUDs.
I mean, when we have these options: VIEW | EDIT | DELETE
I want my VIEW option like EDIT option, but without form elements, just the values.
This will minimize so much the time spent to create these views.
Someone knowks something like that?

In my last project I had this dilemma too. My solution may not be the most elegant, but it did the job. Mind you, I use a Form viewscript decorator in stead of a full decorator generated elements. But you could adjust this example to use decorators I presume. What I'm showing is a very basic example, to give you a general idea. Here's what I did:
class Cms_Form_Page extends Zend_Form
{
const FOR_CREATE = 'forCreate';
const FOR_READ = 'forRead';
const FOR_UPDATE = 'forUpdate';
const FOR_DELETE = 'forDelete';
protected $_name = 'page';
private $_for;
private $_viewScripts = array(
self::FOR_CREATE => 'page-manager/partials/form-page-create.phtml',
self::FOR_READ => 'page-manager/partials/form-page-read.phtml',
self::FOR_UPDATE => 'page-manager/partials/form-page-update.phtml',
self::FOR_DELETE => 'page-manager/partials/form-page-delete.phtml'
);
public function __construct( $for = self::FOR_CREATE, $options = null )
{
$this->_for = $for;
parent::__construct( $options );
}
public function init()
{
$this->setName( $this->_name )
->setAttribs( array( 'accept-charset' => 'utf-8' ) )
->setDecorators( array(
'PrepareElements',
array( 'ViewScript', array( 'viewScript' => $this->_viewScripts[ $this->_for ] ) ),
'Form'
) );
$elements = array();
swith( $this->_for )
{
case self::FOR_CREATE:
$title = new Zend_Form_Element_Text( 'title' );
$elements[] = $title;
break
case self::FOR_READ:
$id = new Zend_Form_Element_Hidden( 'id' );
$elements[] = $id;
break;
case self::FOR_UPDATE:
$id = new Zend_Form_Element_Hidden( 'id' );
$elements[] = $id;
$title = new Zend_Form_Element_Text( 'title' );
$elements[] = $title;
break;
case self::FOR_DELETE:
$id = new Zend_Form_Element_Hidden( 'id' );
$elements[] = $id;
break;
default:
throw new Exception( 'invalid Form type' );
}
$submit = new Zend_Form_Element_Button( 'submit' );
$elements[] = $submit;
$this->addElements( $elements );
}
}
So, basically, I pass one of the class constants to it's constructor. And based on that value, I determine what elements are needed for the form, and how the elements should be rendered.
For instance, for create you could have a select dropdown form field where you would choose a Locale, where for delete this would be a hidden field (not shown in my example btw).
Hope this has given you some ideas.
PS:
In one of the selected viewscripts you could then simply show the value of an element (along with rendering the hidden element too), with something like:
<?
$form = $this->element;
?>
... some html
// let's presume id and locale are hidden form fields for current form type
// (Cms_Form_Page::FOR_UPDATE for instance)
<?= $form->id->renderViewHelper(); ?>
<?= $form->locale->renderViewHelper(); ?>
// and here we simply output the current locale value
// of course, you should have populated the values in the form somewhere first
<dt>Current locale:</dt>
<dd><?= $form->locale->getValue(); ?></dd>
...etc
So, I think you'ld be best of with using viewscript decorators for the form, or you could roll your own form element decorator that renders the hidden field (if neccesary) and simply shows it's value in some html tag.

Hector from Nabble, show me this, which seems to be the best way:
class Default_View_Helper_FormView extends Zend_View_Helper_Abstract
{
public function formView(Zend_Form $form)
{
$html = "<dl>";
foreach ($form->getElements as $element) {
$html .= "<dt>{$element->getLabel()}</dt>";
$html .= "<dd>{$element->getValue()}</dd>";
}
$html .= "</dl>";
return $html;
}
}

I'm not sure if I understand, but I think that for the view option you can just fetch the data from your model. No need to access them through Zend_Form.
But if you want the make your form read-only, you can add readonly (setAttrib('readonly', 'readonly')) attribute to your elements.

Made a couple minor additions to the accepted answer to cover common elements that may be special cases:
class Default_View_Helper_FormView extends Zend_View_Helper_Abstract
{
public function formView( Zend_Form $form )
{
$html = '<dl>';
foreach ( $form->getElements() as $element ) {
if( $element instanceof Zend_Form_Element_Submit ) {
continue;
}
$html .= '<dt>' . $element->getLabel() . '</dt>';
$value = $element->getValue();
if( $element instanceof Zend_Form_Element_Checkbox ) {
$value = ($value) ? 'Yes' : 'No';
}
else if( $element instanceof Zend_Form_Element_Select ) {
$value = $element->getMultiOption($value);
}
$html .= '<dd>' . $value . '</dd>';
}
$html .= '</dl>';
return $html;
}
}

The only problem with the accepted answer is that you're creating all the elements and then ignoring them.
Using the control logic from fireeyedboy's answer, you could instead switch all the elements to Zend_View_Helper_FormNote which does the same thing.
Just depends on if the optimization matters.

Related

How to merge subform array into single array in Zend Framework1.11

I am using Zend Framework1.11. In my Zend Form I have two zend sub form, I have added these two sub form using addSubForm function.
Now when I call this zend form in controller then isValid function is not working. I have called it as follow..
public function registeredAction(){
$form = new Application_Form_RegisteredForm();
$form->setAction('registered');
$formData = $this->_request->getPost();
if($form->isValid($formData)){
// save into database using model class.
} else {
$form->populate($formData);
}
$this->view->form = $form;
}
In following code isValid is not working, while I print_r the $fotmData requested array, it print array like:-
Array(
[personal] => Array
(
[firstname] => 'Example',
[lastname] => 'Solution'
)
[MAX_FILE_SIZE] => 8388608
[address] => Array
(
[country] => 'IND',
[state] => 'RAJ'
)
);
I have also used the setData() function but it is not working, it's give exceptional error "Message: Method setData does not exist", I have used php array_merge function but return array is not working with isValid().
Can anyone help me to solve this problem. so I can easily store form data into database.
Thanks!
Take a look at array_merge
http://php.net/manual/de/function.array-merge.php
$newFormData=array_merge($formData["personal"],$formData["address"]);
My solution is to create new base form with new method getSubFormsValues(), i.e.:
class My_Form extends Zend_Form
{
public function getSubFormsValues()
{
$values = array();
foreach ($this->getSubForms() as $form) {
$name = $form->getName();
$value = $form->getValues();
$values = array_merge($value[$name], $values);
}
return $values;
}
}
When you can call $my_form_obj->getSubFormValues() in your code.

silverstripe dataobject searchable

I´m trying to have certain DataObjects (News) displayed in the default SearchResult Page. So the result should display normal Pages and News.
Is there an easy way to accomplish that in Silverstripe 3?
Or is it recommended to code it completely custom - I mean a custom controller/action which handles the search request and creates a result list, which I display then in a custom template?
I found this, but obviously search is disabled right now:
https://github.com/arambalakjian/DataObjects-as-Pages
Thx and regards,
Florian
I usually but together a custom search function after enabling FulltextSearchable. So in _config.php I would have
FulltextSearchable::enable();
Object::add_extension('NewsStory', "FulltextSearchable('Name,Content')");
replacing Name and Content with whatever DBField you want to be searchable. And each searchable DataObject have this in their class to enable search indexes (pretty sure this needs to be added and run dev/build before enabling the extension, and only works on MySQL DB).
static $create_table_options = array(
'MySQLDatabase' => 'ENGINE=MyISAM'
);
then in my PageController I have my custom searchForm and results functions.
Here is the search function that returns the search form, called with $search in the template:
public function search()
{
if($this->request && $this->request->requestVar('Search')) {
$searchText = $this->request->requestVar('Search');
}else{
$searchText = 'Search';
}
$f = new TextField('Search', false, $searchText);
$fields = new FieldList(
$f
);
$actions = new FieldList(
new FormAction('results', 'Go')
);
$form = new Form(
$this,
'search',
$fields,
$actions
);
//$form->disableSecurityToken();
$form->setFormMethod('GET');
$form->setTemplate('SearchForm');
return $form;
}
and here the custom results function to handle the queries...
function results($data, $form, $request)
{
$keyword = trim($request->requestVar('Search'));
$keyword = Convert::raw2sql($keyword);
$keywordHTML = htmlentities($keyword, ENT_NOQUOTES, 'UTF-8');
$pages = new ArrayList();
$news = new ArrayList();
$mode = ' IN BOOLEAN MODE';
//$mode = ' WITH QUERY EXPANSION';
//$mode = '';
$siteTreeClasses = array('Page');
$siteTreeMatch = "MATCH( Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords ) AGAINST ('$keyword'$mode)
+ MATCH( Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords ) AGAINST ('$keywordHTML'$mode)";
$newsItemMatch = "MATCH( Name, Content ) AGAINST ('$keyword'$mode)
+ MATCH( Name, Content ) AGAINST ('$keywordHTML'$mode)";
//Standard pages
foreach ( $siteTreeClasses as $c )
{
$query = DataList::create($c)
->where($siteTreeMatch);
$query = $query->dataQuery()->query();
$query->addSelect(array('Relevance' => $siteTreeMatch));
$records = DB::query($query->sql());
$objects = array();
foreach( $records as $record )
{
if ( in_array($record['ClassName'], $siteTreeClasses) )
$objects[] = new $record['ClassName']($record);
}
$pages->merge($objects);
}
//news
$query = DataList::create('NewsStory')->where($newsItemMatch);
$query = $query->dataQuery()->query();
$query->addSelect(array('Relevance' => $newsItemMatch));
$records = DB::query($query->sql());
$objects = array();
foreach( $records as $record ) $objects[] = new $record['ClassName']($record);
$news->merge($objects);
//sorting results
$pages->sort(array(
'Relevance' => 'DESC',
'Title' => 'ASC'
));
$news->sort(array(
'Relevance' => 'DESC',
'Date' => 'DESC'
));
//output
$data = array(
'Pages' => $pages,
'News' => $news,
'Query' => $keyword
);
return $this->customise($data)->renderWith(array('Search','Page'));
}
I add all the Page classes I want to be searched and that extend SiteTree in the $siteTreeClasses array, and the News parts can be pretty much copied for any other DataObjectI need searchable.
I am not saying this is the best solution and this can definitely be improved on, but it works for me and this might be a good stating point.
I have adapted #colymba's solution into a silverstripe module: https://github.com/burnbright/silverstripe-pagesearch
It allows setting the pagetype in the url.
You'll need to substantially overwrite SearchForm->getResults().
It uses Database->searchEngine(), but those are tailored towards SiteTree and Page classes.
The "proper" solution is to feed the data into a search engine like Solr or Sphinx.
We have the SS3-compatible "fulltextsearch" module for this purpose:
https://github.com/silverstripe-labs/silverstripe-fulltextsearch
It's going to take some upfront setup, and is only feasible if you can either host Solr yourself, or are prepared to pay for a SaaS provider. Once you've got it running though, the possibilities are endless, its a great tool!

Yii CJuiAutoComplete for Multiple values

I am a Yii Beginner and I am currently working on a Tagging system where I have 3 tables:
Issue (id,content,create_d,...etc)
Tag (id,tag)
Issue_tag_map (id,tag_id_fk,issue_id_fk)
In my /Views/Issue/_form I have added a MultiComplete Extension to retrieve multiple tag ids and labels,
I have used an afterSave function in order to directly store the Issue_id and the autocompleted Tag_ids in the Issue_tag_map table, where it is a HAS_MANY relation.
Unfortunately Nothing is being returned.
I wondered if there might be a way to store the autocompleted Tag_ids in a temporary attribute and then pass it to the model's afterSave() function.
I have been searching for a while, and this has been driving me crazy because I feel I have missed a very simple step!
Any Help or advices of any kind are deeply appreciated!
MultiComplete in Views/Issue/_form:
<?php
echo $form->labelEx($model, 'Tag');
$this->widget('application.extension.MultiComplete', array(
'model' => $model,
'attribute' => '', //Was thinking of creating a temporary here
'name' => 'tag_autocomplete',
'splitter' => ',',
'sourceUrl' => $this->createUrl('Issue/tagAutoComplete'),
// Controller/Action path for action we created in step 4.
// additional javascript options for the autocomplete plugin
'options' => array(
'minLength' => '2',
),
'htmlOptions' => array(
'style' => 'height:20px;',
),
));
echo $form->error($model, 'issue_comment_id_fk');
?>
AfterSave in /model/Issue:
protected function afterSave() {
parent::afterSave();
$issue_id = Yii::app()->db->getLastInsertID();
$tag; //here I would explode the attribute retrieved by the view form
// an SQL with two placeholders ":issue_id" and ":tag_id"
if (is_array($tag))
foreach ($tag as $tag_id) {
$sql = "INSERT INTO issue_tag_map (issue_id_fk, tag_id_fk)VALUES(:issue_id,:tag_id)";
$command = Yii::app()->db->createCommand($sql);
// replace the placeholder ":issue_id" with the actual issue value
$command->bindValue(":issue_id", $issue_id, PDO::PARAM_STR);
// replace the placeholder ":tag_id" with the actual tag_id value
$command->bindValue(":tag_id", $tag_id, PDO::PARAM_STR);
$command->execute();
}
}
And this is the Auto Complete sourceUrl in the Issue model for populating the tags:
public static function tagAutoComplete($name = '') {
$sql = 'SELECT id ,tag AS label FROM tag WHERE tag LIKE :tag';
$name = $name . '%';
return Yii::app()->db->createCommand($sql)->queryAll(true, array(':tag' => $name));
actionTagAutoComplete in /controllers/IssueController:
// This function will echo a JSON object
// of this format:
// [{id:id, name: 'name'}]
function actionTagAutocomplete() {
$term = trim($_GET['term']);
if ($term != '') {
$tags = issue::tagAutoComplete($term);
echo CJSON::encode($tags);
Yii::app()->end();
}
}
EDIT
Widget in form:
<div class="row" id="checks" >
<?php
echo $form->labelEx($model, 'company',array('title'=>'File Company Distrubution; Companies can be edited by Admins'));
?>
<?php
$this->widget('application.extension.MultiComplete', array(
'model' => $model,
'attribute' => 'company',
'splitter' => ',',
'name' => 'company_autocomplete',
'sourceUrl' => $this->createUrl('becomEn/CompanyAutocomplete'),
'options' => array(
'minLength' => '1',
),
'htmlOptions' => array(
'style' => 'height:20px;',
'size' => '45',
),
));
echo $form->error($model, 'company');
?>
</div>
Update function:
$model = $this->loadModel($id);
.....
if (isset($_POST['News'])) {
$model->attributes = $_POST['News'];
$model->companies = $this->getRecordsFromAutocompleteString($_POST['News']
['company']);
......
......
getRecordsFromAutocompleteString():
public static cordsFromAutocompleteString($string) {
$string = trim($string);
$stringArray = explode(", ", $string);
$stringArray[count($stringArray) - 1] = str_replace(",", "", $stringArray[count($stringArray) - 1]);
$criteria = new CDbCriteria();
$criteria->select = 'id';
$criteria->condition = 'company =:company';
$companies = array();
foreach ($stringArray as $company) {
$criteria->params = array(':company' => $company);
$companies[] = Company::model()->find($criteria);
}
return $companies;
}
UPDATE
since the "value" porperty is not implemented properly in this extension I referred to extending this function to the model:
public function afterFind() {
//tag is the attribute used in form
$this->tag = $this->getAllTagNames();
parent::afterFind();
}
You should have a relation between Issue and Tags defined in both Issue and Tag models ( should be a many_many relation).
So in IssueController when you send the data to create or update the model Issue, you'll get the related tags (in my case I get a string like 'bug, problem, ...').
Then you need to parse this string in your controller, get the corresponding models and assigned them to the related tags.
Here's a generic example:
//In the controller's method where you add/update the record
$issue->tags = getRecordsFromAutocompleteString($_POST['autocompleteAttribute'], 'Tag', 'tag');
Here the method I'm calling:
//parse your string ang fetch the related models
public static function getRecordsFromAutocompleteString($string, $model, $field)
{
$string = trim($string);
$stringArray = explode(", ", $string);
$stringArray[count($stringArray) - 1] = str_replace(",", "", $stringArray[count($stringArray) - 1]);
return CActiveRecord::model($model)->findAllByAttributes(array($field => $stringArray));
}
So now your $issue->tags is an array containing all the related Tags object.
In your afterSave method you'll be able to do:
protected function afterSave() {
parent::afterSave();
//$issue_id = Yii::app()->db->getLastInsertID(); Don't need it, yii is already doing it
foreach ($this->tags as $tag) {
$sql = "INSERT INTO issue_tag_map (issue_id_fk, tag_id_fk)VALUES(:issue_id,:tag_id)";
$command = Yii::app()->db->createCommand($sql);
$command->bindValue(":issue_id", $this->id, PDO::PARAM_INT);
$command->bindValue(":tag_id", $tag->id, PDO::PARAM_INT);
$command->execute();
}
}
Now the above code is a basic solution. I encourage you to use activerecord-relation-behavior's extension to save the related model.
Using this extension you won't have to define anything in the afterSave method, you'll simply have to do:
$issue->tags = getRecordsFromAutocompleteString($_POST['autocompleteAttribute'], 'Tag', 'tag');
$issue->save(); // all the related models are saved by the extension, no afterSave defined!
Then you can optimize the script by fetching the id along with the tag in your autocomplete and store the selected id's in a Json array. This way you won't have to perform the sql query getRecordsFromAutocompleteString to obtain the ids. With the extension mentioned above you'll be able to do:
$issue->tags = CJSON::Decode($_POST['idTags']);//will obtain array(1, 13, ...)
$issue->save(); // all the related models are saved by the extension, the extension is handling both models and array for the relation!
Edit:
If you want to fill the autocomplete field you could define the following function:
public static function appendModelstoString($models, $fieldName)
{
$list = array();
foreach($models as $model)
{
$list[] = $model->$fieldName;
}
return implode(', ', $list);
}
You give the name of the field (in your case tag) and the list of related models and it will generate the appropriate string. Then you pass the string to the view and put it as the default value of your autocomplete field.
Answer to your edit:
In your controller you say that the companies of this model are the one that you added from the Autocomplete form:
$model->companies = $this->getRecordsFromAutocompleteString($_POST['News']
['company']);
So if the related company is not in the form it won't be saved as a related model.
You have 2 solutions:
Each time you put the already existing related model in you autocomplete field in the form before displaying it so they will be saved again as a related model and it won't disapear from the related models
$this->widget('application.extensions.multicomplete.MultiComplete', array(
'name' => 'people',
'value' => (isset($people))?$people:'',
'sourceUrl' => array('searchAutocompletePeople'),
));
In your controller before calling the getRecordsFromAutocompleteString you add the already existing models of the model.
$model->companies = array_merge(
$model->companies,
$this->getRecordsFromAutocompleteString($_POST['News']['company'])
);

Extending Zend_View_Helper_FormElement

I have created this file at My/View/Helper/FormElement.php
<?php
abstract class My_View_Helper_FormElement extends Zend_View_Helper_FormElement
{
protected function _getInfo($name, $value = null, $attribs = null,
$options = null, $listsep = null
) {
$info = parent::_getInfo($name, $value, $attribs, $options, $listsep);
$info['id'] = 'My new ID';
return $info;
}
}
How can i get the normal form elements to use this instead?
Why i want this?
Say that i use the same form multiple times on a page, the 'id='-tag of the formelements will apear multiple times, this is not w3c-valid. So initially i want to prefix the id with the id of the form.
Any better ideas or ways to do this is much apreciated.
Update: Just realized it's the same problem with the decorators :( Don't think this is the right path i've taken.
Create new form class extending Zend_Form and in the init() method use variable $ns to add prefix/suffix to all elements. You can set $ns variable through form constructor.
class Form_Test extends Zend_Form
{
protected $ns;
public function init()
{
$this->setAttrib('id', $this->ns . 'testForm');
$name = new Zend_Form_Element_Text('name');
$name->setAttrib('id', $this->ns . 'name');
$name->setLabel('Name: *')->setRequired(true);
$submit = new Zend_Form_Element_Submit('submit');
$submit->setAttrib('id', $this->ns . 'submitbutton');
$submit->setLabel('Add')->setIgnore(true);
$this->addElements(array($name, $submit));
}
public function setNs($data)
{
$this->ns = $data;
}
}
In the controller or wherever you are calling this forms specify each form instance:
$form1 = new Form_Test(array('ns' => 'prefix1'));
$this->view->form1 = $form1;
$form2 = new Form_Test(array('ns' => 'prefix2'));
$this->view->form2 = $form2;
// Validation if calling from the controller
if ($form1->isValid()) ...
Using multiple instances of same forms on a page can be validated if used as subform.
SubForms prefix all id's with the name/identifier of the subform.

Drupal - Include more than one user_profile_form on a page

Edit:
I think it is because the action is the same or something. I tried to modify the action using this:
function mytheme_user_profile_form($form) {
global $user;
$uid = $user->uid;
//print '<pre>'; print_r($form); print '</pre>';
$category = $form['_category']['#value'];
switch($category) {
case 'account':
$form['#action'] = '/user/'.$uid.'/edit?destination=user/'.$uid;
break;
case 'education':
$form['#action'] = '/user/'.$uid.'/edit/education?destination=user/'.$uid;
break;
case 'experience':
$form['#action'] = '/user/'.$uid.'/edit/experience?destination=user/'.$uid;
break;
case 'publications':
$form['#action'] = '/user/'.$uid.'/edit/publications?destination=user/'.$uid;
break;
case 'conflicts':
$form['#action'] = '/user/'.$uid.'/edit/conflicts?destination=user/'.$uid;
break;
}
//print '<pre>'; print_r($form); print '</pre>';
//print $form['#action'];
$output .= drupal_render($form);
return $output;
}
But, the form action, when the form is actually rendered is unchanged. They're all /user/%uid
Can I modify the form action?
I am including several different "categories" of the user profile form on one page, and the code will correctly output the forms I'm specifying. Each form is in a separate collapsible div.
My problem is twofold.
(1) The existing values for the fields aren't pre-populated and
(2) Clicking on "Save" for one section will result in a warning: Email field is required, regardless of which form you're actually saving
I am pretty sure that for problem #2, it is because the name of the button is the same in all cases, as is the form id.
print '<h3>– Account Settings</h3>';
print '<div class="expand">';
print(drupal_get_form('user_profile_form', $user, 'account'));
print '</div>';
print '<h3>– My Info</h3>';
print '<div class="expand">';
print(drupal_get_form('user_profile_form', $user, 'Personal'));
print '</div>';
print '<h3>– Experience</h3>';
print '<div class="expand">';
print(drupal_get_form('user_profile_form', $user, 'experience'));
print '</div>';
print '<h3>– Education</h3>';
print '<div class="expand">';
print(drupal_get_form('user_profile_form', $user, 'education'));
print '</div>';
Problem #1: ? Could you post the html source?
For problem #2:
OK, I'll step through the code here:
The validation handler for the user profile form (user_profile_form_validate()) calls
user_module_invoke('validate', $form_state['values'], $form_state['values']['_account'], $form_state['values']['_category']);
Which looks like
<?php
/**
* Invokes hook_user() in every module.
*
* We cannot use module_invoke() for this, because the arguments need to
* be passed by reference.
*/
function user_module_invoke($type, &$array, &$user, $category = NULL) {
foreach (module_list() as $module) {
$function = $module .'_user';
if (function_exists($function)) {
$function($type, $array, $user, $category);
}
}
}
?>
So, the validation handler for this form is going through every module looking for user hook functions and calling them with $type = 'validate'. (Note that 'category' param is optional here - contrib modules are not required to use it)
Let's look at user.module's user hook as an example to see what happens:
function user_user($type, &$edit, &$account, $category = NULL) {
if ($type == 'view') {
$account->content['user_picture'] = array(
'#value' => theme('user_picture', $account),
'#weight' => -10,
);
if (!isset($account->content['summary'])) {
$account->content['summary'] = array();
}
$account->content['summary'] += array(
'#type' => 'user_profile_category',
'#attributes' => array('class' => 'user-member'),
'#weight' => 5,
'#title' => t('History'),
);
$account->content['summary']['member_for'] = array(
'#type' => 'user_profile_item',
'#title' => t('Member for'),
'#value' => format_interval(time() - $account->created),
);
}
if ($type == 'form' && $category == 'account') {
$form_state = array();
return user_edit_form($form_state, (isset($account->uid) ? $account->uid : FALSE), $edit);
}
//<-- LOOK HERE -->
if ($type == 'validate' && $category == 'account') {
return _user_edit_validate((isset($account->uid) ? $account->uid : FALSE), $edit);
}
if ($type == 'submit' && $category == 'account') {
return _user_edit_submit((isset($account->uid) ? $account->uid : FALSE), $edit);
}
if ($type == 'categories') {
return array(array('name' => 'account', 'title' => t('Account settings'), 'weight' => 1));
}
}
So, it is only supposed to validate if the category == 'account'
In the function _use_edit_validate, we find:
// Validate the e-mail address:
if ($error = user_validate_mail($edit['mail'])) {
form_set_error('mail', $error);
}
There's your error message.
Since that form is only supposed to validate when the category == 'account', and your problem (#2) seems to be that it always validates regardless of the category, maybe your forms are not being rendered as unique form instances? Drupal might be rendering a complete form each time, and just setting a hidden form value to whatever the category is (like in this form's definition function in user_pages.inc $form['_category'] = array('#type' => 'value', '#value' => $category);)
It would be helpful to see the actual html source output.
==EDIT 10-15-09 in response to updated question===
OK, it looks like your method (editing $form['#action'] manually in the theme layer) may not be possible (see this post for reference). If you want to alter the form action you need to write a custom module that implements hook_form_alter() (it won't work in a theme template file). This function allows you to modify how a form is rendered, in your case the user modification form. There are more details on form modification here.
I am not 100% sure that's what you want to do though; (since it looks like you already must create a module) perhaps you want to hook into hook_user() instead; this function "... allows modules to react when operations are performed on user accounts.". You may be able to react to the category in this function and block/allow whichever user changes you like.
However, if it's just email address validation that is the problem, and if you are dealing with existing users, why don't you just make sure the email address is set before you save?