What I'm trying to accomplish
I'm building a favorites module and I need the ability to:
Select from a dropdown, hardcoded list of options
Have it save to the database
Upon refreshing the page, remove the already saved option from the list of options so it may not be added again
The third part is where I am unsure of how to proceed.
How my code is set up
This is my form:
/*
* Implentation of hook_form().
*/
function f25_favorites_form() {
$listOfPaths = f25_favorites_listOfPaths();
$form['path_options'] = array(
'#type' => 'value',
'#value' => array(
'default' => $listOfPaths['default']['#title'],
'concierge' => $listOfPaths['concierge']['#title'],
'concierge/add' => $listOfPaths['concierge/add']['#title'],
'survey-questions' => $listOfPaths['survey-questions']['#title'],
'survey-questions/add' => $listOfPaths['survey-questions/add']['#title'],
'profiles' => $listOfPaths['profiles']['#title'],
'profiles/add' => $listOfPaths['profiles/add']['#title'],
'statistics' => $listOfPaths['statistics']['#title'],
)
);
$form['path'] = array(
'#type' => 'select',
'#title' => t('Select Page'),
'#required' => TRUE,
'#weight' => '11',
'#options' => $form['path_options']['#value'],
);
$form[submit] = array(
'#type' => 'submit',
'#weight' => '1000000',
'#value' => t('Add')
);
return $form;
}
The name of the paths/options are called via a reference array:
/*
* List of Paths to add to favorites
*/
function f25_favorites_listOfPaths() {
$list = array();
$list = array(
'default' => array(
'#title' => t('Add to favorites'),
),
'concierge' => array(
'#title' => t('Concierge'),
'#image' => drupal_get_path('module', 'f25_favorites').'/img/concierge.png',
'#desc' => t('Concierge'),
),
'concierge/add' => array(
'#title' => t('New Concierge'),
'#image' => drupal_get_path('module', 'f25_favorites').'/img/concierge.png',
'#desc' => t('Concierge > Add'),
),
'survey-questions' => array(
'#title' => t('Survey Questions'),
'#image' => drupal_get_path('module', 'f25_favorites').'/img/survey-questions.png',
'#desc' => t('Current Survey Questions'),
),
'survey-questions/add' => array(
'#title' => t('New Survey Question'),
'#image' => drupal_get_path('module', 'f25_favorites').'/img/survey-questions.png',
'#desc' => t('Survery Question > Add'),
),
'profiles' => array(
'#title' => t('Profiles'),
'#image' => drupal_get_path('module', 'f25_favorites').'/img/profiles.png',
'#desc' => t('User Profiles'),
),
'profiles/add' => array(
'#title' => t('Add Profile'),
'#image' => drupal_get_path('module', 'f25_favorites').'/img/profiles.png',
'#desc' => t('Profiles > Add'),
),
'statistics' => array(
'#title' => t('Statistics'),
'#image' => drupal_get_path('module', 'f25_favorites').'/img/statistics.png',
'#desc' => t('Performance Stats'),
),
);
return $list;
}
And all this is what grabs the data on the databse:
/*
* Write Form data to database
*/
function f25_favorites_form_submit($form, &$form_state){
global $user;
$listOfPaths = f25_favorites_listOfPaths();
$selected = $form_state['values']['path'];
$data = array(
'uid' => $user->uid,
'path' => $selected,
'title' => $listOfPaths[$selected]['#title'],
'weight' => 10,
'timestamp' => time(),
);
drupal_write_record(f25_favorites, $data);
}
Possible Solutions
I've been told that I could used hook_form_alter() in order to modify my array but I am unsure as to when I should be comparing the db_query to my array and how to modify the differences accordingly.
I hope I've done a good job explaining what I'm try to do.
What would be the best way to accomplish this?
Instead of writing every response in f25_favorites_listOfPaths(), shouldn't you get them from the database?
You can then change whatever you want in the submit function to the database so that you don't fetch again the previously selected answer.
Example :
function f25_favorites_listOfPaths() {
return variable_get('f25_favorites_array_' . $user->uid, array(
// your $list array
));
}
function f25_favorites_submit_form($form, &$form_state) {
// your stuff already
drupal_write_record(f25_favorites, $data);
// Now what I propose you to do :)
variable_set('f25_favorites_array_' . $user->uid, array(
// new $list array without the favorite selected
));
}
The use of variable_get/set() should of course be replaced by your own table if you have too much datas.
P.S. : hook_form() does not exist :)
Related
I have created a new module to add a new step to the signup process to get the user's phone number. Everything works fine, except the fact that after the signup, I need to grab the phone number I have received in my specific step and add it to the users table. For this purpose, I have added a hook in my module's manifest file as follows to be triggered after signup completes:
<?php
return array(
'package' =>
array(
'type' => 'module',
'name' => 'advancedsmsplugin',
'version' => '4.0.0',
'sku' => 'com.company.sms',
'path' => 'application/modules/Advancedsmsplugin',
'title' => 'Advanced SMS Plugin',
'description' => 'Advanced SMS Plugin',
'author' => 'Company Ltd.',
'callback' =>
array(
'class' => 'Engine_Package_Installer_Module',
),
'actions' =>
array(
0 => 'install',
1 => 'upgrade',
2 => 'refresh',
3 => 'enable',
4 => 'disable',
),
'directories' =>
array(
0 => 'application/modules/Advancedsmsplugin',
),
'files' =>
array(
0 => 'application/languages/en/advancedsmsplugin.csv',
1 => 'application/modules/User/Form/Signup/Phone.php',
2 => 'application/modules/User/Plugin/Signup/Phone.php',
),
),
'hooks' => array(
array(
'event' => 'onUserCreateAfter',
'resource' => 'User_Plugin_Phone',
),
),
);
?>
I have also created the class that I named in the hook namely User_Plugin_Phone and saved it as application/modules/user/Plugin/Phone.php.
class User_Plugin_Phone extends Core_Plugin_Abstract {
public function onUserCreateAfter($event) {
echo '<script>console.log("Inside User_Plugin_Phone::onUserCreateAfter");</script>';
echo '<script>alert("Inside User_Plugin_Phone: onUserCreateAfter");</script>';
$payload = $event->getPayload();
}
}
But, logs show that the hook is not triggered at all. I have checked this and this, and I think I have followed them correctly. Any ideas why this does not work]?
Can someone help me to set up a layout for the administration module and set up a layout for the application module ? . In the image below you can see my folder structure:
This is the content of my module.config.php from the administration module:
<?php
return array(
'controllers' => array(
'invokables' => array(
'Administration\Controller\Admin' => 'Administration\Controller\AdminController',
),
),
// The following section is new and should be added to your file
'router' => array(
'routes' => array(
'album' => array(
'type' => 'segment',
'options' => array(
'route' => '/administration[/:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Administration\Controller\Admin',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
//'base_path' => 'http://www.attila-naghi.com/',
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => array(
'layout/layout' => __DIR__ . '/../view/layout/layout2.phtml',
// 'administration/admin/index' => __DIR__ . '/../view/administration/admin/index.phtml',
// 'error/404' => __DIR__ . '/../view/error/404.phtml',
// 'error/index' => __DIR__ . '/../view/error/index.phtml',
),
'template_path_stack' => array(
DIR . '/../view',
),
),
)
?>
here i set up the layout. But for some reason if i access the application module, it loads the layout from the administration module. WHY ?
this is the content of the module.config.php file from the application module:
return array(
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '[:controller[/:action]][/:param1]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*'
),
'defaults' => array(
'action' => 'index',
'__NAMESPACE__' => 'Application\Controller',
// 'param1' => 'tralala'
)
)
)
)
),
),
),
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'aliases' => array(
'translator' => 'MvcTranslator',
),
),
'translator' => array(
'locale' => 'en_US',
'translation_file_patterns' => array(
array(
'type' => 'gettext',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
),
),
),
'controllers' => array(
'invokables' => array(
'Application\Controller\Index' => 'Application\Controller\IndexController',
'Application\Controller\Create' => 'Application\Controller\CreateController',
'Application\Controller\Blog' => 'Application\Controller\BlogController',
'Application\Controller\Portofolio' => 'Application\Controller\PortofolioController',
'Application\Controller\User' => 'Application\Controller\UserController',
),
),
'view_manager' => array(
'base_path' => 'http://www.attila-naghi.com/',
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => array(
'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
),
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
// Placeholder for console routes
'console' => array(
'router' => array(
'routes' => array(
),
),
),
);
and this is the application.config.file content:
return array(
// This should be an array of module namespaces used in the application.
'modules' => array(
'Application',
'Administration'
),
......
You have to change layout from the controller. just specify this code above the ViewModel
$this->layout('administration/admin/index');
The configs of all modules are merged into a single config. The last loaded module will overwrite the layout from the first module. You can use the module below to set layout per module.
https://github.com/EvanDotPro/EdpModuleLayouts
I have recently found a way to do it (someone else's solution).
Add this to your module.config.php (in my case the module is called Album, this is based on the demo application of ZF2):
'module_layouts' => array(
'Album' => 'layout/layout.phtml'
),
The other necessary change needs to be done in the main Module.php file (add this onBootstrap method there and edit as may be needed):
public function onBootstrap($e) {
$e->getApplication()
->getEventManager()
->getSharedManager()
->attach('Zend\Mvc\Controller\AbstractController', 'dispatch',
function($e) {
$controller = $e->getTarget();
$controllerClass = get_class($controller);
$moduleNamespace = substr($controllerClass, 0, strpos($controllerClass, '\\'));
$config = $e->getApplication()->getServiceManager()->get('config');
if (isset($config['module_layouts'][$moduleNamespace])) {
$controller->layout($config['module_layouts'][$moduleNamespace]);
}
}, 100);
}
As requested here is my way for different layouts. This was not my idea but since i cannot find the source i will post the code here. If someone does know please add the URL in comment and i will include it in the answer. If you read the sources i gave you in the other question and the above answers i am sure will understand what is happening here.
Module.php
use Zend\ModuleManager\ModuleManager;
> > public function init(ModuleManager $moduleManager)
> {
> $sharedEvents = $moduleManager->getEventManager()->getSharedManager();
> $sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
> // This event will only be fired when an ActionController under the MyModule namespace is dispatched.
>
> $controller = $e->getTarget();
$controller->layout('backofficeLayout');
>
> }, 100);
>
>
>
> }
module.config.php
'view_manager' => array(
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_path_stack' => array(
'backoffice' => __DIR__ . '/../view',
),
'template_map' => array(
'backofficeLayout' => __DIR__ . '/../view/layout/myaccount-backoffice.phtml',))
I have just noticed that is better to use custom name for each of the layout in your layout folder. Instead of using layout.phtml use layout-mymodulename.phtml. It worked better along with the previously highlighted points.
How i can set a success_handler (and failure_handler) for the form authentication provider?
Silex ignores me with this config:
<?php
use WebFactory\Security\UserProvider;
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'dev' => array(
'pattern' => '^/(_(profiler|wdt)|css|images|js)/',
'security' => false
),
'default' => array(
'pattern' => '^/.*$',
'anonymous' => true,
'form' => array(
'login_path' => '/login',
'check_path' => '/login_check',
'success_handler' => 'authentication_handler', //<-- here
'failure_handler' => 'authentication_handler', //<-- here
),
'logout' => array('logout_path' => '/logout'),
'users' => $app->share(function () use ($app) {
return new UserProvider($app['db']);
}),
),
),
'security.access_rules' => array(
array('^/login', 'IS_AUTHENTICATED_ANONYMOUSLY'),
array('^/private$', 'ROLE_ADMIN'),
),
'security.role_hierarchy' => array(
'ROLE_SIMPLE_USER' => array('ROLE_USER'),
'ROLE_ASSOCIATE' => array('ROLE_USER'),
)
));
And this my custom (never invoked)
$app['authentication_handler'] = $app->share(function ($app) {
return new \WebFactory\Security\AuthenticationHandler($app['url_generator']);
});
It is a bug?
The way you set success and failure handlers is by defining a service called security.authentication.success_handler.$name or security.authentication.failure_handler.$name, where $name is the name of the firewall.
For example:
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'foo' => ...,
),
));
$app['security.authentication.success_handler.foo'] = $app->share(function ($app) {
return new Your\Own\SuccessHandler();
});
The security service provider will then detect the handlers by convention.
I have a problem with the EdpModuleLayouts module. I put Module.php in module/EdpModuleLayouts/ directory with the following content:
<?php
namespace EdpModuleLayouts;
class Module {
public function onBootstrap($e) {
$e->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e) {
$controller = $e->getTarget();
$controllerClass = get_class($controller);
$moduleNamespace = substr($controllerClass, 0, strpos($controllerClass, '\\'));
$config = $e->getApplication()->getServiceManager()->get('config');
if (isset($config['module_layouts'][$moduleNamespace])) {
$controller->layout($config['module_layouts'][$moduleNamespace]);
}
}, 100);
}
}
I also registered it in the config/application.config.php:
return array(
'modules' => array(
'EdpModuleLayouts',
'Main',
'Administrator',
'Object'
),
'module_layouts' => array(
'Main' => 'layout/main',
'Administrator' => 'layout/admin',
),
'module_listener_options' => array(
'module_paths' => array(
'./module',
),
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
),
);
The config ot the "main" module looks like:
<?php
return array(
'router' => array(
'routes' => array(
'Main' => array(
'type' => 'Literal',
'options' => array(
'route' => '/main',
'defaults' => array(
'__NAMESPACE__' => 'Main\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '[/:controller][/:action][/:id]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]*',
),
'defaults' => array(
),
),
),
),
),
),
),
'service_manager' => array(
'factories' => array(),
),
'controllers' => array(
'invokables' => array(
'Main\Controller\Index' => 'Main\Controller\EmailController',
'Main\Controller\Error' => 'Main\Controller\ErrorController',
'Main\Controller\FAQ' => 'Main\Controller\FAQController',
'Main\Controller\Index' => 'Main\Controller\IndexController',
'Main\Controller\Pages' => 'Main\Controller\PagesController',
'Main\Controller\Settings' => 'Main\Controller\SettingsController',
'Main\Controller\User' => 'Main\Controller\UserController',
),
),
'view_manager' => array(
'template_map' => array(
'layout/main' => __DIR__ . '/../view/layout/main_layout.phtml',
'layout/header' => __DIR__ . '/../view/layout/main_header.phtml',
'layout/footer' => __DIR__ . '/../view/layout/main_footer.phtml',
'index/index' => __DIR__ . '/../view/index/index.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
),
'template_path_stack' => array(
__DIR__ . '/../view',
),
'display_exceptions' => true,
'exception_template' => 'error/index',
'display_not_found_reason' => true,
'not_found_template' => 'error/404',
),
);
But when I access whatever module in the application I want, it throws an exception:
( ! ) Fatal error: Uncaught exception 'Zend\View\Exception\RuntimeException' with message 'Zend\View\Renderer\PhpRenderer::render: Unable to render template "layout/layout"; resolver could not resolve to a file' in D:\WebServer\www\homepage\vendor\library\Zend\View\Renderer\PhpRenderer.php on line 461
( ! ) Zend\View\Exception\RuntimeException: Zend\View\Renderer\PhpRenderer::render: Unable to render template "layout/layout"; resolver could not resolve to a file in D:\WebServer\www\homepage\vendor\library\Zend\View\Renderer\PhpRenderer.php on line 461
What's the reason?
As a newcomer to ZF2, it took me a while to make EdpModuleLayouts module to work. The key is to configuring the "view_manager" and "template_map" settings. Please visit http://www.webtrafficexchange.com/zf2-configure-layout-each-module-edpmodulelayouts for configuration samples.
All this is doing is setting the appropriate layout.
controller->layout($config['module_layouts'][$moduleNamespace]);
The error message is telling you that it was unable to find the template it is trying to set. You still need to configure your layouts in the template map. Here is an example from one of my projects.
'view_manager' => array(
'template_map' => array(
'cms-admin/admin/dashboard' => __DIR__ . '/../view/cms-admin/admin/dashboard.phtml',
'cms-admin/login/login' => __DIR__ . '/../view/cms-admin/login/login.phtml',
'cms-admin/users/index' => __DIR__ . '/../view/cms-admin/admin/users/index.phtml'
)
),
'module_layouts' => array(
'CmsAdmin' => 'layout/admin-layout',
),
Does the if (isset($config['module_layouts'][$moduleNamespace])) evaluates to true? I think that your option module_layouts should be in the config directory:
./config/layouts.config.glob.php:
<?php
return array(
'module_layouts' => array(
'Main' => 'layout/main',
'Administrator' => 'layout/admin',
),
);
?>
Could you give that a try?
I have not been able to get drupal_get_form to pass on the node data. Code snippets are below. The drupal_get_form documentation (api.drupal.org) states that it will pass on the extra parameters. I am basing the node data not being passed because (apparently) $node['language'] is not defined in hook_form which causes $form['qqq'] not to be created and thus the preview button shows up.
My goal here is that the preview button show up using path "node/add/author" but doesn't show up for "milan/author/add". Any alternative methods for achieving this goal would be helpful but the question I want answered is in the preceding paragraph. Everything I've read indicates that it should work.
This menu item
$items['milan/author/add'] = array(
'title' => 'Add Author',
'page callback' => 'get_author_form',
'access arguments' => array('access content'),
'file' => 'author.pages.inc',
);
calls this code
function get_author_form() {
//return node_form(NULL,NULL);
//return drupal_get_form('author_form');
return author_ajax_form('author');
}
function author_ajax_form($type) {
global $user;
module_load_include('inc', 'node', 'node.pages');
$types = node_get_types();
$type = isset($type) ? str_replace('-', '_', $type) : NULL;
// If a node type has been specified, validate its existence.
if (isset($types[$type]) && node_access('create', $type)) {
// Initialize settings:
$node = array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => 'bbb','bbb' => 'TRUE');
$output = drupal_get_form($type .'_node_form', $node);
}
return $output;
}
And here is the hook_form and hook_form_alter code
function author_form_author_node_form_alter(&$form, &$form_state) {
$form['author']=NULL;
$form['taxonomy']=NULL;
$form['options']=NULL;
$form['menu']=NULL;
$form['comment_settings']=NULL;
$form['files']=NULL;
$form['revision_information']=NULL;
$form['attachments']=NULL;
if($form["qqq"]) {
$form['buttons']['preview']=NULL;
}
}
function author_form(&$node) {
return make_author_form(&$node);
}
function make_author_form(&$node) {
global $user;
$type = node_get_types('type', $node);
$node = author_make_title($node);
drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t($node->title), 'node/' . $node->nid)));
$form['authorset'] = array(
'#type' => 'fieldset',
'#title' => t('Author'),
'#weight' => -50,
'#collapsible' => FALSE,
'#collapsed' => FALSE,
);
$form['author_id'] = array(
'#access' => user_access('create pd_recluse entries'),
'#type' => 'hidden',
'#default_value' => $node->author_id,
'#weight' => -20
);
$form['authorset']['last_name'] = array(
'#type' => 'textfield',
'#title' => t('Last Name'),
'#maxlength' => 60,
'#default_value' => $node->last_name
);
$form['authorset']['first_name'] = array(
'#type' => 'textfield',
'#title' => t('First Name'),
'#maxlength' => 60,
'#default_value' => $node->first_name
);
$form['authorset']['middle_name'] = array(
'#type' => 'textfield',
'#title' => t('Middle Name'),
'#maxlength' => 60,
'#default_value' => $node->middle_name
);
$form['authorset']['suffix_name'] = array(
'#type' => 'textfield',
'#title' => t('Suffix Name'),
'#maxlength' => 14,
'#default_value' => $node->suffix_name
);
$form['authorset']['body_filter']['body'] = array(
'#access' => user_access('create pd_recluse entries'),
'#type' => 'textarea',
'#title' => 'Describe Author',
'#default_value' => $node->body,
'#required' => FALSE,
'#weight' => -19
);
$form['status'] = array(
'#type' => 'hidden',
'#default_value' => '1'
);
$form['promote'] = array(
'#type' => 'hidden',
'#default_value' => '1'
);
$form['name'] = array(
'#type' => 'hidden',
'#default_value' => $user->name
);
$form['format'] = array(
'#type' => 'hidden',
'#default_value' => '1'
);
// NOTE in node_example there is some addition code here not needed for this simple node-type
$thepath='milan/author';
if($_REQUEST["theletter"]) {
$thepath .= "/" . $_REQUEST["theletter"];
}
if($node['language']) {
$thepath='milan/authorajaxclose';
$form['qqq'] = array(
'#type' => 'hidden',
'#default_value' => '1'
);
}
$form['#redirect'] = $thepath;
return $form;
}
That menu path coincides with this theme (PHPTemplate)
This might not be it but I see that you use $node as an object at first (title) and then as an array (to get the language) in the make_author_form() method. If $node is an object, then that explains why you cant retrieve $node['language'].
Not sure if I completely understand what you are trying to do but it would be a good idea to use page arguments for it, I think.
function mymodule_form_alter($form_id, &$form) {
// If $form_id is {node->type}_node_form
// Then, check for the first argument in the URL and hide/show Preview accordingly
}
Turned out to be a basic programming error in line 4 of the make_author_form function. I was zeroing out the $node variable myself.