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