navigate site menu

Start learning with our library of video tutorials taught by experts. Get started

Drupal 7 Custom Module Development

Drupal 7 Custom Module Development

with Jon Peck

 


Extend your Drupal 7 sites with custom modules, which allow you to create everything from admin interfaces to forms. Author Jon Peck describes how modules extend your base Drupal installation, then walks through how to write your own module with a practical example featuring geo-positioned alternative energy centers. The course also describes how to control access to site features, create new content types, build forms, understand data persistence, embrace coding standards, and much more.
Topics include:
  • Creating your first module
  • Interacting with hooks
  • Working with permissions and roles
  • Controlling access
  • Adding a menu item to an admin interface
  • Using the Form API (FAPI) to quickly create a form
  • Creating custom form validation
  • Manually creating a custom content type
  • Validating user input
  • Importing content using feeds
  • Creating a block
  • Understanding best practices and coding standards

show more

author
Jon Peck
subject
Developer, Web, CMS, Blogs, Web Design, Programming Languages, Web Development
software
Drupal 7
level
Advanced
duration
2h 57m
released
Oct 16, 2012

Share this course

Ready to join? get started


Keep up with news, tips, and latest courses.

submit Course details submit clicked more info

Please wait...

Search the closed captioning text for this course by entering the keyword you’d like to search, or browse the closed captioning text by selecting the chapter name below and choosing the video title you’d like to review.



Introduction
Welcome
00:04Welcome to Drupal 7 Custom Module Development. I'm Jon Peck.
00:09In this course, I'll show you how to extend Drupal 7 sites with new functionality using custom modules.
00:15Topics covered include how to control access to new site features, how to create new content
00:20types and build forms, how Drupal facilitates data persistence, how to implement proper
00:26coding standards, and much more.
00:29The end result will be a functional module and the foundational knowledge necessary for
00:34developing solutions within the Drupal content management framework.
00:37Let's get started.
Collapse this transcript
Using the exercise files
00:00In this course, I'll be developing a module that will extend the functionality of an existing
00:04Drupal 7 site installation.
00:07The recommended configuration is PHP 5.3 or above, Apache 2 or above, MySQL 5 or above, and Drupal 7.15.
00:18Other web servers such as Nginx or IIS should function, but will not be supported.
00:24Future versions of Drupal 7 should be compatible with the code written in this course, but
00:28we'll not be explicitly supported.
00:31Setting up an AMPP stack is beyond the scope of this course.
00:34If you do not already have a development server I recommend using a local development server
00:39running on your workstation.
00:41The Up and Running with Linux for PHP Developers course here in the lynda.com online training
00:46library will allow you to have an optimized virtual server running like any other program
00:51in your existing operating system.
00:53I will be demonstrated using a server created using this technique.
00:58Alternatively, you can use a web server solution stack packaged in your native operating system.
01:03XAMPP from apachefriends.org has distributions for Linux, Windows, Mac OS X, and Solaris.
01:11WampServer from wampserver.com is explicitly for Windows and MAMP from mamp.info is for Mac OS X only.
01:21Each of these packages will allow you to execute the exercises found in this course.
01:26Installing additional software within your native operating system is covered in the
01:30course installing Apache, MySQL, and PHP with David Gassner here in the lynda.com online training library.
01:37If local development is not an option, third-party hosted servers that have the appropriate versions
01:43of PHP, MySQL, and Apache also support Drupal 7.
01:48The exercise files for this course are contained in folders by chapter and movie.
01:53On my workstation, I have them in a folder named sandbox that my virtualized Linux server can access.
01:59Depending on your web server configuration you may need to store these files in a different
02:03place such as on a remote web server or in a folder accessible by local Apache, MySQL, and PHP stack.
02:11All custom development will take place in one folder;
02:13sites/all/modules/custom/windfarm.
02:19The first folder contains the entire web root.
02:23And each following exercise folder will contain the start state of the windfarm directory for each movie.
02:29Premium subscribers have access to a customized Drupal installation profile which was used
02:33for the site install in the recording of this course.
02:37There is no functional difference between this installation profile and the standard
02:41installation profile.
02:42The only changes relate to the design and the inclusion of the exact versions of the
02:46contributed modules used in this course.
02:48A Drush Make file used to download the contributed modules into the installation profile is freely
02:53available to all subscribers.
02:55But no support will be provided for its use.
02:58Finally, you will need some sort of editor or integrated design environment to edit your PHP scripts.
03:04I will be demonstrating with a free PHP editor NetBeans 7.2 for netbeans.org.
03:10I strongly recommend setting up NetBeans using the community maintained guide at drupal.org.
03:15This will configure support for Drupal's naming conventions and standards.
03:19A final note. As different web hosting configurations serve content from different URLs, the address
03:25you see in my browser may not exactly match what you see on your workstation.
Collapse this transcript
Understanding what you should know
00:00Before starting this course you should have a basic knowledge of the PHP language and
00:04have written a few scripts.
00:06Without this background you may lack the context necessary to understand what you are doing
00:11which will not serve you well.
00:12If you're looking for some background or a refresher, I recommend PHP with MySQL Essential
00:18Training with Kevin Skoglund here in the lynda.com online training library.
00:23Performing a site installation of Drupal 7 and module installation is considered out
00:27of scope of this course.
00:29Drupal 7 Essential Training by Tom Geller here in the lynda.com online training library
00:34provides several chapters on installing Drupal in several different environments along with
00:39the foundations of module management.
Collapse this transcript
1. Key Concepts
What are modules and why use them?
00:00Before I describe what modules are, let's explore the Drupal architecture at a high
00:04level for some perspective.
00:07Drupal is referred to as a Content Management Framework or CMF.
00:10A CMF is an application programming interface that can be used to create and customize a
00:16Content Management System.
00:18What does this mean practically?
00:20Drupal out of the box, consists of a number of components that can be used to build a
00:24website, but assembly is definitely required.
00:27This is different than a straight Content Management System such as WordPress.
00:32WordPress is extremely good at building blogs and minimal websites and has been optimized to do just that.
00:38As a result, you can get a blog up and running in WordPress very quickly.
00:42There is a cost though, while WordPress and the other similar blogging content management
00:46systems do have the ability to be extended, they are purpose-oriented.
00:51Often the technical needs of extending functionality outweigh the capabilities of the core system.
00:56In contrast, Drupal can be used to create websites in many different scales.
01:01From simple sites such as brochures, blogs and portfolios, Drupal can also be used to
01:06scale to full enterprise grade applications with hundreds of thousands of users.
01:11This flexibility is a double-edged sword.
01:13A core Drupal 7 installation is kind of like opening a box of building blocks and dumping
01:18them on the floor.
01:19There may be some recognizable shapes and some instructions on how to build a simple
01:23structure, but as a whole, it can be intimidating. Then focusing,
01:28one can see that each building block can connect to one another and experimentation
01:32and construction begins.
01:34With a context of what Drupal is in its core, the purpose and use of a module becomes clear.
01:39A Drupal module refers to software components that extend the functionality of a Drupal
01:44installation in order to accomplish a particular task or goal.
01:48According to the official Drupal module developer's guide, there are three distinct kinds of modules.
01:54Core modules, which are included with the Drupal distribution itself.
01:58Each core module is approved by core developers and the community.
02:01Core modules are already included within your site installation.
02:05Examples include, field, help, and user.
02:10Contributed modules which are written and maintained by members of the Drupal community
02:13and shared with the GNU Public License or GPL, as Drupal is. The official repository
02:19of contributed modules is found at drupal.org/project/ modules where close to 11,000 distinct projects are hosted.
02:29Examples of contributed modules include views, pathauto, and backup, and migrate.
02:34And finally, custom modules which are created by individual developers.
02:39The licensing of custom modules can vary.
02:41Some are shared only within a company or group, while others are released freely on public
02:45repositories such as GitHub. Sometimes they can be a fork or a variation of an existing
02:51contributed module.
02:53Like Drupal itself, Drupal modules are written in PHP.
02:56For Drupal 7, PHP 5.2.5 or higher is required while PHP 5.3 is officially recommended.
03:05Contributed and custom modules can optionally have external dependencies, such as a software
03:09library that needs to be installed separately from the module itself.
03:13For example, the Print module requires a PDF writing library such as TCPDF to be available.
03:20When there is an external dependency, the library or libraries in question are typically
03:24not included with the module itself.
03:27This is done for multiple reasons.
03:28Potential licensing issues can be avoided which is particularly important for GPO licensed
03:33modules found on drupal.org.
03:35Additionally, it avoids duplication of effort and cluttered source control repositories
03:40by separating disparate components logically.
03:43To review: Drupal modules extend the functionality of the core Drupal installation.
03:49There are core, contributed, and custom modules.
03:52Modules can interact with third-party libraries, but are typically not distributed with the
03:56libraries themselves.
03:58Using modules, a developer can manipulate virtually every aspect of a Drupal system
04:03without needing to alter the source code of core itself.
04:07One of the published Drupal best practices is never hack core.
04:12This cannot be stressed enough.
04:14Changing core yourself makes it virtually impossible to apply updates and upgrades that
04:18offer bug and security fixes along with performance enhancements.
04:22Second, it makes it harder for the next person.
04:25Maintenance of the site by others becomes difficult at best as they have to reverse
04:29engineer the hacks created by their predecessors.
04:33And third, it can introduce security holes.
04:36Security vulnerabilities can be introduced inadvertently, as a sole developer does not
04:40have the same oversight and peer review that core contributions receive.
04:45There are thousands of contributed modules that are available under the GPL license.
04:50So before writing a custom module, one should use due diligence and check to see if a module
04:54with that functionality already exists.
04:57If it does, then leverage the existing model and don't waste time reinventing the wheel.
05:03If the solution does exist, but there are deficiencies or shortcomings, submit an issue
05:07through the project homepage on drupal.org describing the problem and ideas on resolving it.
05:13The Drupal community actively request that developers regardless of their affiliation, contribute code back.
05:19So if there is an issue that you know how to resolve, please do contribute your code
05:23as a patch back to the project maintainer through an issue.
05:27Proper attribution will be given and the end product will be improved based on the collaborative effort.
05:32I will demonstrate how to build a custom module to solve a particular problem.
05:37Storing and rendering information about Wind Farms, then importing data from a public source
05:42to populate the system.
05:44Some contributed modules will be leveraged to save time and avoid duplication of effort.
05:49In practice, use freely available modules to get as close as possible to the necessary
05:54solution, then custom build the functionality needed to get the job done.
05:58Let's start building a custom module.
Collapse this transcript
Creating your first module
00:00Drupal looks for modules in several places.
00:03While it may be tempting to put a contributed or custom module in the modules directory
00:07of the site root, this directory is reserved for Drupal core modules.
00:12Instead, additional modules are typically stored within the sites > all > modules directory of the web root.
00:21If you have installed contributed modules, you may see some directories there now.
00:26Best practice is to organize modules within subdirectories with clear headings, such as
00:31contrib and custom.
00:34Contrib is short for contributed modules, which I previously mentioned, and custom is
00:38for custom modules, which I will be demonstrating.
00:42Each Drupal module has a human readable name and a machine name.
00:46The machine name is used throughout the module development, while the human readable name
00:50is only shown on administrative interfaces.
00:54Drupal module machine names follow a consistent naming convention.
00:58All lower case, no spaces, and concise as possible.
01:02You can use underscores in the module name, but it can get unwieldy if you have more than one.
01:08Numbers are allowed, but are generally not recommended as a best practice.
01:12As the module name is also used in function names, module names cannot start with a number.
01:18Here are some good examples of naming conventions.
01:21These are real contributed modules available on drupal.org.
01:25Coder, a module for algorithmically assisting with code review, devel, which is development
01:31and collection of debugging tools, and google_analytics, which is a helper for adding a
01:37Google Analytics code to a website.
01:39Next, some examples that are technically legal but are not recommended for various reasons;
01:45these module names are imaginary.
01:48Test1, it's a legal name but does not clearly describe the purpose of the module.
01:53A number should be avoided as well.
01:56Canoper_2, it's a legal name but it contains a specific version number.
02:03Cat_and_dog_administration_assistant _program, legal, but too verbose.
02:08Finally, some bad module names.
02:11Again, these modules do not exist; MyModule with a capital M for both.
02:16The upper case and camel case are not appropriate for module names.
02:21Test with a capital T space Module, the spaces are not allowed and it starts with a capital letter.
02:282good; similar to PHP methods and functions, module names cannot start with a number.
02:35The first step in module creation is describing the module to Drupal.
02:39Instead of describing module metadata in a function which can have a lot of overhead
02:44and dependencies, the metadata is described in the PHP INI format.
02:49The INI format is a very simple and widely used configuration formats which can be read
02:54directly from the file system.
02:56The metadata is stored within an .info file.
02:59The name refers to the naming convention and the file extension.
03:02A .info file consists of a module machine name followed by a period and the file extension info.
03:10For the purposes of the demonstration, I will develop a module to facilitate the management
03:15of a database of Wind Farms.
03:16A concise way of describing the module would be Wind Farms, which I will combine into one
03:21word, to keep the machine name simple.
03:24I'm going to create the .info file for the module.
03:28Within the IDE or text editor, navigate to the directory's sites > all > modules, right-click
03:37on modules, and go to New > Other, select Other, then Empty file.
03:45File Name, this is the machine name for the module.
03:47So the filename should be windfarms.info.
03:51Remember, it's best practice to place all custom modules in a subdirectory called custom,
03:58then the module itself is contained in a folder with the same machine name.
04:02Under Folder specify a subdirectory, /custom/ windfarms, then click Finish to create the file.
04:13.info files consist of a simple series of directives.
04:17Each directive contains a name, an equal sign for assignment, and a value.
04:22You can also place comments within .info files.
04:25Any line that begins with a semicolon is ignored by the Drupal INI Parser.
04:30I'm going to describe the windfarms module with only three required directives: name,
04:36description, and core.
04:39The first directive to create is the name of the module.
04:43As the machine name is already specified in both the folder and the .info file, there
04:47is no need to explicitly declare it.
04:50However, the module must be able to be managed from the Drupal user interface, so human readable
04:55name needs to be assigned.
04:58The name directive can be created with the following syntax, name = Wind Farms.
05:05Next, a description is needed. This should be a single plain text description in one
05:11sentence that describes the purpose of the module; description = A database of wind farms.
05:20Finally the core directive.
05:22This specifies the major version of Drupal that is required for this module.
05:26There is no need to specify particular minor version of Drupal, so the version wildcard
05:31x is used; core = 7.x, save the .info file.
05:39At this point the module is described to Drupal.
05:41However, if I open a browser and go to the Drupal module list, you will not see the module listed anywhere.
05:48One final component is still needed, a .module file.
05:52A .module file is just a PHP file with a different file extension.
05:57This is the dynamic heart of the module that by convention contains all the major implementations
06:02of Drupal functionality.
06:04For the time being this file can be empty.
06:07In the IDE, right-click on the windfarms directory and go to New > Empty File, and name it windfarms.module,
06:19Finish to create the file.
06:22As this is a PHP file, open a new PHP code tag, but do not close it.
06:27According to the Drupal coding standard, the closing PHP code tag is purposely omitted
06:32from the module and include files.
06:35This is done for several reasons; it eliminates the possibility of unwanted whitespace at
06:39the end of a file from causing header errors, XML validation issues and other similar problems.
06:46The closing delimiter is optional in PHP and php .net itself does not use closing delimiters.
06:52Therefore, the closing PHP tag can be omitted.
06:56Even though this file can be empty for now, Drupal coding standards dictate that each
07:00file has standardized comments containing documentation, also known a docblock at the
07:05beginning of the file.
07:07These docblocks are formatted using the doxygen format, which is used by a document generator
07:12of the same name.
07:14To create a file level docbock, type forward slash followed by two asterisks then type Enter.
07:20Some IDs will automatically create a standard docblock, if not, indent one space and add
07:26a new asterisk followed by a space.
07:29Each file level docblock starts with a token @file on its own line.
07:34This indicates to the documentation parsers and people who are reading the source code
07:38that this documentation block contains a description of the contents of this file.
07:42On the next line, still within the comment block, describe in a high level sentence the
07:46purpose of the module.
07:48In this case, Custom functionality for a wind farm database.
07:56If the comment block is not closed beneath, do so now.
07:59Finally, add a new line to the end of the file.
08:02This is considered a best practice and conforms the Drupal coding standard in order to make
08:07patches easier to read.
08:09Save the module, then go to the browser and click on Modules, scroll down to Other and
08:18the newly created Wind Farms module will be listed.
08:22Note that there is no version or operations available and that the module is not enabled by default.
08:29There is no need for operations at this time, but not knowing the version of a module is
08:34not very helpful.
08:35When a module is packaged and available on drupal.org, the version is automatically added
08:40by the source control system.
08:42As this module is being developed outside drupal.org, the version will need to be manually specified.
08:49Return to the IDE and open the .info file.
08:53The module version can be specified with the name version, followed by the assignment operator.
08:59The version itself should start with a core version of Drupal, which in this case is 7.x
09:04followed by a hyphen, then the module version in point format.
09:09Do not use spaces or underscores for the version.
09:12I will call this version, 0.1, an additional optional label can be specified after the module version.
09:19There are several common naming conventions for optional labels; dev for development only,
09:25which is often volatile; alpha, which is the first phase of software testing; beta, which
09:32is feature complete, and RC for release candidate final testing.
09:37The optional label is set with a dash followed by the label.
09:41As this module is under development, I will label it as -dev.
09:47Save the .info file and return to the browser and refresh.
09:55The Wind Farms module now shows a version.
09:58The new custom module has been described to Drupal and is now recognized.
10:02There is now a placeholder where custom functionality will be placed.
10:06In the next segment, I will provide an overview of Drupal's application programming interface
10:11known as hooks.
Collapse this transcript
Interacting with hooks
00:00Drupal uses a unique approach for providing an application programming interface.
00:05A hook is a PHP function that allows a module to hook into Drupal and modify behavior.
00:12A Drupal hook functions similar to a callback, in that they act as triggered events.
00:17Unlike an actual callback, hooks aren't registered with a listener.
00:21Instead, Drupal executes hooks based on their name up on a particular event.
00:27Hooks are similar to object oriented architecture in several ways.
00:31Hooks are constructed with a strict naming convention with function names prefixed with a module name.
00:37Similar to an abstract method, each hook has a defined set of parameters and return type.
00:43To use a hook, a developer will implement the hook by writing a function that conforms
00:48to the naming convention and accepts the same parameters and returns the same type.
00:53There are hundreds of hooks available in Drupal core, allowing developers to manipulate practically
00:58every aspect of a page build.
01:01Some common examples of hooks include, hook_form, which allows robust user facing forms to be
01:07created in a standardized manner using the form API; hook_help, which allows a module
01:13to make documentation available to the user, and hook_menu, which gives modules the ability
01:19to both add items to the site menus and to route page requests to code based on a URL.
01:24A comprehensive list of hooks found in Drupal core can be found at api.drupal.org.
01:32In practice, adding a hook to a module is straightforward.
01:36One of the first things I look for in a contributed module is the documentation, which also includes built in help.
01:43Additionally, having a centralized repository of instructions that can be easily added to
01:47as development progresses is a good habit to be in.
01:51Therefore, the first hook that I will implement in the windfarms module is hook_help.
01:57For an example of implementation of hook_ help, open your browser and click on Help.
02:04You will see a list of Help topics on a number of items, each of which is named after the
02:08core module that implements hook_help.
02:11I'm going to add a Help for the Wind Farms module to this area.
02:16Click on Help, this will describe the purpose of the help module.
02:22Notice the path, admin help; this is the base path, then help again; this is the name of
02:29the module that is implementing hook_help.
02:32Open your IDE and navigate to the windfarms.module.
02:37Before I do anything, I will add documentation in conformance to the Drupal coding standard.
02:42It's also just common sense to document your work.
02:45I'll create a new documentation block, similar to the file docblock above where the function will go.
02:52When implementing a hook, I have the option to document the individual parameters.
02:56However, this isn't required as there should already be comprehensive documentation in
03:01the original hook.
03:02Therefore, it's usually omitted.
03:04According to the standard, all that is needed is the word implements and the name of the
03:08hook that is being implemented, which in this case is hook_help followed by parentheses
03:13to indicate that a function is being implemented and a period, as this is the end of a sentence;
03:22Implements hook_help.
03:26Drupal code comment should be written as complete sentences, complete with proper grammar, capitalization
03:31and punctuation and a full stop at the end.
03:34If a comment spans multiple sentences, only use one space between sentences.
03:39This along with many other coding standard rules may sound needlessly pedantic.
03:44Consider the alternative, especially in the context of working with many different developers.
03:49By enforcing both their professional approach to internal documentation and coding, one
03:54can anticipate the structure, look, and feel of the code base, which leads to easier to
03:58understand and adaptable code.
04:02Now that the documentation is in place, it's time to add some functionality.
04:06To implement a hook, I need to create a function in my module, replacing the word hook with
04:12the name of my module and containing the same number of parameters, start with function.
04:19Naming conventions of Drupal hooks dictate that any hook must start with the name of
04:23the module, followed by an underscore.
04:25As I'm implementing hook_help, I will call this function windfarms_help.
04:32According to the Drupal hook_help API, there are two parameters: path and arg.
04:43The path parameter contains the router path as defined in hook_menu, which I will get
04:47into more detail in a bit.
04:49In short, it's similar to a partial URL without the domain or the first slash.
04:54The argument parameter can be safely ignored for now. There is no need for it at the time being.
04:59With that said as the parameter exists in the Drupal hook, it must be implemented.
05:03Based on the hook_help API, I know that the base limitation is expecting that I switch
05:08on the parameter path; switch path.
05:16Add a case to the switch for the wind farms module starting with admin/help.
05:22The Drupal core help module has made it easy to add a path for each module's documentation.
05:27All that is needed is a pound sign followed by the name of the module that you would like
05:31to add documentation for, which in this case is windfarms.
05:42According to the API documentation, hook_help returns a localized string containing the help text.
05:50Localization is a much larger topic, but in practice, Drupal already has a facility for
05:55handling localization, the t function.
05:58The t function translates a string into a particular language.
06:02Additionally, it can handle variable replacements within strings as well, which can be very useful.
06:07Finally, it also provides string sanitization.
06:11Practically speaking, the t function can just be passed a string and written in line with other markup.
06:16Due to the way the localization system works, the t function cannot accept a variable.
06:22Start by defining a variable for the Return value.
06:25I prefer ret_val, start with a standardized heading.
06:32The h3 tag will work nicely.
06:35The t function localizes text but should not be used for tags and other HTML elements.
06:41Therefore, I will start the header with an h3 tag as just a string, then call the t function.
06:50I will describe what this module is for with an About section.
06:58Next, concatenate assign a ret-val variable with a paragraph tag followed by the t function
07:03containing a high-level explanation about this module. The Wind Farms module makes it
07:16easy to manage a database of wind farms, followed by a closing p tag.
07:24Finally return the ret_ val that has been created.
07:31Save the wind farms module then, return to the browser and go back to the Help page.
07:37You will see no change in the page, why not? The module has not been enabled yet, so Drupal
07:42is not interacting with it.
07:44Go to the Modules menu item, then scroll to the other group, the Wind Farms module will be listed.
07:52Next to it, check the box for Enabled, then click Save configuration.
08:01Now next to the Wind Farms module under Operations, a new option appears named Help. Click on
08:08it and the help text that was defined in hook_help will now be displayed.
08:12If you click on the breadcrumb for Help, the Wind Farms module will now be listed in the Help topics.
08:20In this chapter, I've explored what modules are and how they relate to Drupal, created
08:24a barebones module, giving an introspection into the hook interface for extending Drupal,
08:30and finally, implemented a Help page using hook_help.
08:33Coming up, I will explore how to access site features in contents that can be controlled
08:38with user roles and permissions.
Collapse this transcript
2. Creating an Administrative Interface
Working with permissions and roles
00:00This chapter will explore how to control access to site resources, demonstrate how to create
00:05permission programmatically, then how to implement a functional administrative interface for
00:10the cost of module.
00:12Access control is an extremely low level yet important aspect of creating an application.
00:17The user module in Drupal Core provides the mechanisms used by Drupal to manage user accounts.
00:23Including account creation, logins, and logouts, and assigning a role and permissions to a user.
00:29A Drupal role is a mechanism for assigning specific permissions to a group of users.
00:35In your browser, navigate to People then click on the Permissions tab.
00:41You will see a list of the individual permissions available, and on the right, columns containing
00:45the available roles.
00:47By default, there are three roles that are available in a stock Drupal site installation.
00:52Anonymous which indicates that the viewer has not logged into the site, authenticated
00:57meaning a user has logged into the system successfully, and administrator who was assigned
01:02site administrator privileges to do things like add and remove users, edit content, and so forth.
01:09The very first user account in the system, user ID 1, is considered to be a root user,
01:16meaning that they always have every permission no matter which roles are assigned.
01:21In the context of Drupal custom development it often makes sense to create a new permission
01:25in order to facilitate granular management.
01:29Permissions are checked automatically through the user access function from the user core module.
01:34While roles can be created programmatically as well I tend to advise not to do so as it
01:39forces developer's ideal organizational structure on to other site administrators.
01:45In short, it makes sense to provide just enough granularity that different site administrators
01:50can manage in their own style without getting in the way.
01:53Therefore, in this module, I will create a custom permission to control access to the
01:58administrative interfaces for managing windfarms.
02:01
Collapse this transcript
Creating a custom permission
00:00Similar to help, permissions can be created with a hook.
00:04In particular, Drupal 7 uses hook_permission which applies permissions defined in a module
00:09so they can be used from the user permissions page along with granting or restricting who
00:14can execute actions.
00:16Hook permission returns an array in a standardized format.
00:19Looking at the API documentation I can see that hook permissions takes no parameters.
00:24So the only thing I need to do is generate the array structure.
00:28Though hook_permissions array consists of a key that contains the name of the permission
00:32followed by a nested array definition that contains at the minimum two key value pairs.
00:37A title and description each passed through the t function.
00:40There are additional options, but they are used internally and rarely.
00:44Open the IDE and navigate to the windfarms.module, start with an appropriate doc block for the
00:50new hook which implements hook permission.
00:54Create a new function named windfarms permission.
01:00Hook permission takes no parameters.
01:03Next, add a return statement followed by an array definition.
01:09The key is the name of the permission. This is commonly all lowercase with words separated by spaces.
01:16I will use administer windfarms as the key followed by a second array definition.
01:22In the sub-array I will define the title which will be properly capitalized in human readable.
01:27This is displayed in the administrative interfaces.
01:30Since this is user-facing, I will wrap the title value in the t function to facilitate translation.
01:37Finally, I will add the description.
01:41This is usually a sentence long also wrapped in the t function, and shown in the same administrative interfaces.
01:48Perform administrative tasks on Wind Farms functionality.
01:52Save the module, return to the browser, and refresh the Permissions page.
02:01As you can see, the page does not list the new permission.
02:05The reason for this is because the permissions are cached.
02:08This is a normal issue encountered when developing in certain areas within Drupal.
02:12A change is made but does not show up immediately.
02:16To rectify this, clear the Drupal caches, go to Configuration, and then under Development
02:25click Performance, click the button Clear All Caches.
02:33Now return to the People page.
02:35Then the Permissions tab.
02:40The Wind Farms permission will now be shown.
02:42If you're logged in as UID 1, the root user and the first user that was created on the
02:47system, then you'll always have this permission.
02:50When a module is installed, administrators will be automatically granted the module permissions as well.
02:56Since the permission was added after the module was installed, the permission is not granted at this time.
03:02To allow other administrators to manage Wind Farms check the box under administrators for
03:08the Administer Wind Farms permission, then click Save permissions.
03:12Now that roles and permissions have been described and a new permission Administer Wind Farms
03:17has been implemented using hook permission, a protected administrative interface can be
03:21created for managing custom settings associated with custom functionality in addition to the
03:26content itself.
Collapse this transcript
Adding a menu item for an admin interface
00:00Before I create the wind farms administrative interface, I need to describe the location
00:04of the interface to Drupal.
00:06The menu system, which is distinct from the actual menu module, defines the navigational
00:11menus and routes page requests to code, based on URLs.
00:16These routes based on URLs are known as paths.
00:19The menu module itself provides an administrative interface to allow menus and the routing to be managed.
00:26To add a new menu item and routing path to the menu system, I will use hook_menu.
00:32Items added via hook_menu are aggregated by the menu system and assigned a hierarchy.
00:37This hierarchy can be initially setting code, but can also be manipulated using the menu
00:42module through the admin interface.
00:44Hook_menu defines menu items in page callbacks.
00:48This means a module can define what items are shown in the menu, the path that is associated
00:53with the menu item, ends the callbacks to be used when that path is accessed.
00:57The menu system does not access hook_menu very often, instead relying on caches to increase performance.
01:04Similar to when the permission was added, the cache will need to be cleared after hook_menu is implemented.
01:10Looking at the hook_menu API I can see that it takes no parameters and returns an array structure.
01:16Open the browser and navigate to the Configuration menu.
01:22There are a number of groups each with a description and a number of individual menu settings.
01:27I will be adding a group for wind farms and an interface for managing wind farms settings to the page.
01:33Note the page URL, admin config, this will be used when defining the menu items.
01:39Open the IDE and open windfarms.module.
01:43Start by adding a docblock that states that this function will implement hook_menu.
01:48Next, create the function itself. The function will be named windfarms_menu; it takes no parameters.
01:56I will be adding menu items, so I will name the return value to reflect that.
02:01Start by initializing an empty array, when the menu items have been defined they should be returned.
02:09Adding menu items to this particular administrator section is unique, in that, unless the plan
02:14is to add a menu item to an existing group, a new group will have to be created.
02:19Fortunately, this can be accomplished through hook_menu, the first item I will create is
02:24to group any settings associated with a windfarms module.
02:28I will start with an in-line comment describing what this menu item is going to do.
02:34Note that I'm using the Drupal coding standard for in-line comments meaning that the comment
02:38is structured like a regular sentence and ends with a full stop; Admin configuration group.
02:47Next I'm going to add an array to the items array keyed by the path.
02:51As the admin page URL is admin config, I will start with that followed by the name of the custom module.
02:58Remember not to start the path with a slash; items admin/config/windfarms is assigned to array.
03:09There are over a dozen different keys that can be used to configure menu items, but most
03:13are optional and have logical defaults so they can be left out.
03:17For an item to appear on a menu, it must have a title, which will be shown to the user as a bold heading.
03:23The menu system uses a callback to process the title and the description which defaults
03:28to the t function.
03:29So wrapping the value in t is not necessary; title Wind Farms.
03:37In addition to the title a description of the group should be added.
03:41With the default admin view this won't be visible, but some contributed administrative
03:45modules do use it and it's a best practice to always include the description; description
03:51Administer Wind Farms.
03:53Finally, I need to define who has permission to see these settings.
03:59In the previous segment, I created a custom permission.
04:02I will use that now, to manage access control there are two parameters; the access arguments
04:08and the access callback.
04:10By default, the access callback function is user access; meaning it will be checking our
04:16permission by name.
04:17As I created a permission that is used by user access I can use the default for the callback.
04:23I do need to specify the name of the permission to check which I will do with a key named
04:29access arguments array administer wind farms.
04:37This completes the configuration group.
04:39Now that I have a place to put the custom menu item, I can create it.
04:44Start a new comment, this one for the settings menu item; Admin configuration settings.
04:53Next add a new element to the items array keyed with a path.
04:57I will use the same path as above, with one addition, manage.
05:01Which will be the name of the page that I will create to manage wind farms settings;
05:06items admin/config/windfarms/manage = array, same as before a title is needed.
05:15Give it a logical name and stay concise.
05:18In this case, title Wind Farm settings.
05:25The description which will be shown on the regular admin page should be short and to
05:29the point; description Manage Wind Farm settings and configurations.
05:40Define the access arguments. This can be the same as the previous menu item; copy paste.
05:49As the menu item is actually referring to a place that can be navigated to additional
05:53steps are needed to allow this to function.
05:56The first thing that is needed is a definition of a page callback.
06:00The page callback is the name of the function that displays a web page when a user visits the path.
06:06In this case, I'm going to leverage some of Drupal's built-in functionality to quickly
06:10add a complete system for building a form, validating the input and handling the form submission.
06:17I'm going to describe how to work with Drupals form API or FAPI in the next segment.
06:23For now, all that is needed is the name of the page callback, Drupal get form which is
06:28part of the form API, page callback => drupal_get_form.
06:36Drupal_get_form takes one parameter, the form identifier.
06:41So I will pass that as a page argument, so drupal_get_form knows which form to process.
06:45I have not created the form yet.
06:48But I can anticipate the name. Forms are defined with hook form.
06:52So I know that the function name will start with the custom module name windfarms.
06:57Hook form allows multiple implementations within a module.
07:01Each with a distinct name as long as they end with underscore form.
07:06This helps with code organization, instead of having one giant hook form with a switch statement.
07:11As this is an admin settings form, call it what it is; similar to the other hooks the
07:16function name will and in form.
07:19Therefore, the full identifier of the form will be windfarms_admin_settings_form, page
07:27arguments => array(windfarms_admin_settings_ form') save the module, then return to the browser
07:39and refresh the admin configuration page.
07:46Similar to permissions, the menu system also leverages caches.
07:49So the new group is not visible yet.
07:52Clear the cache as to be able to see the changes by going to the DEVELOPMENT group, and clicking on Performance.
08:00Then clear all caches, return to the admin Configuration page and the new Wind Farms
08:12group is now visible.
08:15Click on the menu item. You'll see two legitimate error messages.
08:20The first means that Drupal retrieve form was unable to find a form by that name.
08:25The next message is a complaint that the callback is invalid.
08:28Let's create the form in the next segment to make this interface useful.
Collapse this transcript
Using the Form API (FAPI) to create a form quickly
00:00For web application developers, the act of building forms and processing form submissions
00:05is a common occurrence.
00:07Numerous coding frameworks contain standardized mechanisms that can be leveraged to create
00:11forms, including support for common form elements, prevention of cross-site request forgeries, and so forth.
00:18Drupal is no exception, containing a comprehensive system known as the form API or FAPI.
00:25The Drupal form API provides a sophisticated network for building, validating, and executing forms.
00:32Using hooks, modules can cleanly manipulate any form within Drupal including those within
00:37core, by extending and overriding functionality, instead of needing to rewrite or hack it in.
00:43Drupal forms have four distinct steps; creation where the form elements and hierarchal structure
00:49are defined using a standardized array, theming which refers to the manipulation of the design.
00:55There are multiple levels of theming from individual form elements all the way up to
00:59the structural layout of the entire page.
01:02In general, theming is mostly out of scope of this course, but it's helpful to know that the step exists.
01:09Validation immediately following form submission, Drupal form system performs generalized built-in validation.
01:16The validation step allows a developer to add more specific requirements for validation.
01:21And finally submission;
01:23once the user input has passed validation custom behavior can be outlined.
01:27Otherwise no functional changes occur.
01:30In the Wind Farms module, I'm going to specify three settings for an administrative interface:
01:35A toggle for enabling Google maps, a default map center latitude and longitude, and a default
01:41zoom level of the map.
01:43I will cover seven common form elements: markup, checkbox, fieldset, text, select, and submit.
01:53Drupal Core contains additional form elements including date, file, password, radio, and more.
02:01The interactions and definitions of these additional form elements are very similar
02:05to the elements I will be demonstrating.
02:08To create a form, an implementation off hook_form is needed.
02:13Similar to hook menu, form elements are declared within an array.
02:18Form elements can be nested allowing for complex hierarchies to be defined.
02:22By default, form elements are displayed in the order that they are defined in which enhances
02:27code readability.
02:29Let's create the form for the administrative settings for the Wind Farms module.
02:34Return to the IDE, navigate to the end of the wind farms module and add a dockblock
02:40for the form definition;
02:43Implements, hook_form, then same as the function name I discussed in the previous segment,
02:51create a new function named function windfarms_admin_settings_form.
03:00Looking at the hook form API, I can see the hook_form takes two parameters.
03:05The first, the node that is being added or edited.
03:09In this context, it won't be used as I'm not actually editing a node.
03:13The second, pass by reference, is form_state.
03:19Form state passes data about the processed form between steps. Form state is passed by
03:24reference to allow its contents to be manipulated and passed between the form creation, validation, and submission.
03:31Next, add a variable named form and set its value to an empty array.
03:38The name of the variable is technically unimportant, but it's best practice to name variables based
03:43on what their contents are.
03:45Add a return statement for the form variable at the end.
03:47As it's easy to forget to return the form definition after adding a number of form elements, return form.
03:57The first form element I will add is the Drupal form APIs default type markup.
04:02Markup elements generate HTML to display inside a form. As they are default, there is no need
04:08to explicitly set a form element as markup.
04:11I will use a markup form element to give an administrative user an overview of the purpose of this form.
04:17Form elements are added to the form array using the unique name for data to be processed.
04:22Think of it as the name attribute in an HTML input and follow the same naming conventions,
04:27the value will be an empty array; form overview = array.
04:37The form API uses hash tag names for the parameters to allow Drupal to differentiate between a
04:42nested form element and interactions with the API.
04:45The only parameter needed for a markup element is hash tag markup. The value should be wrapped
04:51in a t function; markup t 'This interface allows administrators to manage general Wind Farm Settings'.
05:06There is no restriction on the content that can be added to the markup element.
05:10So HTML can be added as well.
05:12By default, the contents of the markup are not wrapped in any tags.
05:17To wrap an entire form element in an HTML tag there are additional parameters that can
05:22be defined; the prefix and suffix.
05:25I'm going to wrap the page overview in a paragraph tag. Prefix and suffix can be defined independently
05:31and do not depend on one another meaning a prefix can be defined in the suffix can be emitted.
05:37This can be useful when manipulating markup to facilitate JavaScript operations, for example.
05:43Prefix is the paragraph tag and suffix is the closing paragraph tag.
05:55Typically HTML tags should be avoided in the form definition to allow a separation of logic and design.
06:01Instead it should be done in the theme.
06:04As theming really isn't covered in this course, and theming does interact with the form API,
06:08I feel it's worth demonstrating.
06:11Now that the purpose of the page has been given I will add the first functional element;
06:15a check box for toggling a Google map that will be created later.
06:19I will use the key wind farms g map for the name of the element to be consistent with
06:24how it will be saved in the database.
06:28The first parameter will be the title.
06:32This will be displayed on the form typically before the element itself.
06:35The value of the title should be wrapped in the t function, Enable Google Maps.
06:43Next a description, I like to think of this as context and help to the user.
06:48Again, the value will be wrapped in the t function, description t, 'When enabled, Google
06:59Maps will be rendered if latitude and longitude are known'.
07:08As this form element needs to be a check box, I will need to set the type to specify how
07:12this element is handled and rendered; type checkbox.
07:19Finally, I will specify a default value.
07:23The default value is initially set the first time the form is accessed.
07:27If the form fails validation, then the user specified value is used in its place.
07:32As the check box is binary, meaning it's on or off, the default value should be set as
07:37the integer zero or one.
07:39In a later segment, the value for these settings will be loaded from the database.
07:43So for the time being I will just default the element to being on; default_value 1.
07:52I'm going to enclose the next form elements in the field set.
07:56Field sets are used to give context and group form elements which improves usability and accessibility.
08:02The end result will be a nested array, with a field set definition at the head with the
08:07individual form elements as children.
08:09The form elements I'm going to put in this field set are the default map center location,
08:14so I will name this field set default_center.
08:18Form 'default_center' is an array with the title Map Center, a description ('Location
08:40of the center of the map of wind farms.') then the type is fieldset.
08:51Field sets have an additional property, the ability to collapse the display.
08:55This is optional but can be especially useful when working with a large form.
08:59By default, the collapsible property is disabled. To enable it, set it to Boolean true; collapsible set to TRUE.
09:12The next property is the default collapse state.
09:14I will set it to Boolean false to have it open by default, collapsed set to FALSE.
09:24Now that the fieldset has been defined, I can add the individual form elements within
09:28it using nesting.
09:29I'm going to use the text field form element for the latitude and longitude of the map center.
09:35For the default value, I will use the coordinates of a wind farm in upstate New York, which
09:39is close to my office. Form 'default_center' 'windfarms_default_center_lat'
09:50= array and the title of (Latitude) and the description.
10:03I'll describe the expected format, Signed degrees format DDD.dddd. Close the parenthesis.
10:12The type is textfield and the default value is 42.91455 as it's important to know where
10:29the center of the map is, I'm going to make these form elements required; required TRUE.
10:38Next, I'll do the longitude.
10:39I am going to paste to save some time, call it default_center_long, for the title Longitude,
10:54and for the default_value -75.569851.
11:02The last setting I will capture is the default zoom of the Google Maps map.
11:07The Google maps API zoom level is between 0 and 20.
11:11As this is a closed domain of options, I will use a select list as the form element.
11:16Select list take an additional parameter, the options, which is an array where the keys is
11:21the value which in turn maps to the label to be shown to the user.
11:25While the option array can be defined in line with the form element, sometimes it's necessary
11:29to build the options outside of the form element and pass a variable containing the array of options.
11:35I'm going to build the options rate using range to define the elements 0 through 20
11:40then overwrite some individual keys with specific values to give context to the user.
11:46Options = range, the range of 0 to 20 in one step increments, options 0, it's going to be
11:570 Furthest for some context, and options 20 t 20 Closest.
12:09Now that the values are defined, I can specify the form element.
12:13Form windfarms_default_gmap_zoom = array, the title of Google Map zoom, and a description
12:35default level of zoom between 0 and 20.
12:41For type I will specify select.
12:49Options will be set to the options array. The default_value, I will set at 8 and required is TRUE.
13:05I'm almost done with the form.
13:07One final element is necessary, a submit button.
13:11No special name is required for the name, so I will just name it submit; form submit
13:17= array, it'll be type.
13:27Only one additional key is required, the value, which will be what is shown as the label to the user, Save.
13:35Save the module then return to the browser and navigate to the Wind Farms Settings page.
13:43Instead of the error, the newly created form will be shown.
13:46If any of the form elements are missing go back and check the types first.
13:50The Drupal form API is much more likely to do nothing than to throw a visible error.
13:55While I've not specified either form validation or submission handling it,
13:59the use of the required fields has been automatically implemented.
14:03Remove the value for latitude and longitude then click Save. You will see a generic error
14:12message for each form element that was required.
14:15Latitude fields required is required, and so forth, and the elements that is errored is
14:19highlighted in red.
14:20In the next segment, I will describe how to customize these error messages and provide
14:24more granular validation, then how to handle a validated submission.
14:29
Collapse this transcript
Creating custom form validation
00:00The Drupal form API provides a mechanism for a number of common validations.
00:05These include required fields, which I've demonstrated, and also includes ensuring that the value
00:10given to a select form element is actually in the domain of available options.
00:15It even provides cross-site request forgery protection without any additional form elements.
00:21With that said, the complexity of the checks don't go much beyond these options and the
00:26error messages are minimal at best.
00:28Fortunately Drupal provides a mechanism for customizing the form validation.
00:33Before I continue, I will recommend that you would enable the devel module.
00:37Devel is a contributed module that consists of a suite of modules for assisting module
00:42developers with the helper functions and the other debugging information.
00:46I utilize it on a regular basis in my development environment, with a caveat of it should never
00:51be installed in production and care should be taken not to deploy code to production,
00:56with debugging output.
00:58Enable the devel module by going to Modules, scrolling down to DEVELOPMENT, clicking ENABLED next to Devel.
01:10Then Save configuration.
01:12If the devel module is not listed, please download it and install it from drupal.org.
01:18Drupal form validators are standardized callback functions.
01:22The function name is the same as the form creation with the addition of underscore validate.
01:27Drupal will automatically look for a function that matches this naming pattern and use it if it exists.
01:33If a function doesn't exist, there are no consequences other than the lack of custom validation.
01:39Return to the IDE and create a new docblock for the form validator.
01:45There is no actual hook form validate, but the Drupal form API allows forms to be validated
01:51as if there were; validates Wind Farm admin settings.
01:58Next create a new function beneath the docblock called function windfarms_admin_settings_form_validate,
02:07which takes two parameters; the form and the form_state passed by reference.
02:15Let's get some introspection into the form state itself.
02:18It's a large array containing over a dozen keys.
02:22To quickly display the contents of the form state upon form validation, I will leverage
02:26one of the helper functions from the develop module called DPM which stands for Drupal Print Message.
02:34DPM displays a variable to the message area of the page, dpm form_state, save and return to the browser.
02:43Go to configuration, scroll to Wind Farms Settings, let's create a bad value here and submit the form.
02:55The sanitize values are stored in the values array; the debugging information shows the
02:59contents of form state.
03:01Click Array and you can see the contents. The sanitize values are stored in values.
03:10DPM is very useful for determining array structures and values, return to the IDE.
03:15I'm going to be a little bit more specific about what DPM is displaying to make it easier
03:20to see what's going on.
03:23So DPM form state values; in particular the sanitize response from the user is contained
03:29in key values in the form state arrays, so I'll be using values for both validation and
03:33submission handling.
03:35To add a validation to a form, perform whatever logic that is needed.
03:39If the logic fails then, call function form_set_error.
03:44Form set error takes two parameters; the first is the name of the element that has a problem,
03:49and the second is a translated string that contains the error message.
03:53I will demonstrate validation using the text field for latitude and longitude which has
03:57a specific format maximum and minimum values.
04:01First, I'll do some setup to improve readability.
04:05I will use a regular expression to check the format of the sign degrees.
04:09Optionally, there can be a period.
04:11If so then it must be followed by at least one digit.
04:17Regular expression for validating signed degrees, signed_degree_regex = the start of the regular
04:32expression and optionally plus or minus followed by at least one digit.
04:40Then optionally a period followed by one or more digits, make that optional, and then the
04:49end of the expression.
04:51Next, since the submitted values have rather long names and are located in the nested array
04:56I'm going to assign a shorter variable name to their values.
05:01Shorthand for long array names; lat = form_ state values windfarms_default_center_lat.
05:16And long is set to form_state values windfarms_default_center_long.
05:24The first checks I will perform are on the format of the latitude and longitude using
05:29regular expressions I just wrote.
05:31I'll start with a comment.
05:36Validate latitude and longitude format.
05:41Then a simple if statement, if a regular expression does not match if not, preg_match signed_degree_regex
05:50and latitude call form _set_error.
05:54The first parameter is the name of the form element that is failed and the second is a
05:58string passed through the t function; form_set_ error windfarms_default_center_lat and the t function,
06:11Invalid latitude must be a signed degree.
06:21Create a second variation for longitude.
06:25The logic stays the same but the names and message are slightly different.
06:30Next, validate the latitude and longitude values.
06:34Each value must be between negative -180 and positive +180. Validate latitude and longitude values.
06:46If not -180 <= lat && lat <= 180.
06:59form_set_error windfarms_default_center_lat, and the message Latitude must be between -180 and 180.
07:17And we'll do the same for the longitude.
07:22Long, long, long and Longitude, save the file and return to the browser.
07:37Ensure that the form validation fails by putting invalid values into latitude and leave longitude empty.
07:47Note the contextual accurate messages.
07:50Longitude field is required and the invalid latitude message.
07:55Change the latitude and longitude to valid values that are different than the default.
07:59So I'm going to set to 1 and 2 then click Save. The DPM output is on top from the validation
08:09but the latitude and longitude have mysteriously gone back to their defaults.
08:13Why is that? By default on a failed form validation, the form rebuilds and is displayed to the
08:19user with their input.
08:22If the form passes validation then the form is not rebuilt.
08:25In the next segment, I will describe how to handle a successful form submission.
Collapse this transcript
Handling successful form submissions and saving variables
00:00Once validation is passed, the Drupal form API looks for submit callback.
00:05Return to the IDE, then create a new docblock for the submission callback.
00:12Process a validated Wind Farm admin setting submission.
00:19Similar to the way the validate function was named, a submit callback name starts with the
00:23form name followed by underscore submit. Same as a validate function, it takes two parameters;
00:29the form then the form state passed by reference; windfarms_admin_settings_form_submit, form
00:40then form_state, passed by reference.
00:47The first thing I want to do is tell the form to rebuild so the submitted values will be
00:51displayed even on success, instead of showing the default values.
00:55To do that I will manually set a flag in the form state.
01:00As form state was passed by reference, changes within this function will affect other form processors.
01:06The flag name is rebuild and accept Boolean values true or false.
01:11I'll start with a comment to describe what I'm about to do; Rebuild the form, form_state
01:19key rebuild = TRUE, save the module then return to the browser.
01:28Change the input to something valid, but different.
01:34This time the form elements contain the submitted input.
01:38Next load the page without a form submission and the default values are shown.
01:45To complete this interface I need to be able to save the settings.
01:48Drupal provides a mechanism for persistent variables that allows for practically any
01:53datatype to be stored in the database.
01:55There are three functions: variable_get which gets a value by name and optionally provides
02:01default value if the variable has never been set, variable_set which saves a value to the
02:07database by name, and variable_del which deletes a value from the database by name.
02:13Variables are useful for settings but in general should not be used for content.
02:19Variable names are, by best practice, lowercase and separated by underscores and start with a module name.
02:26Go back to the IDE and navigate the form creation of windfarms_gmap, and in particularl the default_value.
02:37Instead of just defaulting to 1, I will change it to use variable_get and use 1 as the default
02:43value, variable_get windfarms_ gmap and set the default value.
02:51The name is passed as a string and I will use the same name as the form element.
02:56Now if the windfarms_gmap has never been set it will default to 1.
03:00I will do the same for the latitude, variable_get, windfarms_default_center_lat,
03:14and for the longitude, variable_get windfarms_default_center_long.
03:26And finally the zoom; variable_get, windfarms_default_gmap_zoom.
03:36Now that the form can get the default for the variables let's set the variables upon
03:40a successful form submission.
03:43Return to the form submit callback then, beneath the form state add a comment describing what
03:50is about to happen; Save Wind Farm setting variables.
03:56Use variable set in the form state values.
03:59No additional manipulation of the input is needed as the form API handles the input sanitization
04:05and variable_get handles the database interactions.
04:10Variable_set, windfarms_gmap which is from form _state values, windfarms_gmap; variables_set,
04:25windfarms _default_center_lat which is from the form_state once again with the same name.
04:34Do the same for the longitude and finally the zoom, windfarms_default_gmap_zoom, save
04:52then return to the browser.
04:55Change the form values to something valid and click Save. 35 and 40.
05:05The page looks like it did before when it passed validation complete with a new input.
05:10Reload the page without a form submission to verify the persistence of the variables.
05:17As it stands, the admin interface is functional but lacks a usability touch.
05:21There is no feedback to the user when the settings are saved.
05:25Drupal core has a mechanism to store user messages within the session and display them.
05:30To set one of these messages use drupal_set_message.
05:35Return to the IDE and the form submit callback.
05:39At the end, provide an appropriate message to the user stating that the variables have been saved.
05:44Drupal set message takes two parameters.
05:47The first which is required is the message itself wrapped in the t function.
05:51The second is an optional message type, the default is status.
05:55Developers can also set warning and error messages, start with a comment for context.
06:02Notify user, then set the message, Drupal_ set_message t Wind farm settings saved.
06:12Finally, as development in this admin section is complete remove the TPM debugging call
06:18within the validator. Save the module and then return to the browser.
06:26Save new settings and this time the message is displayed to the user and no debugging
06:33information is shown.
06:38In the next, segment I will demonstrate best practices with variable persistence using
06:42module install and uninstall hooks.
Collapse this transcript
Exploring best practices with variable persistence
00:00In the previous video, I used variable_get with a default value to populate the form
00:04with sensible defaults.
00:07This works quite well when the form is submitted, but what happens if functionality that requires
00:11those variables is triggered?
00:12They just won't be there unless I use a variable_get that also has default values.
00:17That doesn't scale well however.
00:19What if I want to use that variable in a dozen places? Then I have to define a default value
00:24a dozen times. Then if I ever change the default then I have to re-factor all those places, and so forth.
00:30That approach doesn't scale well.
00:32Instead, I will leverage a one-time triggered event when a module is installed to set the
00:37defaults in one place using hook_install.
00:41Return to the IDE.
00:43By convention, hook_install is always placed in its own file named module name.install.
00:49Therefore for the windfarms module I will create a New > Empty File called windfarms.install,
00:54windfarms.install.
00:57Install files are just PHP scripts, so I will start the file with an open PHP tag and a
01:03file level doc block, Wind Farms installation.
01:10Then create a new doc block for the hook_ install implementation, Implements hook_install.
01:20Create a new function called windfarms.install.
01:25Hook_install takes no parameters.
01:31Then add an in-line comment. I'm initializing the variables in hook_install, so I'll just
01:37say that, Set default variables.
01:42Using variable_set, set the four variables with the appropriate defaults; windfarms_gmap
01:49set to 1, variable_set ('windfarms_default_center_lat') set to 42.91455, variable_set ('windfarms_default_center_long')
02:07is set to -75.569851, variable_set ('windfarms_default_gmap_zoom') is set to 8.
02:22Include a message to the user to describe what happened.
02:25Now before I do that there is a gotcha that can be slightly confusing.
02:29While the t localization function is used extensively throughout module development,
02:34there are times when the t function is not directly available.
02:37Drupal doesn't have access to the database in the early parts of the installation process,
02:42and as such there are actually multiple mechanisms available for translation.
02:47Depending on when it's invoked, Drupal requires the use of one of two translation functions.
02:52This can be bit unwieldy but there is a compromise.
02:56By using the get_t function in an ambiguous circumstance such as a module installation
03:01the contextually appropriate translation function can be used.
03:06Start with a comment, Get localization function for installation as t() may be unavailable.
03:15Assign the t variable to get_t.
03:19Using the t variable give the user feedback, drupal_ set_message ($t ('Wind Farms variables created.')).
03:35When removing a program it's common to find bits and pieces left around.
03:39This is at best annoying.
03:42Therefore it's best practice to clean up when removing a module.
03:46There is a companion hook to hook_ install called hook_uninstall.
03:51Similar to hook_install, hook_uninstall is only called once, but unlike hook_install
03:56it must be explicitly executed after the module is disabled.
04:00Start with the doc block stating that this function implements hook_uninstall, Implements
04:07hook_uninstall().
04:11Like hook_install, hook_uninstall takes no parameters; function windfarms_uninstall using
04:20the variable_del function delete the variables that were created, variable_del ('windfarms_gmap'),
04:32variable_del. I'll copy and paste from above to save time, variable_del for longitude, variable_del and the zoom.
04:54Next inform the user of the removal using drupal_set_message and using the t variable
04:58to provide access to the translation function.
05:02Inform the user of the removal, $t = get_t(), then drupal _set_message ($t('Wind Farms variables removed.')).
05:16Save, then return to windfarms.module and remove the default values, so there is a single
05:22point of entry for the defaults.
05:25First the Google Maps toggle, the latitude and longitude and the zoom.
05:41Save the module and return to the browser.
05:44Reload the page without the form submission to verify that the persistence is working.
05:49Next, go to the Modules Interface, scroll to Other, unchecked the box next to Wind Farms
05:55to disable it and click Save configuration.
05:59Now that the module is disabled you can go to the Uninstall tab.
06:04The Wind Farms module will be listed, check the Uninstall box next to Wind Farms, then click Uninstall.
06:11You'll be given a confirmation screen confirmed by clicking Uninstall. Two messages are displayed.
06:18The first is the custom message that I set about Wind Farms variable being removed, and
06:23the second is a generic message.
06:26Return to the module list, let's scroll to the bottom and enable Wind Farms.
06:34This time an additional message is shown, Wind Farms variables created.
06:39This act of customizing settings for a module from an administrative interface is a common
06:43and sometimes repetitive task.
06:46Drupal has some mechanism that will simplify this process by performing the repetitive aspects.
06:50In particular, the Submit button, saving the variables and giving a message to the user.
06:56For the purposes of this course, it's useful to show how the Drupal form API interacts
07:01which is why I'd walk you through that process.
07:04Now that the demonstration is complete, I'll simplify the form leveraging an additional
07:08function and reduce the overall amount of code.
07:12Return to the IDE and open windfarms.module, completely remove the submit handler.
07:21Next, go to the bottom of the form creation and remove the Submit button.
07:30Replace the return form with a call to another function, system_settings_form, which takes
07:38one parameter the form.
07:41This will allow Drupal to add additional form elements, save the variables and give a generic
07:46message to the user, Save, then return to the browser.
07:51Go to Configuration, then Wind Farms Settings.
07:57Notice that the Submit button now says Save configuration. This is the default.
08:02Change the zoom and some other elements and click Save configuration, you'll now see a
08:11more generic message, The configuration options have been saved.
08:15Leveraging built-in Drupal functionality the system still performs in the same manner,
08:20but with a lot less repetitive code.
08:22I'll reload the page and verify that the persistence works.
08:28There is one final touch that I will add to the admin interface, an easy link to the configuration
08:33page from the module list.
08:35Go to the IDE and open windfarms.info.
08:39Add line at the end of the file with a key configure, and the value as the path to the
08:46administrative interface that was just created, admin/config/windfarms/manage.
08:54Save then return to the Drupal site and go to the Modules list.
09:01Scroll to the bottom and a new link to the configuration page is now shown next to Wind
09:05Farms, click on it to see how it works.
09:10Throughout this chapter, the administrative interface has been created.
09:13I demonstrated the Menu and Form API, variable persistence, how to install and uninstall
09:19a module, then how to simplify the creation of an administrative interface.
09:23In the next chapter I will create and populate the new content type for storing instances
09:28of a Wind Farm.
Collapse this transcript
3. Creating a New Content Type Programmatically
Introduction to content types
00:00So far the only persistent data that I've been dealing with have been persistent variables.
00:06While useful for storing settings, they have an extremely limited scope.
00:10Web applications typically handle many different kinds of content each with its own behavior
00:15and interactions.
00:16In Drupal, a single item of content is referred to as a node and each node belongs to a single
00:23content type. Each content type has a common set of properties such as publishing options,
00:29creation dates, unique identifiers, and so forth.
00:32These properties are known as fields.
00:35For example, Drupal Core provides two content types enabled by default.
00:40Articles, which are generally used for information that is updated frequently and categorized,
00:46and basic pages which are typically used for static content.
00:50Other content types that come with Drupal core include blog entries, forms for discussions and polls.
00:57New content types can be created and managed using the Drupal admin interface.
01:01However, this can be a laborious process and doesn't typically offer custom functionality
01:06such as a triggered event.
01:08A site administrator can export content types using contributed modules such as features,
01:14but depending on the project needs this can be cumbersome and requires an additional module
01:18to accomplish something that can be done efficiently yourself.
01:21By leveraging a content type a developer can take advantage of many of Drupal's built-in
01:26mechanisms for managing content widgets for entering and editing different kinds of data,
01:31validating and rendering, easy persistence, and much more.
01:35Additionally, the content type will be compatible with contributed modules such as views, feeds and others.
01:41By creating and maintaining the content type in code, deploying site features becomes a
01:45more manageable task.
01:47I'm going to create a content type windfarm to store and display standardized data about windfarms.
01:54In particular, the content type will have six fields: the name of the facility, the description,
02:01the number of units at the facility, the wind turbine manufacturer, the latitude and longitude.
02:08I'll create some example nodes of content type windfarm then scale up by importing an
02:13existing table to populate the database. Let's get started.
Collapse this transcript
Creating a custom content type manually
00:00Previously, I demonstrated how to add persistent variables to module, then how to remove them
00:05cleanly using hook install and hook uninstall.
00:08When creating and destroying a content type the same hooks are used, which is logical.
00:13Open the IDE and navigate the windfarms. install file and navigate to hook_install following
00:19the message to the user.
00:22I'm going to define the content type itself using an associative array. Start with the
00:27variable definition named content type, Content type definition, content_type = array. The
00:37order of the keys is unimportant, but I'll use a logical progression.
00:41First, the type of content. This is a machine name. All lower case with spaces instead of underscores.
00:49In this case, I will use windfarm as the name of the content type.
00:54Next, a human readable name for the content type. I will use the T variable for the Translation
01:02function, since hook installed for a module can be called during site installation.
01:08Name is Wind Farm, following the name a brief description.
01:16A Wind Farm, including location.
01:22The next key is for the title label, title_label.
01:29This is shown on the edit page. Usually it's just title. This is used as the heading of the page.
01:34In the case of a wind farm it doesn't have a title, it has a name.
01:39Based on the data set that I'll import later, I'll use a more generic label of facility
01:43name to cover the different types of wind farms.
01:48A content type has a base which refers to the source of hooks that are used for various
01:54events such as loading and saving.
01:57Drupal provides a number of these by default with nodes. To use them, I must explicitly
02:03declare that I'm going to use node content as the base, base, node_content.
02:11Finally, there is a flag that needs to be set that indicates with this custom type was
02:16added via the add content type interface.
02:19This isn't actually true, but it gives site administrators at additional flexibility if
02:24they like to manipulate the content type, custom TRUE.
02:30There are a large number of settings for a content type, but most of them can be left
02:34on their defaults.
02:36Drupal offers a function that will go through the settings and only set the ones that are
02:39missing and I'll use that to complete the content type definition for a node.
02:45Set remaining definitions with defaults.
02:50Node_type = node_type_set_defaults which takes the parameter of the content type.
03:00Now that the base structure is complete, save the content type using the function node_type_save
03:06which takes the array structure that I defined as the parameter.
03:10Save the content type, node_type_save, node_type.
03:18Later on, I will fully define how to remove the content type and everything associated with it.
03:23For now, just save the windfarm.install file.
03:26At this time the base content type has been added with only a title.
03:31If I were to uninstall the module then reinstall the content type for wind farm would be created,
03:36but it wouldn't be very useful in that it would only have one editable field, the facility name.
03:41I'll cover adding custom fields in the next segment.
03:44
Collapse this transcript
Fleshing out a content type with fields
00:00Before describing how to add custom fields, some background is needed.
00:05Drupal 7 introduced an abstracted mechanism for handling data that made it possible to
00:10share the same Application Programming Interface for managing data structures and how the data
00:15structures relate.
00:16This API is known as The Entity API.
00:20At the highest level, an entity type is an abstract group of fields.
00:25A field is a reusable data container holding primitive data types such as text, numbers, and so forth.
00:32An example of an entity type is a node, and nodes have both the title stored as text,
00:37and a unique identifier the node ID stored as a number.
00:42An entity is an instance of an entity type, and any given entity has the same fields.
00:48For example, node ID number 12 has the title of Finn, and node ID number 28 is called Jake.
00:56Both nodes have the same fields, title, and node ID, but the values can be different.
01:02Entity types can be sub-classed as a bundle.
01:05A bundle is an entity type with a predefined collection of attached fields.
01:10Each bundle has the fields that are in the entity type along with a group of additional fields.
01:15So to continue the example, a Wind Farm is a bundle of the entity type node and contains
01:21additional fields for the latitude and longitude.
01:24When a field is attached to a bundle, it's attached as a field instance.
01:30This distinction allows a field to be attached to multiple bundles without having to duplicate the field.
01:35Currently, the wind farm content type has no fields.
01:39The title is actually stored as part of the node table.
01:42Let's add a field that contains a description of a Wind Farm facility.
01:46Both the basic page and article have a body field and many content types have a similar field.
01:52Recognizing that, Drupal has a helper function to add the body field with logical defaults.
01:58A body field is optional, so if there's no need for it, it can be emitted cleanly.
02:03Navigate to hook_install after the node_type_save.
02:07The function node_add_body_field takes two parameters; a node type and a translated field for the label.
02:15It handles the creation of the field if necessary and creates an instance to relate the fields
02:19of the content type.
02:21Add a field for the body, node_add_body_field which takes the node type, and then Description
02:34as a translated string.
02:37If I were to save right now and install these changes, I would have a Wind Farm with a facility
02:41name and description; functional but too minimal to be useful.
02:46Now comes the true custom functionality, adding the custom fields to the content type.
02:51There are two steps: the creation of the field itself, then attaching the field as an instance
02:57to the content type.
02:59I will be using the function field_create_ field which takes a standardized array definition
03:05of a field structure to create the fields themselves.
03:08While I could just call field_create_field with an array, the resulting code can get a bit messy.
03:14Therefore, I will create an array keyed by the field name with a nested array containing
03:19the field definition.
03:21At the end, I'll loop through the array, and call field_create_field with each value; Create fields,
03:30$fields = array()
03:34The fields name needs to be all in lowercase and a maximum 32 characters long.
03:38It also needs to be completely unique in any Drupal site installation.
03:43The only allowed special character is an underscore which I can use instead of a space.
03:48It's best practice to start with a content type name followed by a concise description of the contents.
03:54The first field I will add is for the number of units at a given Wind Farm; $fields
03:59'windfarm_unit_count' = array.
04:09The first key is the 'field_name' which is the same as the key; 'windfarm_unit_count'.
04:17Next, the type. Drupal provides several field types.
04:23The defaults include file, image, number decimal, number integer, number float, text, and text long.
04:33As the count is a whole number, I'll use 'number_integer'.
04:38The next key is optional; the cardinality, which refers to the number of values that
04:42can be stored in the field.
04:44The default is 1, meaning 1 value per field.
04:48If you set cardinality to the constant field cardinality unlimited, then there is no limit
04:54to the number of values that can be stored; Optional, 'cardinality' => 1.
05:03Finally, 'settings' for the data itself.
05:09A maximum length is needed for the default form elements that will be generated automatically.
05:15Settings is a nested array, so we'll set the 'max_length' to 5.
05:23That's all that is needed for a field.
05:25Next, I'll define the fields for latitude and longitude using number_float and a maximum
05:30length of 20; $fields 'windfarm_latitude' = array(, so the 'field_name' is 'windfarm_latitude'.
05:48The 'type' is 'number_float', 'settings' is an array that contains the 'max_length'
05:58of the form element which will be 20.
06:01I am going to copy the latitude, and paste it for the longitude.
06:17The last field needed is a text field to contain the name of the turbine manufacturer.
06:22I'll use a maximum length of 60; $fields
06:25'windfarm_turbine_manufacturer' = array(, 'field_name', the same, the 'type' is
06:45'text' and for the 'settings', I'll copy those, 'max_length' will be 60.
06:55Now that the fields have been defined, I'll iterate through the array and call field_create_field
07:00on each field; foreach ($fields as $field) {, field_create_field ($field). By creating
07:12the fields, there is now a place to store the data.
07:15However, there is currently no way for the content type to know about the fields.
07:19Therefore, I will create field instances for each field which will relate the field to
07:24the bundle which in this case is the content type windfarm.
07:28To do so, I'll use the function field_create_ instance which takes one parameter; a standardized
07:33array definition of an instance.
07:36Similar to fields, I will define them one at a time and iterate over the array and create each of them.
07:42I will also leave off two settings for each until the loop as they're exactly the same.
07:49Start by creating an empty array for the instances; Create Field Instances,
07:56$instances = array().
08:02I will need to create a field instance for each field that was created: windfarm's unit
08:07count, windfarm's latitude, windfarm's longitude, and windfarm's turbine manufacturer.
08:13I'll keep them in the same order that the fields were created in.
08:16Instances have a dual purpose.
08:18Not only do they relate a field to the bundle, they also provide the definitions for how
08:23the user interfaces interact with the field; $instances
08:27'windfarm_unit_count' = array(). Similar to field creation, the first thing
08:35that should be defined is the field's name; 'field_name' => 'windfarm_unit_count'.
08:44Next, a label which is user facing. The value is a string that should be wrapped in the t variable
08:50for localization.
08:52The label is used both in generated forms for editing and on the default display; $t(Number of Units).
09:04Following the label is a description of the field.
09:07This can be thought of as help text, as by default, it's only shown when editing a field.
09:14Like the label, it should be wrapped in the t variable.
09:17The description is optional but highly recommended, $t( 'Number of individual units at a given Facility').
09:27In Drupal, a widget provides the form API elements used when creating and editing content
09:33along with standardized validation.
09:36While I can define my own widgets, Drupal comes with a number of widgets that can be
09:39leveraged to save time.
09:41In particular, text which includes textfield, textarea, and textarea with summary, options
09:49including select, buttons, and on and off, then file and image.
09:54For this module, all I need is text to text field.
09:59The widget is set with an array containing a type.
10:01Other options are available such as weight for specifying the order of elements in the form.
10:06I will cover weight in an upcoming segment.
10:10So 'widget' => array.
10:14Then, the 'type' => 'text_textfield'.
10:22An optional key is required.
10:24This forces the user to provide a value.
10:26If left out, the field won't be required; 'required' => TRUE; 'settings'. Settings
10:38is a nested array, similar to the field definition.
10:41In this case, only one thing is needed; text processing.
10:45This affects several things including the widget and display.
10:49If set to 0, then the content will be treated like plain text and sanitized.
10:54If set to 1, then the interface will allow the selection of the text format like plain
10:59text, filtered HTML, and full HTML.
11:02For a number, leaving it at 0 is optimal; 'text_processing' => 0.
11:10That's all that is needed for the first instance.
11:13The next windfarms_latitude will be very similar for the first half; $instances
11:21'windfarm_latitude'
11:23= array, name is windfarm_latitude, 'label' is passed with the t variable, ('Latitude'),
11:40the 'description' => $t('Signed degrees format (DDD.dddd)'), the 'widget' => array(), containing
11:55a 'type' => 'text_textfield'.
12:03For 'settings', do an array of 'text_processing' which again I'll set to 0 as this is a number.
12:13Now that I defined the label and widget settings, I will add a new section called display.
12:19Display controls how the content type is displayed.
12:22Similar to settings, display takes an array keyed by the name of how the content is being viewed.
12:29Default is used to refer to any view that doesn't already have an explicit method for
12:32displaying the content.
12:35You can also specify something for default than something else for a particular view
12:39such as full, which is used when viewing the node itself.
12:44While it's useful to know the latitude and longitude of a Wind Farm, I want to hide it from users.
12:49Therefore, I'll set the type to hidden for all views.
12:53So display is going to be set to an array, key default is a nested array, and the type will be hidden.
13:07Longitude can be set up in the exact same way.
13:09I'll paste from the previous instance.
13:16Change latitude to longitude, and the label to longitude.
13:26Finally, the turbine manufacturer; $instances
13:29'windfarm_turbine_manufacturer' = array(, the field name which I will set
13:42to the value above, the 'label' which is turbine_manufacturer, the description which is the name of the Turbine Manufacturer.
14:02Then, the 'widget' which I'll set to array, 'type' => 'text_textfield'.
14:11I'll add an additional setting for display.
14:15A modifier that will show the label for the turbine manufacturer in-line with the value,
14:19as opposed to above, which is the default and hidden which I used previously; 'display'
14:26=> array, 'default' and array again, and the 'label' will be inline.
14:38Now that the instances have been defined, I'll set up the loop to create each instance;
14:44foreach ($instances as $instance).
14:49As mentioned before, two keys are required that haven't been set yet.
14:53The first defines the entity type, which in this case is a node; $instance 'entity_type' = 'node'.
15:03The second is the bundle.
15:04In the context of a node, the bundle is the content type.
15:09And since the content type is windfarm, the bundle is set to windfarm; $instance 'bundle' = 'windfarm'.
15:18Finally, called Field Create Instance with a standardized array; field_create_instance($instance).
15:27Save, then return to the browser.
15:30As the content type is created when the module is installed, I will have to disable the module
15:36by going to Modules, scroll to the bottom, uncheck Enabled next to Wind Farms and save configuration.
15:44Then, go to Uninstall, and uninstall the Wind Farms module, and confirm.
15:58And go back to list, enable Wind Farms, and save configuration.
16:07Then, go to Content > Add Content, and the new content type Wind Farm should be listed.
16:18We'll enter in some valid data, Caney River Wind Project for the Facility Name.
16:24For the Description, I will add the actual location which is in Elk County, Kansas.
16:31There are 111 units, and the Latitude which is at 37.448424 degrees, and the Longitude
16:43which is -96.425027, and the Manufacturer is Vestas.
16:54Save the Wind Farm.
16:58The Facility Name is used as the title of the page.
17:00The Description is shown underneath.
17:03The label for Number of Units is shown above the Content, while the Turbine Manufacturer
17:07is shown in-line.
17:10Latitude and Longitude are not shown at all.
17:13There's also a new tab visible put there by the Develop Module called Devel. Click on it now.
17:20The Devel tab just displays the contents of the nodes including the keys and any nested values.
17:26It's very useful if you want to know the node structure.
17:28For example, click down to body which has an Array.
17:32It has one key, und, which is actually the name of the language.
17:36Currently, the content of language is not defined.
17:39So Drupal refers to that as und for short.
17:43The language contains only one element which is same as the cardinality.
17:47You can see that there is a value which is how it is stored in the database, a format
17:51which defaulted to filtered _html, and a safe_value.
17:55This is what shown to the user and has been properly sanitized.
17:58There are two tabs of the Devel; the first default is Load.
18:02This is the node itself.
18:04The second, Render, is the standardized array structure that is used for displaying the
18:10contents of the node when the contents are rendered by the theme.
18:15Go back and edit the node, and put in some bogus data for the Number of Units like the word apple.
18:22Then save.
18:25A PDOException; that's pretty catastrophic and indicates that I attempted to save text
18:30in a numeric database field. Clearly, validation is needed to prevent this from happening.
18:36
Collapse this transcript
Validating user input for a content type
00:00In the previous segment, each field instance had a widget associated with it.
00:05The widget provides a form API structure that is used when creating and editing a node of
00:10that content type.
00:12As the form API was used, I can leverage Drupal system of hooks that allows modules to interact
00:17with one another.
00:19Hook_form_alter alters a form before it's rendered.
00:22Return to the IDE and open windfarms.module, and navigate to the end, start with the DocBlock,
00:32Implements hook_form_alter(), then the function; like other hooks, start with the module name.
00:44It takes three parameters: the form, passed by reference, the form_state also passed by
00:52reference, and a form_id.
00:57I want to add validation to the node edit form and I don't know the form_id, therefore
01:02I'm going to add some debugging information;
01:06dpm($form_id) and I'd like to see the structure of the form, so dpm($form), save, then return
01:15to the browser, then clear all caches.
01:20Go to Configuration > Performance, then Clear all caches.
01:27Once cleared, the hook_form_altar debugging information will be shown for every form.
01:32Return to the node and edit it.
01:38The form_id windfarm_node_form is what I'll need to manipulate the form, and now I know
01:44the form structure.
01:48Click on the form, then scroll down.
01:50The form elements are named the same as the fields which is quite useful.
01:55One of the features of the form API is validation mechanism.
01:58This is typically a callback function that processes an entire form like windfarms, admin,
02:04settings, form, validate.
02:06As the form_id is known, I can now contextualize the debugging information to only the windfarm_node_form
02:12using a switch statement.
02:14As this is for the windfarm content type, note that windfarm is singular, not plural.
02:20Go back to the IDE and place a switch statement, switch on form_id, case 'windfarm_node_form'.
02:37We can move the form debugging into the case, so it's not shown on every page.
02:43And we can remove the debugging information on the form_id because we already know it.
02:50Go back to the browser, reload the page, then inspect the form, looking for validate.
03:02Validate takes an array of function names that are used to validate the form.
03:05I can add a custom function to this array. Back to the IDE.
03:10Since form is passed by reference, I can directly manipulate it by adding the name of a function
03:15that I'm about to write.
03:17I'll start with the module name then the form_id followed by validate.
03:22This way, it'll be obvious that this function is part of this module and has a very specific purpose.
03:28Remove the debugging information from the form, then $form, #validate, and it adds the
03:37array 'windfarms_windfarm_node_form_validate'.
03:47Next, I'll add the validation function; start with the DocBlock, Validates Wind Farm content type.
03:59As before with the admin settings form, the validation function takes two parameters,
04:03the form and the form state passed by reference, function windfarms_windfarm_node_form_validate,
04:13which takes the form then the form state passed by reference.
04:20For now I'd like to see what's going to be validated.
04:23As this form is generated automatically, this structure is going to be a little different,
04:28dpm($form_state).
04:31Next, I'll intentionally set an error on the entire form.
04:35This will prevent the form from submitting and allow me to see the debugging information,
04:41form_set_error with an empty string which sets it to the entire form, and then testing is the message.
04:48Save, then return to the browser and submit the form.
04:58This time, you'll get a generic error message, the DEVEL array contains the form state.
05:05Look for values, windfarm_unit_count is in there, but look closer, und is shown.
05:15This is an artifact of the field definition and automatic form generation.
05:20Look deeper into und and you'll see an array with one key followed by value.
05:26This is what I'd like to validate.
05:28Go back to the IDE and remove the debugging information.
05:34Start with a comment, Validate Unit Count.
05:37Remember, it's in values, windfarm_unit_count, instead of using the string und, use the Drupal
05:45constant language, none.
05:47This is a best practice and makes it clear what's going on.
05:50Since there's only one element in the array, due to the cardinality, just take the elements
05:54by index 0, then the value.
05:58This complication in nesting is the cost trade- off by leveraging Drupal's internal facilities
06:02for creating a content type.
06:05Next, validate the value.
06:08If the value is not empty and, start parentheses, is not numeric, or the intval of value not
06:26equals value, or value is less than equals to zero, set an error on the windfarm_unit_count
06:38element, form_set_error 'windfarm_unit_count', then the t function and the explanation 'The
06:49number of units on a wind farm must be a positive whole number.'
06:58Save and return to the browser.
07:00We'll close the debugging information, put in some bad data for the number of units,
07:08and resubmit the form.
07:10This time a contextually appropriate message is shown and the Number of Units form element is highlighted.
07:16Good! Now I can add the validation for the latitude and longitude.
07:20Go back to windfarms.module.
07:24I've already created a regular expression for validating signed degrees, but at this
07:28point, I need to use it in two places.
07:31I could copy and paste, but that's really not a good idea.
07:34If I find myself about to duplicate code with minimal changes, I'll create a function instead.
07:40Start by creating a function that starts with an underscore.
07:44This visually indicates to other developers that this function is intended to only be
07:48used internally by this module.
07:51Follow the underscore with the name of the module, underscore then what the function
07:55is supposed to do, validate_signed_degrees.
07:58Take one parameter, value.
08:01The regular expression can now be copied and pasted without regret.
08:06I'm going to copy the signed degree regular expression, and paste
08:13it in to the new function, then I'm going to return, preg_match the signed_degree_regex, and the value.
08:24I'm going to use a ternary statement as preg_ match returns an integer, so TRUE: FALSE.
08:32Now that the function is defined, include proper documentation.
08:38This will determine if a value is in signed degrees format.
08:44This takes a string, the value, and then the explanation, The value to be tested, and then
08:54this returns Boolean, TRUE if correctly formatted.
09:01There is a second set of validations for the geographic coordinate range between negative
09:07180 and 180, function _windfarms_validate _geo_coord_range which takes a value.
09:21We can use the same logic from above.
09:27Return, I'm going to paste from above.
09:30I'm going to replace latitude with a value, again add the DocBlock, Determine if a geographic
09:43coordinate is within the valid range.
09:49Takes a string, called value, which is The value to be tested, and it returns a boolean
10:00which is TRUE if between -180 and 180.
10:06Before I forget, I'll refactor the admin section to use the new validation functions and remove
10:11the unused declarations.
10:14So I'll start with the signed degrees.
10:16I don't need the regular expression anymore, and we'll use the new function.
10:23I will also use the new function for at the latitude and longitude values, windfarms,
10:31the range on the lat and on the long.
10:43Now that the admin validation function is cleaned up, I can move on to validating the
10:47latitude and longitude provided by the user.
10:51Go back to the node_form_validate function, start with the comment, Validate latitude.
11:02Similar to the unit count, remember to use LANGUAGE_ NONE, then index zero then the value, $lat = $form_state
11:10'values' 'windfarm_latitude', LANGUAGE_NONE, 0 and 'value'.
11:22I'm going to combine the two arrow checks into one.
11:24If either fails, set the error in the form; if not, windfarms geo coordinate
11:31range for the latitude, or not windfarm_signed_degrees, lat then form_set_error, and the key 'windfarm_latitude'
11:47with a message, using the t function 'Latitude must be valid coordinates in signed degrees'.
11:59The longitude check is same as latitude, only the names have been changed to protect the innocent.
12:03I'm going to copy and paste from the latitude because it's very, very similar, validate,
12:10longitude; change the key to longitude and the variable.
12:22So if not the range of the longitude, or the signed degrees of the longitude, then form_set_error
12:30on the longitude, then replace 'Longitude must be valid coordinates in signed degrees.'
12:38Now that validation is complete, ensure that all debugging information has been removed,
12:42then save and return to the browser.
12:45Attempt to break the form submission with bad values; orange, puppy, and hit Save.
12:59You'll now get contextually appropriate error messages.
13:02The node form is validating which is a good thing.
13:05I still need to add the logic necessary to cleanly remove the new fields, instances and
13:09content type upon uninstall.
Collapse this transcript
Removing a content type
00:00Let's continue with the best practice of leaving a clean slate upon uninstall by removing the
00:05content type that was created.
00:07If I just delete the content type, it doesn't take into account any fields or instances I've added.
00:12So those should be removed cleanly as well.
00:15Finally, if I remove the content type, the existing nodes will be orphaned and will really
00:19mess up functionality.
00:21Therefore, it makes sense to delete those nodes as well.
00:24I'll start by removing the existing content of type windfarm.
00:28To do that I'm going to leverage and Drupal function called node_delete_multiple which
00:32deletes a group of nodes based on array of their node IDs.
00:36I'll assemble that list using a database query.
00:40One of the features and Drupal 7 is a database abstraction layer which should be leveraged
00:44whenever dealing with user input.
00:46This layer unifies the interactions with different database servers and provides a structured
00:51interface for building queries, providing security, and other best practices.
00:56Interactions with a full object-oriented database abstraction layer will be covered
00:59in a later chapter.
01:00In this particular circumstance it's a cannon to kill a fly.
01:04I just need a list of all node IDs of type windfarm and no user input is required.
01:09Therefore, it's acceptable within Drupal coding standards and in this circumstance to use
01:14db_query which executes an arbitrary query string against the database connection while
01:18still leveraging the database abstraction.
01:21In the IDE, open windfarm start install and navigate to hook uninstall.
01:26I'll start with a comment describing what I'm about to do, Get all node IDs with windfarm content type.
01:35Next, define the query itself.
01:38Nodes themselves are stored in a database table called node and their primary key is
01:42NID short for Node ID.
01:45All we need is the Node ID.
01:46So I will select only that;
01:49$sql_query = 'SELECT nid'. Next, the from.
01:55One of the things that db_query does is perform string replacement in conjunction with the
01:59database settings.
02:01If for example, a database configuration had prefixed all tables with Drupal_ then "FROM
02:07node" wouldn't work.
02:09Therefore, table names are wrapped in curly braces; db_query will in turn replace those
02:14curly braces with whatever table prefix is necessary, and strip out the braces.
02:19So the end result will look like a standard SQL query.
02:22Nodes are stored in the node table.
02:24So $sql_query .= 'FROM {node}';.
02:30Context is needed for the query.
02:32In particular, the content type should be specified as a filter.
02:36The type name is stored by machine name in the node table under the column type.
02:41In addition to replacing the curly braces with proper table names, db_query also performs
02:46variable replacement.
02:47There are two formats for placeholders for variable replacement, named and unnamed.
02:53Best practice is to always use named placeholders as they're more flexible and provide context.
02:59Named placeholders always start with a colon, followed by letters, numbers, and underscores.
03:05Unnamed placeholders are just question marks.
03:07This works, but if you have a lot of placeholders this gets confusing, and therefore isn't recommended.
03:14When using a placeholder no quotes are necessary.
03:17$sql_query .= 'WHERE {node}.type = :type';.
03:25Finally, assign the variable result to db_query.
03:30The first parameter is the query string itself followed by an array of arguments that are
03:36keyed by the placeholder name with a value of what we would like to filter.
03:42Db_query returned to prepare statement object that has been executed.
03:46These objects can be iterated through like an array which is quite convenient.
03:50Create an empty array to hold the node IDs;
03:53$nids = array(). Iterate through the result.
03:57Results will return standard objects which relate each row returned from the database query.
04:02So foreach ($result as row).
04:06As I selected only the node ID, known as an nid, all a need to do is add that value to
04:11the nids = $row->nid;.
04:17Now that the array of node IDs has been prepared it can be passed to node_delete_multiple.
04:23That function doesn't return anything so it's okay just to call it on its own, Delete all
04:29windfarm content.
04:32Node_delete_multiple ($nids); and inform the user of what just happened.
04:39Drupal_set_message($t('Wind Farms content removed.'));.
04:48Now that the content is deleted, let's get rid of the fields and field instances associated
04:53with the content type.
04:54Start with the logical comment, Removed all fields and field instances.
05:00Next, I'll call field_info_instances which gets all field instances of associated with
05:06the entity type and bundle.
05:09Field info instances takes two parameters: the entity type, and the bundle name which
05:14in this case is node and windfarm.
05:17The instances are returned as a nested array known as the instant structure keyed by the field name.
05:23So I'm going to iterate foreach (field_info_instances( of 'node' and 'windfarm' to be specific, as $field_name => $instance);.
05:37Before I get rid of the instance I'll delete the field as well.
05:41I will use method field_delete_field which just takes one parameter, the field name.
05:47Field_delete_field($field_name);.
05:52Then field_delete_instance, just takes an instance structure which field info
05:58instance returns.
06:00Again, let the user know what just happened.
06:03This is unnecessary in most contexts, but it's good for testing and development.
06:07Drupal_set_message($t('Wind Farms field and field instances removed.'));.
06:19The fields and instances have been removed in one loop.
06:23Now that the content, fields, and field instances have been removed, the content type itself can be removed.
06:29After everything that has been done, the actual removal of the content type itself is a one
06:34line call function node_type_delete which takes one parameter, the name of the node type.
06:42Delete the content type.
06:45Node_type_delete('windfarm'); then provide a message to the user; drupal_set_message($t('Wind
06:59Farm Content Type removed.'));.
07:05One final step remains and this is more of a regular maintenance.
07:08When deleted, a field actually persists for little while until it can be cleanly removed
07:13by a regularly scheduled cron.
07:15This can be accelerated especially when the removal is on-demand.
07:19The function field_purge_batch takes one parameter, the size of the batch, and can be used to
07:26remove these deleted fields.
07:28Clean up deleted fields and then we'll call field_purge_batch with the batch size of 1000.
07:37Save the file then return to the browser.
07:42Go to the Modules list, then disable Wind Farms, Uninstall the Wind Farms Module, and confirm.
07:55The page now displays messages stating that everything has been removed relating to the
07:59Wind Farms module.
08:01Go to the Content tab and the Wind Farm Content is now missing.
08:05This is a good thing.
08:06All traces of the wind farms module have been removed from the system cleanly which eliminates
08:11the possibility of strange behaviors later with half broken references to missing code and content.
08:17Go back to the Modules list and re-enable the Wind Farm content type as we'll be needing it later.
08:25In this chapter, I introduced content types, created a custom content type, added fields,
08:30and field instances through the content type, validated user input, then cleanly removed
08:36all data relating to the custom content type.
08:38Next, I'll explore how to build upon this foundation leveraging third-party modules
08:43and demonstrating new areas of Drupal functionality.
08:47
Collapse this transcript
4. Extending the Module Functionality
Importing content using feeds
00:00One of the problem domains I encounter often is the need to import content from a data
00:05source into Drupal.
00:06The United States Department of Energy created the Open Energy Information initiative or
00:11OpenEI as a platform to connect the world's energy data.
00:15Licensed under a Creative Commons Public Domain one of the many collections of open data they
00:19publish is a database of wind farms in the United States.
00:24As this module facilitates the creation of a database of wind farms, I'm going to use
00:28the freely available CSV to import data.
00:31I've trimmed the data set a bit to include only fields that are supported in this content type.
00:36One of the many advantages of Drupal is the open platform for developing and sharing content,
00:41and functionality, and importing data is one of those areas.
00:45The Feeds Module imports and aggregates data as nodes, users, taxonomy terms or even simple
00:51database records.
00:52Out of the box it supports RSS feeds, CSV files, and much more, and these imports can
00:58be performed as one-offs or unscheduled regular intervals.
01:02This particular demonstration is not going to require any custom development for the
01:06importer which may seem a little counterintuitive given the name of the course.
01:10The point is that while I can do something manually, when somebody else has built and
01:14maintained a great wheel that tens of thousands of other people use on a daily basis, there's
01:19no reason to reinvent it.
01:21Feeds does have some requirements.
01:23Download and extract the following modules if they're not already installed.
01:27Feeds which is the base module, ctools which is required by feeds; it's also a great development library,
01:34and job_scheduler which is required by feeds, and facilitates the scheduling of imports.
01:40When they are available go to the Module list, and enable Feeds Admin UI.
01:51The required modules will be enabled as needed.
01:53Now that feeds is enabled, I need to setup the mechanism for importing data.
01:58Go to Structure then Feeds importers.
02:02Each Feed importer can be used to define how data is handled upon import, including what
02:06factor will be used, mapping, standards, and so forth.
02:10Click Add Importer, for the name I'll use OpenEI Wind Farms, for the description CSV
02:20Import of OpenEI Wind Farm Database, click Create.
02:27The first thing that needs to be configured is the basic settings which defines general
02:32behavior of the importer, click Settings, disable Periodic Import by selecting Off then click Save.
02:42Next, configure the Fetcher which gets the data from a source.
02:46There are two options right now; File upload and HTTP Fetcher, switch to a File upload and click Save.
02:55Then change the Parser.
02:58By default Feeds will parse RSS and Atom Feeds.
03:02Switch to the CSV parser, then click Save.
03:06By default, the Processor used will be the Node processor which creates and updates nodes
03:11from the parsed content.
03:13This is what I want, so no change is needed.
03:15Go to the Node Processor Settings, under Content type select Wind Farm, then Save.
03:24Finally, define the Mapping.
03:28This will define the mapping of the CSV headers to the content type fields.
03:32On the left, the Source is the CSV header name, and on the right the Target is the content type field.
03:40So we'll add them one at a time.
03:42So the first will be Facility Name, I will map it to Title which is the default name
03:49of the node title.
03:50In the content type I named this Facility Name, but this interface doesn't check that
03:55deeply for, click Add.
03:58Next, Facility; I will map this to the Description, just so there's some content.
04:09NumberOfUnits, which I will map to Number of Units.
04:19Latitude, maps to Latitude.
04:25Longitude, maps to Longitude.
04:32And finally, WindTurbineManufacturer. This maps to the Turbine Manufacturer, click Save.
04:45That was fairly laborious, wasn't it?
04:47Fortunately, this is a one time thing.
04:49When I said that custom development wasn't needed I was telling the truth.
04:53I'm not writing an importer, however, code will be involved.
04:57One of the tabs on this page is Export.
05:00This generates PHP code that can be embedded in a module that will programmatically create
05:05the feed importer when that module is enabled.
05:08Click on Export now, Select All and Copy.
05:14Go to the IDE and open windfarms.module, scroll to the end, then create a doc block.
05:26Modules can make hooks available to other modules and feeds is no exception. To provide
05:30a default feeds importer, I need to implement hook, feeds, importer, default.
05:38Implements hook_feeds_importer_default(); function windfarms_feeds_importer_default(),
05:52which takes no parameters.
05:54Feeds exports into a variable called Feeds Importer.
05:58Hook_feeds_importer_default returns an array of these feeds.
06:01Start with a variable called export with an empty array.
06:06$export = array().
06:09Paste the contents of the export in.
06:13Then indent it, so it lines up cleanly.
06:17Remove the long comment about disabling.
06:21At the end, add the feeds importer to export with the key that matches the machine name
06:27of the exported feed, which can be found in the feeds importer ID property.
06:32In this case, openei_wind_farms; $export 'openei_wind_farms' = $feeds_importer.
06:47Then return $export. Before this will work, Feeds needs to know that this default exists.
06:58Ctools has a plug-in API that feeds leverages.
07:01So the Wind Farms module needs to let ctools know that it's providing defaults.
07:10Implements hook_ctools_plugin_api(); function windfarms_ctools_plugin_api().
07:23The API takes two parameters: the module and the API both strings and both sent to the empty string.
07:31($module = '', $api = '').
07:36Add some logic. If the feeds module and the API question is the Feeds importer default,
07:43so if ($module == 'feeds' && $api == 'feeds_importer_ default') then you tell ctools that the API version
07:59is 1; return array ('version' => 1). Save then return to the browser.
08:12Go to Structure then Feeds importers, the status is now overridden.
08:18If it's not, you'll need to clear the cache, click Revert to go back to the version stored in code.
08:27The importer now is using the configuration stored in the module as indicated by the status default.
08:33Now that the windfarms module depends on feeds, that information needs to be made available to Drupal,
08:39so we can require feeds for enabling Wind Farms.
08:42Go back to the IDE, and open the windfarms.info file.
08:46While feeds requires several other modules to function, Wind Farms explicitly depends
08:51on feeds, and feeds can handle its own dependencies.
08:55Drupal is intelligent enough to enable everything required if you just enable windfarms.
09:00Add a line to the end of the info file.
09:02Dependencies can contain multiple values so using this standard INI format for arrays,
09:07set the key as dependencies and the value to fields, Save.
09:15All the groundwork is complete.
09:17The windfarms module will now import, and the configuration is now stored in code.
09:22Go back to the browser, then click on the link to the Import page.
09:27There should be an option OpenEI Wind Farms, click it now.
09:32Go to File, and Choose File.
09:39Browse to the location of the windfarms. csv file, then click Open and Import.
09:48The Status Message indicates that 948 nodes are created.
09:53Return to the homepage.
09:56Returning to the homepage, the latest wind farms to be imported are now shown.
10:01Click on one of the imported items.
10:05As it looks now, it's not very interesting.
10:08In the next segment, I'll add a Google Map of the facility which will be much more useful.
Collapse this transcript
Adding a Google Map using theming
00:00I imported wind farms from the Open Energy Information Initiative and I can now view
00:05each node of content type wind farm.
00:08I hid the latitude and longitude because there was no need to show it to the user in this context.
00:13However, this view is uninteresting.
00:16Having a map with a marker indicating where the wind farm is has a positive impact on the user.
00:21To do this, I'm going to inject an element into the full view of the node and make a
00:26custom function for displaying the element.
00:29By providing this functionality within the custom module, I am allowing themers to override
00:34or customize the base functionality I provided.
00:37I will not be theming or designing the look and feel of the entire node view. That's out
00:43of the scope of this course, and we'll be better explored in depth on its own.
00:48I'm going to add a map using the Google Maps JavaScript API Version 3 which allows developers
00:53to embed Google Maps into web pages.
00:55It's fast, flexible and free for websites that are free to consumers.
01:01However, I don't want to spend the time learning all the ins and outs of the JavaScript API
01:06as I want to focus on delivering features.
01:08I search the contributed modules and found GMap3 Tools, a module that allows a developer
01:14to quickly add a Google Map by providing an API made explicitly for Drupal.
01:20It's minimal and functional so it fulfills my needs.
01:23Open the IDE and then go to windfarms.module and navigate to the end.
01:30The first thing I will do is implement hook_node_ view, which acts on the node that is being assembled
01:36before rendering, hook_node_view, function windfarms_node_view takes three parameters,
01:49the node, the view_mode and the language code.
01:55This function will be accessed on every node view, so unless I give it context, it's going
02:00to try to put a map on every page.
02:02I only want to put the map on the full view of the windfarm content type.
02:07If ($node-> type == 'windfarm' && $view_mode == 'full'.
02:18When rendering a node, Drupal builds a structured array keyed with the element name.
02:23This markup is in the same structure as the form API and is placed in the content property of the node.
02:30I'm going to create a new element called windfarm_ gmap in the content property, $node->content
02:37and as this is a structured array, I will access it by key 'windfarm_gmap' = array.
02:47I'm going to use hash tag markup to indicate that the contents of this element are just
02:52pre-rendered HTML.
02:54I'm going to call the theme function to create the contents.
02:58The theme function generates themed output by examining what was requested and determines
03:03what is the right theming option or template.
03:07Theme takes two parameters: the name of the hook, then an optional array of parameters
03:11that will be passed to the theming target.
03:14I'm going to create a theme function for windfarm_ gmap, so I'll stick with the name, and I'll pass
03:19the node itself to the theme function with key node.
03:23Weight is used to determine the order in which something is displayed. The higher the weight,
03:28the heavier it is and heavy things sink.
03:31Weight is used in the form API and menu API among other places.
03:35In this context, I'm going to use weight to ensure that the Google Map is the last thing
03:40to be shown by setting it to the maximum 100, so #weight is 100.
03:48The next thing I need to do is tell Drupal about the custom theme function and the arguments it takes.
03:54I'll do that by implementing hook theme which registers the custom themes with Drupal.
04:07Hook_theme takes four parameters: function windfarms_theme, existing, type, theme and path.
04:19I won't actually need any of them for what I'm doing, but as they required by the hook,
04:23I must define them.
04:25Hook_theme returns an array keyed by the name of the theme function and some options.
04:30I need to specify that variables will be made available to the theme function, so I'll provide
04:35the structure; return array containing windfarm_ gmap and a nested array, specify the variables
04:49which will just be the node which will default to null.
04:57Now that the hook_theme has been implemented, let's create the theme function itself.
05:02Theme functions don't implement anything,
05:04hooks are otherwise. They just return a string with the output.
05:07Start with a doc block, Wind Farm Google Map theme function.
05:15By naming convention, theme functions must start with theme_windfarm_gmap which takes
05:25variables the array that was created earlier.
05:29Let's get some introspection into variables, dpm($ variables), save to file, then go back to the browser.
05:39Drupal's theme registry, the internal structure for determining what themes what, is heavily
05:43cached to improve site performance, therefore the cache will need to be cleared in order
05:49to see the changes.
05:50Go to Configuration > Development and click on Performance, so you can clear all caches.
06:00Go back to the homepage and view one of the Wind Farms nodes.
06:07Now there's debugging information shown at the top of the page as an array with one element node.
06:14The title is shown as is, but the body, latitude and longitude are the nested arrays with the
06:20language undefined.
06:23Now that I know what the structure of the node is, I can access those elements to add the Google Map.
06:29Return to the IDE and remove the debugging information.
06:34The first thing that needs to be done is to check if the Google Map has been enabled in
06:37the admin interface. If not, just return. Check to see if the Google Map is enabled;
06:48if not variable_get and the name is windfarms_gmap; if not, then just return.
06:58Next, I'll check to see if I have the right information needed to display the map. In
07:03particular, both latitude and longitude are needed, so $lat = $variables 'node'. I'm
07:12going to access windfarm_latitude, LANGUAGE_NONE, 0 and then 'value'.
07:22The longitude will be just about the same, add a comment, Cannot render map without
07:35both latitude and longitude, if lat is equal to the empty string or long is equal to the
07:49empty string, then return.
07:54Now that I'm confident that I have all the context needed, I can start building the map
07:58using the GMap3 Tools module.
08:01Three basic steps are needed.
08:03First, load the include file, second create the map with the configuration array, then
08:10third, add the element that will be populated with the map by ID.
08:15First, I'll include the module using the Drupal function module_load_include, which loads
08:21the module include file. It takes the file extension, the name of the module and optionally
08:26the base filename.
08:28So module_load_include, and let's include file and module is gmap3_tools.
08:39I'm going to need two more things from the windfarm node, the facility name and the description
08:44which I will use to populate the marker on the map.
08:47As this is user supplied input, I don't trust it, as it may contain content other than plain
08:52text, which will at best possibly break the map and at worse, contain malicious code.
08:59Drupal provides mechanism for encoding special characters in a plain text string for HTML
09:03display using the function check_plain which takes the string as a parameter and returns
09:09the sanitized result, $facility_ name = check_plain ($variables 'node' -> title);
09:22$description = check_plain (variables 'node' -> body, LANGUAGE_NONE, 0 and then the key 'value'.
09:35Next, I'll call a function from the GMap3 Tools library to add the map via JavaScript.
09:42It takes a configuration array which I will define in-line and doesn't return anything.
09:47There's no need to learn all of the options and functions for the GMap3 Tools library
09:51as I'm using it as an example.
09:54Different modules have different API implementations gmap3_tools_add_map, then I will create an empty array.
10:05First, I'll set the mapID which is the name of the element that will be populated using
10:10JavaScript. I'll set it to 'gmap-canvas-' $variables 'node'->nid,
10:20which will allow multiple maps to be shown at one time.
10:23Next, I'll set the mapOptions.
10:27This is an array.
10:29I will set the zoom from the variables set from the configuration page variable_get
10:39('windfarms_default_gmap_zoom').
10:44The next one will be the mapTypeId where I will specify the use of a satellite map rather
10:52than a roadmap, so the windfarm can be actually seen, GMAP3_TOOLS_MAP_TYPE_ID_SATELLITE.
11:02To add a marker to the map, set markers to an array populated with a helper function
11:09gmap3_tools_create_marker, which takes four parameters, the latitude, the longitude, the
11:19title, which I will set to the facility name and the content which I will set as the description.
11:27The last configuration is the default position of the markers in gmap3ToolsOptions, defaultMarkersPosition,
11:41which I will set to GMAP3_ TOOLS_ DEFAULT_POSITION_CENTER.
11:50Now that the JavaScript has been injected, specify a target for the map. Use the same
11:57identifier as we specified in gmap3_tools_add_map, 'gmap-canvas-' . $ variables 'node' ->nid.
12:10I'll set the width to 500 pixels and the height to 400 pixels and then close the div.
12:21Finally, return the theme output, save, then return to the browser and refresh the page.
12:32The Google Map has now shown with a marker, hovering will show a title and clicking will
12:38show the description.
12:40Now that I've added a Google Map to the node view, I'd like to create a map that shows
12:44a number of wind farms on a map that allows you to get more information about them.
Collapse this transcript
Creating a block
00:00A Drupal block is an area which displays content whose position is controlled by the theme
00:05and administrative block settings.
00:07I'm going to programmatically create a block that shows a map of wind farms that are within
00:11100 miles of the center specified.
00:14To help us some of the computation I'm going to leverage a library that is within the location module.
00:19There is no need to actually enable the location module to do this, but it does need to be there.
00:25Open windfarms.module and scroll to the end.
00:29Two functions are needed to generate a block and I will use a third to keep the code tidy.
00:34The first function that's needed is an implementation of hook_block_info.
00:39By implementing this hook, I can declare to Drupal that a block with a particular machine
00:43name and human readable administrative name exists.
00:47Start with a docblock, Implements hook_block_info.
00:53Hook_block_info requires no parameters and returns a nested array, function windfarms_block_info, blocks = array.
01:07Hook_block_info requires no parameters and returns a nested array.
01:12The array is keyed by the delta value of the block which is a fancy way of saying the machine name.
01:18Block names are unique to the module, so you don't have to worry about collisions.
01:22I'll create a block with the delta of gmap, blocks gmap is equal to an array.
01:32For each delta, define an array containing one key named info.
01:37This will contain a translated string of the administrative name for the block.
01:43This is shown on the block admin interface and nowhere else.
01:46I'm setting the info to Wind Farm Map.
01:51Finally, return the blocks array.
01:56Next, I'm going to implement hook_block_view which returns the rendered contents of a block
02:02as an array with the two elements: the subject which is the localized title and the content.
02:08The content can either be a string or a renderable array which is similar to the farms, start
02:13with the docblock.
02:16Implements hook_block_view, hook_block_view takes one parameter, function windfarms_block_view
02:27takes one parameter, the delta of the block in question defaulting to an empty string.
02:34As it returns an array, create a variable named block, initialized with an empty array
02:40and then switch on the delta, switch ($delta) and then a case for gmap.
02:52The localized subject is displayed at the top of the block and can be overridden using
02:56the Drupal admin interface.
02:58If no title is needed, the subject is still required but it can be set to null.
03:04This case I'm going to set the $block 'subject' to the translated string Wind Farm Map.
03:13To keep things neat, I will generate the content of the block by calling a custom function
03:17that just returns renderable content and doesn't implement any hook, $block
03:22'content' = windfarm_block_contents, and the ($delta).
03:31After the end of the switch block return the block.
03:38The final function remains, windfarm_block_contents.
03:42I'll create the function first, function windfarm_block _contents, that takes one parameter the ($delta) and
03:52then I'll add the docblock, Wind Farm Block contents for the description, a parameter
04:01string the $delta, which is The block ID.
04:09Then return a string containing the HTML output.
04:15Initialize the output, then switch on the ($delta) and add a case for gmap.
04:27As mentioned before, I will be using a library within the location module to handle some
04:32of the computations required.
04:34While I could theoretically copy and paste the functions, this causes several problems.
04:39First, copying and pasting makes it look like I wrote the functions which I didn't.
04:44Second, the library can be updated and the enhancements would not be reflected in my
04:49code, and third, by mixing context the code structure can get concluded leading to what
04:55is referred to as Spaghetti code, where the logic is a big sloppy mess.
05:00I'm going to use module_load_include again, specifying include in the location module,
05:08in particular the earth file.
05:12The function I'll be using operates in meters, so I'll convert the distance 100 miles into meters.
05:18100 miles, $distance_meters = 100 * 1609.34.
05:26Next, I'll determine the range of the latitude and longitude from the default center.
05:32This is a simplified version of a proximity search which looks in a square, rather than
05:36a radius around a point.
05:38Why simplify? For two reasons, first it's fast and it has enough precision for an intention grabber.
05:45And second, this course is about programming modules not geo-positioning calculations.
05:52Determine the range of lat and long from the default center.
05:58I'll use the default center latitude and longitude from the variables set from the admin interface,
06:05$lat = variable_get ( 'windfarms_default_center_lat').
06:12I'll copy and paste and set that to long, and long.
06:19Next I'll calculate the range using the Earth Library.
06:23So the $range_lat = earth_latitude_range, takes the long and lat and the distance will
06:33be actually distance_meters.
06:37I will do the same for the longitude with earth_longitude_range.
06:44Now that I know where I'll be looking it's time to build the query.
06:47While the last time I needed nodes I used DB query, but this time DB query isn't appropriate.
06:54Drupal 7 introduced the EntityFieldQuery API which allows for queries of entities such
06:59as nodes to be built efficiently, avoiding the need to know the complete internals of
07:04every generated table and column.
07:06This API is used in an object oriented manner rather than procedural.
07:11To start, create a new entity field query object.
07:16Build_query then $query = new EntityFieldQuery.
07:25Context is needed and to filter the query I will use what are known as conditions.
07:30In the end, these conditions will result in where statements with joints as necessary.
07:35The method entity condition adds a condition on entity generic metadata such as the entity
07:40type, bundle, revision ID or entity ID.
07:45Entity conditions take two required parameters, the name of what you want to filter and the
07:50value that you are filtering on.
07:52A third parameter, the operators such as equals, less than, and so forth, is optional and defaults to equals.
07:59I want to only return nodes, which is in the entity type, so Only_show_nodes.
08:07So $query takes entityCondition, the name is entity_type and the value is node.
08:17Next, I will filter further and only show the bundle windfarm.
08:22Only_show_windfarms, $query-> entityCondition, the name is bundle, and the value is windfarm.
08:34Now that I've filtered down to the node of content type windfarm, it's time to find only
08:39wind farms within the latitude and longitude range that I calculated earlier.
08:43To do so I will use the method fieldCondition.
08:47Similar to entityCondition, the first parameter is the name of what I want to filter on, which
08:52in this case is the field's windfarm latitude.
08:55The second parameter is the column that I want to filter on.
08:59Similar to when I was displaying the map the value of the latitude is stored in latitude.
09:05Following the column is the value where the comparison will be made too.
09:09I will be checking to see if the latitude is within a range, so I will pass it the array
09:13containing that range.
09:15If I was filtering on a single value then I could just pass that value rather than putting it in array.
09:21Finally the optional operator, as I'm checking a range I will use the keyword between, Only
09:28show latitude between range, query fieldCondition, which takes the field windfarm latitude, then
09:40that value $range_lat and then the keyword between.
09:48The longitude fieldCondition is constructed in a very similar manner, so I'll copy and
09:53paste, Only show longitude between the range and the windfarm_longitude, it will take the $range_long.
10:05Now that the query has been built, I can execute the query by calling the method execute.
10:12Execute query, so $result = $query->execute.
10:19Following execution an array of associative arrays of stub entities will be returned,
10:23keyed by the entity type on the outside and relevant entity ID on the inside.
10:29Check to see if there are any nodes, if not, return an empty string.
10:33No results, if isset result keyed by the entity type, which in this case is node,
10:44then return the empty string.
10:47If there are nodes then load them all.
10:49To do that, I will use node load multiple, which takes an array of node IDs and returns
10:56an array of node objects, $nodes = node_load_multiple.
11:01I'll use the array_keys of result node.
11:08Now that I have all the nodes for the windfarms that match, I can build the Google map using
11:13gmap 3D tools, same as the theme I will use module_ load_include to load the include file gmap3_tools.
11:27Then create an empty array of markers, $markers = empty array, foreach nodes as node get the
11:37latitude and longitude, so the $lat = $node->windfarm_ latitude, LANGUAGE_NONE, 0 and then the key of 'value'
11:54and the long which is windfarm_longitude.
12:00If either the latitude or longitude are missing, skip the entry.
12:04While it's highly unlikely, zero latitude and zero longitude is a legitimate coordinate.
12:12Cannot render map without both.
12:15If $lat == empty string or $long == empty string, continue.
12:30If it passed validation get the facility name which is the node title $facility_name = check_plain($node_title).
12:42Then get the description, $description = check_plain($node-> body, LANGUAGE_NONE, index 0, and the 'value'.
12:56I'm going to add a link to the node.
12:59While it may be tempting to write a straight anchor tag in HTML, Drupal provides a clean
13:03mechanism for inserting links that integrates with a menu routing system.
13:08The function is just L for link.
13:11This means that you can just pass a raw path for a node, and if you have an alias path
13:15defined for the node, the L function will convert it transparently.
13:19L takes three parameters, but only the first two are required.
13:23First, the text of the link for the anchor tag, then the internal path being linked to.
13:30Add a link to the node.
13:33$description concatenate equals a space followed by the L function, the first part is the label
13:40or info followed by the path node which is node/ and then the node ID.
13:50Finally, create the marker and add it to the array.
13:53Add to markers, $markers, I'm going to append gmap3_tools_create_marker with a latitude,
14:02the longitude, the title is the $facility_ name and the description is the $description.
14:11When the markers are created, I will call gmap3_tools add map again, but this time the
14:15options will be a little different.
14:18Create map with all the markers, gmap3_tools_add_map with an array.
14:31Instead of a dynamic mapID just specify gmap-canvas-block.
14:40The satellite view will be just fine for the map options, mapOptions set to an array and
14:48the mapTypeID set to GMAP3_ TOOLS_MAP_TYPE_ ID_SATELLITE.
14:58Instead of defining the marker in line, just pass the markers array.
15:02So markers set to $markers.
15:06I will use a different option for the default markers position which will automatically
15:10zoom to encompass all the markers on the map.
15:13So gmap3ToolsOptions set to array, defaultMarkersPosition which is set to
15:26GMAP3_TOOLS_DEFAULT_MARKERS_POSITION_CENTER_ZOOM.
15:36Finally, the only HTML that will actually be returned by the block function is the HTML
15:41container for the map.
15:46Set the ID to the same thing as the map ID above, so $output = 'div id="gmap-canvas-block".
15:57The style is the width 500 pixels and a height of 400, and close the div.
16:05Then end the switch statement, then at the end return the $output.
16:14Save then return to the browser.
16:16Now that the block has been completely defined enable it in the admin interface.
16:21Go to Structure then Blocks.
16:27At the bottom, in disabled, Wind Farm Map is now shown. For the REGION set it to Content.
16:37Then arrange the content so Wind Farm Map is shown first, click Save blocks.
16:43The block will be shown on every page above the content which is a little disconcerting
16:47if you're looking at a wind farm or another node.
16:50Therefore set it only show on the front page.
16:54Click configure, only on the listed pages and <front>, click Save block, go to the homepage.
17:09The Wind Farm Map is now displayed on the front page using a block populated by the
17:13database using entity field query.
17:16In this chapter, I've created a feed importer that creates wind farms from a CSV, added
17:22a Google map by integrating with another module and theming, then created a block that contains
17:27a map filled with the results of the geographic search of the custom content.
17:32At this point, the base functionality of the module is complete.
17:36The next question, where to go from here?
17:38
Collapse this transcript
Conclusion
Exploring best practices and coding standards
00:00When developing modules, regardless of the audience, there are number of standard best
00:04practices that one should be aware of.
00:07First, I recommend using some sort of source revision control software. That way, code changes
00:12can be tracked over time, improving collaboration and accountability, providing a mechanism
00:17for versioning and facilitating backups.
00:19No matter how large or small the project is or how many people are participating, source
00:24control is a valuable tool.
00:26I personally use git which is a distributed revision control and source code management
00:31system used by Drupal, Linux core and many other projects. Drupal.org offers free sandbox
00:37git repositories for open source projects for developers looking to experiment.
00:42For more information on git I recommend Git Essential Training with Kevin Skoglund here
00:47in the lynda.com online training library.
00:50Another extremely popular system is Apache Subversion.
00:54There are many other open source and commercial options available beyond git in subversion
00:58if you're seeking a particular feature set.
01:00Throughout this course, I've made reference to the Drupal Coding Standards several times.
01:05Coding Standards which are a series of rules and guidelines used to style the source code
01:09exist to enforce a unified structure to the code base.
01:12The Drupal Coding Standards have evolved over years of community discussion and consensus
01:17in addition to practical real-world application.
01:20The standards applied to indentation, naming conventions, comments, control structures and much more.
01:26By writing standardized code, the overhead from interpreting different developer's styles is reduced.
01:31Even if the audience is limited to one or two people, using these coding standards makes
01:36code easier to maintain and understand.
01:38I highly recommend learning and reading the Drupal Coding Standards.
01:42There's a lot of coding standards and especially in large projects it's easy to make mistakes
01:46or forget a rule or two.
01:48Fortunately there are automating testing suites available that facilitate an algorithmic approach
01:53to coding standard enforcement.
01:55The Coder project includes a module for assisting with code review using regular expressions
01:59to scan source code for things that don't adhere to the Drupal Coding Standards or deprecated
02:04functionality that should be updated.
02:06The Code Review Module has a user interface that can be used to scan a module or all modules
02:10for problems and provides a selectively robust report about what is wrong and what to do to fix it.
02:16Once the Coder project is installed, the Code Review Module can be enabled by going to modules
02:21then checking Enabled next to Coder Review.
02:29Continue with enabling the dependency.
02:32Now the module list has a new option after each description.
02:35A link to Code Review which would display a default report.
02:41Additionally, I can go to configuration and there will be a new element under DEVELOPMENT named Coder.
02:50This brings me to a Selection Form where I can select specific modules or themes to be reviewed.
02:58If you get stuck with an implementation, visit drupal.org/support for a number of options
03:04including community documentation, Internet relay chat, forms for discussion and many other resources.
03:10I personally make have a use of the Drupal API documentation at api.drupal.org.
03:17Often solutions can be found either in the official documentation or in a comment discussion
03:22beneath a particular function, class or a method.
03:25If searching the documentation and the Internet doesn't turn up a solution, then I start asking questions.
03:30It's always good to research before asking instead of the other way around.
03:34An additional form of documentation can be found in the examples project which includes
03:39dozens of tiny modules.
03:41Each of the example modules implements a particular Drupal functionality with comprehensive in-line
03:46documentation and explanation and can be enabled to demonstrate how it works.
03:50Finally, to reiterate NEVER HACK CORE.
03:5499% of the time whatever needs to be accomplished can be done so in a cleaner way with hooks
03:59or other APIs.
Collapse this transcript
Next steps
00:00This course is intended to provide a foundational knowledge of the Drupal infrastructure and
00:05API, providing context in a complex system.
00:08As such, there are a number of areas that can be explored further.
00:12Basic interactions with the Entity API were used to create a new content type.
00:16But what if a more lightweight and custom solution is necessary?
00:19The Entity API allows a developer to build a custom entity from the ground up without
00:24the overhead of nodes.
00:25To facilitate this, the entity project extends the core Entity API to reduce some of the
00:30complexities when creating new entity types.
00:34There are a number of comprehensive examples and community managed documentation available
00:37through the project page of the extended Entity API.
00:41Throughout this course a simplified implementation of locations was built in order to demonstrate
00:46how to interact with various Drupal subsystems.
00:49While the wind farm content type provided stub location with geographic coordinates,
00:53this functionality can be extended much further and already has in the location module.
00:58While I demonstrated the potential for code reuse in a narrow context, the implementation
01:02of location-based services demonstrated was not intended to be a comprehensive solution.
01:08To take the windfarm module to the next level, leverage the location API and take advantage
01:13of features such as full address support, geocoding and more.
01:17This course focused on functionality centric development within Drupal and with little
01:21exception did not cover theming and design.
01:24Drupal 7: Creating and Editing Custom Themes with Chaz Chumley here in the lynda.com online
01:29training library covers these design techniques in detail and provides context helpful for
01:34all Drupal developers, not just designers.
01:37Finally, if you use Drupal, consider getting involved with the Drupal community.
01:41Drupal.org facilitates local user group meetings, conferences on regional, national and international
01:46scales, and much more.
01:48If there's a particular module that you use on a regular basis and you find an issue or
01:52an enhancement, submit an issue to the project homepage.
01:55By providing feedback, module developers can learn from your experience.
01:59Anyone can contribute patches to projects, which will be reviewed by the module maintainers
02:04and attributed to you if used.
02:06Check out drupal.org/contribute to find ways to get involved to the Drupal community.
02:11
Collapse this transcript
Goodbye
00:00From zero to a full featured custom module, this course covered a lot of ground.
00:05Some refer to Drupal as having a learning cliff instead of a learning curve, which based
00:10on my experience is an apt label.
00:12There is a method to the madness to be sure and this course was structured to provide
00:16the background and context necessary to making sense of what is needed to build custom functionality
00:21into a site that can't be found off the shelf.
00:24No one tool is right for every job to be sure.
00:27But knowing which toolbox to open and why is important.
00:30I've come to appreciate Drupal both as a system for building web applications and for the
00:35community that exists around it.
00:37I hope you get involved as well. Thanks for watching.
Collapse this transcript


Suggested courses to watch next:

Drupal 7 Essential Training (7h 23m)
Tom Geller

Drupal 7 Advanced Training (7h 50m)
Tom Geller


PHP with MySQL Beyond the Basics (10h 27m)
Kevin Skoglund


Are you sure you want to delete this bookmark?

cancel

Bookmark this Tutorial

Name

Description

{0} characters left

Tags

Separate tags with a space. Use quotes around multi-word tags. Suggested Tags:
loading
cancel

bookmark this course

{0} characters left Separate tags with a space. Use quotes around multi-word tags. Suggested Tags:
loading

Error:

go to playlists »

Create new playlist

name:
description:
save cancel

You must be a lynda.com member to watch this video.

Every course in the lynda.com library contains free videos that let you assess the quality of our tutorials before you subscribe—just click on the blue links to watch them. Become a member to access all 104,069 instructional videos.

get started learn more

If you are already an active lynda.com member, please log in to access the lynda.com library.

Get access to all lynda.com videos

You are currently signed into your admin account, which doesn't let you view lynda.com videos. For full access to the lynda.com library, log in through iplogin.lynda.com, or sign in through your organization's portal. You may also request a user account by calling 1 1 (888) 335-9632 or emailing us at cs@lynda.com.

Get access to all lynda.com videos

You are currently signed into your admin account, which doesn't let you view lynda.com videos. For full access to the lynda.com library, log in through iplogin.lynda.com, or sign in through your organization's portal. You may also request a user account by calling 1 1 (888) 335-9632 or emailing us at cs@lynda.com.

Access to lynda.com videos

Your organization has a limited access membership to the lynda.com library that allows access to only a specific, limited selection of courses.

You don't have access to this video.

You're logged in as an account administrator, but your membership is not active.

Contact a Training Solutions Advisor at 1 (888) 335-9632.

How to access this video.

If this course is one of your five classes, then your class currently isn't in session.

If you want to watch this video and it is not part of your class, upgrade your membership for unlimited access to the full library of 2,025 courses anytime, anywhere.

learn more upgrade

You can always watch the free content included in every course.

Questions? Call Customer Service at 1 1 (888) 335-9632 or email cs@lynda.com.

You don't have access to this video.

You're logged in as an account administrator, but your membership is no longer active. You can still access reports and account information.

To reactivate your account, contact a Training Solutions Advisor at 1 1 (888) 335-9632.

Need help accessing this video?

You can't access this video from your master administrator account.

Call Customer Service at 1 1 (888) 335-9632 or email cs@lynda.com for help accessing this video.

preview image of new course page

Try our new course pages

Explore our redesigned course pages, and tell us about your experience.

If you want to switch back to the old view, change your site preferences from the my account menu.

Try the new pages No, thanks

site feedback

Thanks for signing up.

We’ll send you a confirmation email shortly.


By signing up, you’ll receive about four emails per month, including

We’ll only use your email address to send you these mailings.

Here’s our privacy policy with more details about how we handle your information.

Keep up with news, tips, and latest courses with emails from lynda.com.

By signing up, you’ll receive about four emails per month, including

We’ll only use your email address to send you these mailings.

Here’s our privacy policy with more details about how we handle your information.

   
submit Lightbox submit clicked