New module 'Content'

This commit is contained in:
Manuel Cillero 2017-07-26 20:30:02 +02:00
parent defad8cdef
commit 43345195e4
125 changed files with 20691 additions and 0 deletions

View file

@ -0,0 +1,149 @@
CCK 6.x-3.0-alpha4
==================
- Security: Open Redirect - SA-CONTRIB-2015-126
- #1401950 by KarenS, Add empty test module with dependency on schema to make schema available to testbot.
- #1363036 by markdorison, Fix Class 'ContentCrudTestCase' not found message in tests.
- #1097548 by DeFr: Fixed warning on node forms for multigroups without any required fields.
- #998494 by zhangtaihao: Fix Node Reference field always displaying 10 results
when using the Advanced View option (with Views 6.x-3.x)
- #1167004 by DeFr: Dramatically enhance node display performance issues.
- #1189090 by gaurishankar, yched: Fixed warnings on multigroups with no
required fields.
- #1423432 by sun: Fixed testInfo() methods throws PHP notices.
- #1533506 by yched: Fixed field tokens not available in hook_update_N().
- #1442886 by pdrake: Fixed fatal error "failed opening theme.inc" in some edge
cases.
CCK 6.x-3.0-alpha3
==================
- #1081072 Imagefield validation does not use error_element, needs a tweak so that the empty required field gets highlighted correctly.
- #1081072 Imagefield validation requires that we alter the field_info values or it will still require an image in an empty group.
- Fix miscellaneous undefined index errors.
- #1081072 Tweak fix for required fields inside non-required group so not all fields are highlighted.
- #1093928 Required option lists do not have an empty option available. Add one to avoid 'An illegal choice has been detected' errors for empty groups.
- #1081072 Fix Cannot use a scalar value as an array problem when using optionwidgets.
CCK 6.x-3.0-alpha2
==================
Features:
- #536260 by Agileware: Make "Add more values" button text themeable.
Bugfixes:
- Fix Use of undefined constant _add_new_group.
- Fix undefined index '#depth'.
- Fix warning: Creating default object from empty value in template_preprocess_content_display_overview_form() (line 134 of ..theme.inc).
- #1063338 by DeFr: Fix undefined indexes for FIELD_NAME_rendered and others.
- Fix undefined group name index in fieldgroup_nodeapi.
- #1053566 by DamienMcKenna: Fix undefined index errors in _fieldgroup_get_tree().
- #1081072 Must be able to save empty, non-required, multigroup, even if it contains required fields.
- #522564 Remove empty groups even if not marked 'remove'.
- Multigroup validation was defaulting to 'required' when created, should default to 'not required'.
- #1092044 Make sure all multigroup formats are preserved, even if not visible on the current tab.
CCK 6.x-3.0-alpha1
==================
Features:
- #1049790 Add tests for fieldgroup.
- #849420 by Roberto Gerola, rconstantine, thekevinday, ericduran, KarenS Add ability to nest fieldgroups within other fieldgroups.
- #1008184 by merlinofchaos, bojanz, dereine, yched: Adapt to Views 3 "semantic views" feature (backwards compatible with Views 2)
- #932680 by Dave Reid: Token integration : allow using of Token API's new $options
- #863226 by KarenS, make sure we have a function that will return inactive instances when other instances of the same field are still active.
- #692822 by Dave Reid, add authoring info and publishing options to CCK extra fields.
- #428650 Conditional cache/menu rebuild for content CRUD methods.
- #334945 Save default values when field is hidden because of access permissions.
- #196421 Deleting unwanted multiple values / multiple values delta issues.
- #119102 Combo field - group different fields into one.
- #505278 Panels 3 and multiple node type fields.
- #495582 Reviewed Panels 3 integration (prep work for combo / multigroups).
Implementation of fieldgroup_view_group() that can be used to render field groups.
- #417122 by quicksketch: allow drupal_alter on field and widget settings.
- #514452 Add new argument $node to content_access() to enhance the context for hook_field_access().
- #523072 by merlinofchaos - Have nodereference relationships limit CCK field availability as well.
- #519870 by joachim - Add a note to say offset starts from 0 in grouping options for views handler for multiple values fields.
- #231453 Allow fields index their columns. Implemented for reference value column in node and user reference fields. Needs update.php.
- #536730 Enhance cells in multiple column tables with CSS classes related to the fields they contain.
- #521002 by mh86 - Support for optgroups in allowed values for select elements.
- Render field descriptions as cell titles when multigroup edit mode is configured with multiple column option.
- #244896 by stella, canaryMason - Add incremental classes for multiple value fields in views.
- #416154 Preparation for Content Multigroup integration with Views.
- #416154 by coltrane, skybow - Synchronized deltas Views integration: Filter on equal deltas in multigroups.
- #227129 by igor.ro - Expose "delta" column in multiple value fields to Views.
- #531662 by neochief - i18n support. Allow external modules to translate field labels, descriptions and allowed values list as typed in the field settings form.
- #531662 i18n support for fieldgroups.
- #558420 Accept trimmed titles in nodereference autocomplete validation to prevent title mismatch errors when title ends with space.
- #580156 Wrap multigroups with unique CSS classes in node view.
- #544542 Allow subgroup fieldsets in a multigroup to be collapsible.
- #596428 by NancyDru - Allow external modules alter the content type list.
- #670344 by dagmar: Make CCK compatible with both, views 2 and views 3.
Bugfixes:
- Multigroups were defaulting to 'required' when created, should default to 'not required'.
- #766734 Restructure multigroup field layout to be consistent with other fieldgroups, and use
content_get_nested_elements() to grab the right values no matter how the fields are structured.
- #739490 by foripepe: Token integration - fix notices during token generation
- #986612 by Dave Reid: Token integration - fix variable name clash (harmless in normal cases)
- #435520 by yched, sun: Fix text fields rendered as 'n/a' in some cases
- #728472 by Darren Oh : Ensure the module's preprocess functions run first
- #894880 by yched: fix notices in check_plain() when rendering empty 'plain text' values
- #705512 by cha0s, roderick: 'add more' button - fix PHP5.3 compatibility
- #736440 by yched, dhthwy: fix memory leaks on long running migration scripts (e.g. migrate.module)
- #887742 by yched: fix notices in _content_get_formatter() in some Views
- #470470 by neilnz, use iLIKE for postgres selects.
- #769592 by vkareh, add default values to nodeapi.
- #714762 by Robbert, make diff module integration PHP5 compliant.
- Add sanitization to nodereference formatters.
- #482774 Update breaks when CCK is disabled.
- #479994 by quicksketch: fix "add more' button with devel.module's query logging (multigroup).
- #499696 by DeFr - Noderefernce / Userreference: fix Views mode when the view has exposed filters.
- #498924 #multiple FAPI attribute is used for a radios and checkboxes in content export forms.
- #409144 Review extra elements for node edit form provided by core modules.
- #361473 CCK fieldgroup panels doesn't respect CCK field privacy settings.
- #515984 Multiple field delta ORDER BY incorrect.
- #414298 by Michelle, merlinofchaos - Follow up to remove fieldgroup.panels.inc (it was moved to panels/content_types).
- #522112 by hefox, prevent malformed condition for vid IN () in views handler for multiple values fields.
- #505278 by Michelle, merlinofchaos - Provide backward compatibility with previous method to build Panels 3 subtype names for fields.
- #523864 Minor coding style issues in Panels 3 relationships implementations.
- #83787 Fix PHP warning: array_merge(): Argument #1 is not an array in content_multigroup.node_form.inc on line 432.
- #481568 by merlinofchaos - Empty property error when attempting to save a user reference in Panels.
- #510396 by yched - Use field/type definition to render fields in views.
- #526500 Fix maximum number of deltas in multigroups with explicit number of repeats defined.
- #393020 by auth - Fieldgroup data is lost when importing to module provided content type with group info for existing fields.
- #538872 Diff does not respect field permissions.
- #522558 Date field jumps delta when date field is emptied or fieldset is deleted.
- #416134 Userreference, impossible to filter allowed values by blocked users. Requires update.php.
- Minor fix in multigroup. Remove the check for module_exists('fieldgroup'), which is something implicit.
- Minor fix in multigroup. Remove unused argument $form_id from invocation to worker functions in hook_form_alter().
- #545942 warning: array_filter() [function.array-filter]: The first argument should be an array in userreference_update_6002().
- #521002 Fix validation errors when using optgroups in allowed values for select elements.
- #550252 by GuyPaddock - content_db_index_exists produces SQL errors when creating node reference fields for MySQL 4 (related to #231453).
- #551280 by xurizaemon - Typo fix for "this field cannot hold more that 3 values" error message.
- #558744 by chellomere - Fix one of the swedish translation strings to be correct, and much clearer.
- #562260 by przadka - content_db_index_exists() has wrong syntax for PostgreSQL.
- #567168 by jcmarco - Checkbox required not defined for on/off widgets.
- #568430 by Jody Lynn - Bad @see in content-field.tpl.php.
- #572672 by Jan van Diepen - Remove redundant (and bad) inclusion of node/content_types.inc in content_copy_import_form_submit().
- #585048 Setting "All users" in "User status that can be referenced" option reverts to "Blocked users".
- #589306 warning: Invalid argument supplied for foreach() in includes/panels/content_types/content_field.inc on line 166.
- #551188 "Add another item" for FileField widgets causes Invalid argument supplied for foreach() in content.node_form.inc on line 456.
- #605152 by pokurek - Missing number formatter fr_2.
- #618910 Multigroup filter fails when using relationship.
- #538458 Do not allow to change the widget type for fields in multigroups when the change is not compatible.
- #604830 by mattyoung - 32 characters limit on field and group identifiers in "Manage fields" screen.
- #464030 by eojthebrave - Typo in content_copy.module help.
- #577590 Add a visual indication to the fieldgroup title if the multigroup is required.
- #577590 Prevent validation errors when multigroup is not required and no subgroup of fields if filled in.
- #577580 Multigroup fields with empty values loose their ordering when saved via node_save.
- #614292 by DeFr - Fix CCK Reference fields based on views broken by recent change in views_plugin_style Views 2.7 (#502348).
- #625768 CCK 6.x-2.6, got fatal error on update.php while running userreference_update_6002().
- #640488 warning: max() [function.max]: Array must contain at least one element in content.module on line 1044.
- #649106 by thekevinday: Fix content_copy_form_alter(), where $form_state argument is not passed by reference.
- #669800 Do not enforce required attribute for multiple value fields the current user is not allowed to edit.
- Fix validation of required fields and multigroups.
- SA-CONTRIB-2010-088 follow up fix for nodereference_autocomplete_access() and content_access().
Changes:
- #421116 Move content.js to js/content.admin.js for consistency with location of new js files.

View file

@ -0,0 +1,5 @@
DEVELOPER DOCUMENTATION
UPDATING FROM 5.x TO 6.x
See http://drupal.org/node/191796 for a guide to updating field modules.

View file

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View file

@ -0,0 +1,47 @@
Content Construction Kit
------------------------
NOTE: Install the advanced_help module (http://drupal.org/project/advanced_help)
to access more help (writing still in progress...)
To install, place the entire CCK folder into your modules directory.
Go to Administer -> Site building -> Modules and enable the Content module and one or
more field type modules:
- text.module
- number.module
- userreference.module
- nodereference.module
Now go to Administer -> Content management -> Content types. Create a new
content type and edit it to add some fields. Then test by creating
a new node of your new type using the Create content menu link.
The included optionswidget.module provides radio and check box selectors
for the various field types.
The included fieldgroup.module allows you to group fields together
in fieldsets to help organize them.
A comprehensive guide to using CCK is available as a CCK Handbook
at http://drupal.org/node/101723.
Known incompatibilitie
----------------------
The Devel Themer module that ships with Devel is known to mess with CCK admin pages.
As a general rule, Devel Themer should only be switched on intermittently when doing
theme work on a specific page, and switched off immediately after that, for it adds
massive processing overhead.
Maintainers
-----------
The Content Construction Kit was originally developped by:
John Van Dyk
Jonathan Chaffer
Current maintainers:
Karen Stevenson
Yves Chedemois

View file

@ -0,0 +1,86 @@
================================================================
UPDATING FROM VERSION 4.7 to 6.x
================================================================
THERE IS NO DIRECT UPGRADE PATH FROM 4.7 TO 6.x!! FIRST UPGRADE
YOUR DATABASE FROM 4.7 TO THE LATEST 5.x VERSION, THEN UPGRADE
TO 6.x.
ALWAYS BACKUP YOUR DATABASE BEFORE UPGRADING!
1) While 4.7 is still installed, upload the latest 4.7 version of
all the CCK files, go to update.php, and run all possible
updates for the Content module and all field modules.
2) Install Drupal version 5. Once it is running, upload and install
the latest 5.x versions of all CCK modules, go to update.php and
run all possible updates.
Jump to the instructions for updating from version 5.x to 6.x.
================================================================
UPDATING FROM VERSION 5.x to 6.x
================================================================
YOU MUST RUN ALL POSSIBLE UPDATES TO YOUR DATABASE IN 5.x USING
THE LATEST 5.x CODE, BEFORE UPGRADING FROM 5.x to 6.x.
ALWAYS BACKUP YOUR DATABASE BEFORE UPGRADING!
1) Before upgrading to 6.x, upload the latest 5.x versions of all
CCK modules, go to update.php and run all possible updates.
2) Disable all CCK modules and remove them from the modules folder
before upgrading.
3) Install Drupal version 6. Leave all contributed modules out of
the modules folder until core modules are up and running.
Set your administration theme to a core theme like Garland until
everything has been updated to help ensure you don't encounter
theme-related problems accessing the administration area.
4) Once core is running, upload and install the latest 6.x versions
of ONLY CCK CORE FILES (the ones in the tarball on the CCK
project page). Enable them, then go to update.php and run all
possible updates. DO NOT add any other CCK modules to the
modules folder until the core CCK files are updated and working
correctly.
5) After updating CCK core modules, you may get messages saying that
some updates failed and that you need to run update.php again.
If you get messages like that, keep re-running update.php until
you get no more messages.
6) Once the core CCK modules are updated and working correctly,
add other CCK modules to the modules folder, enable them,
and run update.php. For best results, do this one module at a
time so you can tell immediately if any of them create problems
without letting those problems interfere with other updates.
Your database should now be ready to run in CCK version 6.x
================================================================
The 4.7 to 5.x steps are necessary because of significant changes
in the database structure between the 4.7 and 6.x versions. These changes
make it extremely difficult to create an automatic upgrade path that
will work reliably in every possible situation.
The extra steps in the 5.x to 6.x upgrade are because all modules
in the modules folder are automatically updated in Drupal 6, even if they
are not enabled. That means that modules that rely on core CCK may try
to run their updates even if core CCK is not enabled, and contributed
CCK modules that have broken updates will have their broken updates
run even if they are not enabled.
A number of updates are dependent on other updates and won't work
until previous updates are finished or specific modules are enabled,
so you may get messages that other modules need to be enabled or that
update.php needs to be re-run, and you need to follow those instructions
until all the updates complete.
Taking extra time during the upgrade by leaving modules out of the
modules folder altogether until you are ready to enable and update them
should reduce or eliminate update problems.

View file

@ -0,0 +1,10 @@
name = Content
description = Allows administrators to define new content types.
package = CCK
core = 6.x
; Information added by Drupal.org packaging script on 2015-06-25
version = "6.x-3.0-alpha4"
core = "6.x"
project = "cck"
datestamp = "1435195385"

View file

@ -0,0 +1,620 @@
<?php
function content_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time
$t = get_t();
if (module_exists('views') && (!function_exists('views_api_version') || views_api_version() < 2.0)) {
$requirements['cck_views'] = array(
'title' => $t('CCK - No Views integration'),
'description' => $t("CCK integration with Views module requires Views 6.x-2.0-rc2 or greater."),
'severity' => REQUIREMENT_ERROR,
);
}
return $requirements;
}
/**
* 'Safe' version of content_types() to use in updates and installs.
*
* Can't safely use content_fields() or content_types() in an update to get
* a fields array, especially without knowing what field modules are enabled,
* or the current state of the database and cache, so create a fields array
* from database info that is limited to fields from modules that are
* currently enabled.
*/
function content_types_install() {
drupal_load('module', 'content');
module_load_include('inc', 'content', '/includes/content.crud');
$module_field_types = $module_widgets = array();
foreach (module_list() as $module) {
if ($field_type = module_invoke($module, 'field_info')) {
$module_field_types[$module] = $field_type;
}
if ($widget_type = module_invoke($module, 'widget_info')) {
$module_widgets[$module] = $widget_type;
}
}
$fields = array();
$db_result = db_query("SELECT * FROM {". content_instance_tablename() ."} nfi ".
" LEFT JOIN {". content_field_tablename() ."} nf ON nf.field_name = nfi.field_name");
while ($row = db_fetch_array($db_result)) {
$field = array_merge($row, unserialize($row['global_settings']));
unset($field['global_settings']);
// There may be module data available for currently disabled modules,
// or missing module data for currently enabled modules, so start over
// to get only field info for enabled modules.
unset($field['module']);
unset($field['widget_module']);
// 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'.
$field['columns'] = isset($field['db_columns']) ? $field['db_columns'] : array();
unset($field['db_columns']);
foreach ($module_field_types as $module => $types) {
foreach ($types as $type_name => $type) {
if ($field['type'] == $type_name) {
$field['module'] = $module;
}
}
}
foreach ($module_widgets as $module => $types) {
foreach ($types as $type_name => $type) {
if ($field['widget_type'] == $type_name) {
$field['widget_module'] = $module;
}
}
}
if (!empty($field['module']) && !empty($field['widget_module'])) {
$field['widget_settings'] = unserialize($field['widget_settings']);
$field['display_settings'] = unserialize($field['display_settings']);
$field['columns'] = (array) module_invoke($field['module'], 'field_settings', 'database columns', $field);
$field = content_field_instance_expand($field);
$fields[$field['type_name']][$field['field_name']] = $field;
}
}
return $fields;
}
/**
* Implementation of hook_install().
*/
function content_install() {
variable_set('content_schema_version', 6009);
drupal_install_schema('content');
}
/**
* Implementation of hook_uninstall().
*/
function content_uninstall() {
drupal_uninstall_schema('content');
// The variable is used during the uninstall process,
// so we removed it at the very end.
variable_del('content_schema_version');
// Remove extra weights.
foreach (node_get_types('names') as $type_name) {
variable_del("content_extra_weights_$type_name");
}
}
/**
* Implementation of hook_enable().
*/
function content_enable() {
// Make sure old data is emptied out of the caches, since it
// may no longer be valid since the module was last enabled,
// especially if not all the same field modules are enabled
// as before. Especially needed during updates.
cache_clear_all('*', 'cache_content', TRUE);
content_clear_type_cache(TRUE);
}
/**
* Implementation of hook_disable().
*/
function content_disable() {
// Make sure old data is emptied out of the caches, since it
// may no longer be valid when the module is re-enabled.
cache_clear_all('*', 'cache_content', TRUE);
content_clear_type_cache(TRUE);
}
/**
* Implementation of hook_schema.
*/
function content_schema() {
// Static (meta) tables.
$schema['content_node_field'] = array(
'fields' => array(
'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'type' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
'global_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
'required' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
'multiple' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
'db_storage' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 1),
'module' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
'db_columns' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
'active' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
'locked' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
),
'primary key' => array('field_name'),
);
$schema['content_node_field_instance'] = array(
'fields' => array(
'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'type_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
'label' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
'widget_type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'widget_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
'display_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
'description' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE),
'widget_module' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
'widget_active' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
),
'primary key' => array('field_name', 'type_name'),
);
$schema['cache_content'] = drupal_get_schema_unprocessed('system', 'cache');
// When the module is first installed, the remaining code in the schema
// will create errors, since these tables have not yet been created.
// We don't need to create data tables on initial installation anyway
// since no fields have been created yet, so just return with this much
// of the schema.
if (!db_table_exists('content_node_field') || !db_table_exists('content_node_field_instance')) {
return $schema;
}
// Dynamic (data) tables.
drupal_load('module', 'content');
// We can't use many helper functions here, like content_fields() or
// content_types() or we risk creating a fatal loop from circular
// logic when they call other functions that use this schema, so create
// the schema directly from a fresh query of the database.
// content_table_schema() and content_database_info() have no
// circular logic and are safe to use here.
$db_result = db_query("SELECT * FROM {". content_instance_tablename() ."} nfi ".
" LEFT JOIN {". content_field_tablename() ."} nf ON nf.field_name = nfi.field_name WHERE nf.active = 1 AND nfi.widget_active = 1");
while ($field = db_fetch_array($db_result)) {
// 'columns' is a reserved word in MySQL4, so our db column is named 'db_columns'.
$field['columns'] = unserialize($field['db_columns']);
unset($field['db_columns']);
$content_table = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
$field_table = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
// We always add a 'per content type' table for each content type that
// has fields.
if (!isset($schema[$content_table])) {
$schema[$content_table] = content_table_schema();
}
$base_schema = content_table_schema($field);
if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
// Per-field storage: add the 'per field' table if needed.
if (!isset($schema[$field_table])) {
$schema[$field_table] = $base_schema;
}
}
else {
// Per-type storage: merge the information for the field
// in the existing table.
$schema[$content_table]['fields'] = array_merge($schema[$content_table]['fields'], $base_schema['fields']);
$schema[$content_table]['content fields'] = array_merge($schema[$content_table]['content fields'], $base_schema['content fields']);
}
}
return $schema;
}
function content_update_last_removed() {
return 1008;
}
/**
* Helper function for module updates :
* - checks no updates are pending for content.module
* - checks content module and the module being updated are both enabled.
*
* @param $module
* The name of the module being updated.
*/
function content_check_update($module = NULL) {
$ret = array();
// Check that modules are enabled before running their updates.
if (!module_exists('content') || ($module && !module_exists($module))) {
drupal_set_message(t("Updates for CCK-related modules are not run until the modules are enabled on the <a href=\"@admin-modules-path\">administer modules page</a>. When you enable them, you'll need to return to <a href=\"@update-php\">update.php</a> and run the remaining updates.", array('@admin-modules-path' => url('admin/build/modules'), '@update-php' => base_path() .'update.php?op=selection')), 'warning', FALSE);
// The content module is not enabled, nothing else can happen.
if ($module && !module_exists('content') && module_exists($module)) {
$query_message = t('!module.module has updates but cannot be updated because content.module is not enabled.<br />If and when content.module is enabled, you will need to re-run the update script. You will continue to see this message until the module is enabled and updates are run.', array('!module' => $module));
}
// The requested module is not enabled, which may be intentional.
// Just let the user know there are updates to be processed if enabled later.
else {
$query_message = t('!module.module has updates and is available in the modules folder but is not enabled.<br />If and when it is enabled, you will need to re-run the update script. You will continue to see this message until the module is enabled and updates are run.', array('!module' => $module ? $module : 'content'));
}
$ret['#abort'] = array('success' => FALSE, 'query' => $query_message);
return $ret;
}
// Check that content.module is up-to-date before running field module updates.
if ($module && (drupal_get_installed_schema_version('content', TRUE) < max(drupal_get_schema_versions('content')))) {
drupal_set_message(t('Some updates are still pending. Please return to <a href="@update-php">update.php</a> and run the remaining updates.', array('@update-php' => base_path() .'update.php?op=selection')), 'warning', FALSE);
$ret['#abort'] = array('success' => FALSE, 'query' => t('Some updates are still pending.<br/>Please re-run the update script.'));
return $ret;
}
// If everything is OK and updates are not aborted, make sure
// content_associate_fields() gets run. With all the complexity of
// the dependent updates, it can get missed when an update is aborted.
// It won't hurt anything to do this more than once in order to be sure
// it doesn't get skipped. Without this step, we can end up with
// field modules that are enabled and updated, but not marked as active
// in the content_node_field table.
if ($module and module_exists($module)) {
content_associate_fields($module);
}
}
/**
* Add module name to fields table to make it easier to identify the fields to delete when a module
* is uninstalled.
*
* Needed because the value drops out of content_info() when module is disabled, so there
* is no other way to find the associated fields.
*/
function content_update_6000() {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
drupal_load('module', 'content');
if (db_column_exists(content_field_tablename(), 'active')) {
return $ret;
}
db_add_field($ret, content_field_tablename(), 'module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''));
db_add_field($ret, content_field_tablename(), 'db_columns', array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'initial' => ''));
db_add_field($ret, content_field_tablename(), 'active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
db_add_field($ret, content_instance_tablename(), 'widget_module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''));
db_add_field($ret, content_instance_tablename(), 'widget_active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
// This will update the table for any modules enabled at this time.
foreach (module_list() as $module) {
content_associate_fields($module);
}
// Fix the cache_content schema
if (db_table_exists('cache_content')) {
db_drop_table($ret, 'cache_content');
}
db_create_table($ret, 'cache_content', drupal_get_schema_unprocessed('system', 'cache'));
variable_set('content_schema_version', 6000);
// The cache table had to be used to store data until this update ran,
// so clear cache out now that we're switching back to the cache_content table.
$ret[] = update_sql('DELETE FROM {cache}');
return $ret;
}
/**
* Rename node_field and node_field_instance tables.
*
* This is a carryover from when the data tables were renamed,
* postponed so we wouldn't create any more havoc than necessary
* until a major version change.
*
* Using 'content_node_field' instead of 'content_field'
* to avoid conflicts with field tables that will be prefixed
* with 'content_field'.
*/
function content_update_6001() {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
drupal_load('module', 'content');
if (db_table_exists('content_node_field')) {
return $ret;
}
db_rename_table($ret, 'node_field', 'content_node_field');
db_rename_table($ret, 'node_field_instance', 'content_node_field_instance');
variable_set('content_schema_version', 6001);
content_clear_type_cache(TRUE);
return $ret;
}
/**
* Get rid of automatic per content tables for content types that have no fields.
* Switching to adding those tables only when needed.
*/
function content_update_6002() {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
drupal_load('module', 'content');
$db_types = content_types_install();
$field_types = array();
$result = db_query("SELECT DISTINCT type_name FROM {". content_instance_tablename() ."}");
while ($type = db_fetch_array($result)) {
$field_types[] = $type['type_name'];
}
foreach ($db_types as $content_type => $content_info) {
if (!in_array($content_type, $field_types)) {
$table = _content_tablename($content_type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
if (db_table_exists($table)) {
db_drop_table($ret, $table);
}
}
}
variable_set('content_schema_version', 6002);
content_clear_type_cache(TRUE);
return $ret;
}
/**
* 'db_columns' column 1st got introduced as 'columns', which is forbidden in MySQL 4.
* This update function will only be useful for early D6 testers...
*/
function content_update_6003() {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
if (db_column_exists('content_node_field', 'columns')) {
db_change_field($ret, 'content_node_field', 'columns', 'db_columns', array('type' => 'text', 'size' => 'medium', 'not null' => TRUE));
}
variable_set('content_schema_version', 6003);
return $ret;
}
/**
* Index the 'nid' column on data tables to optimize node deletion.
* Large tables might deserve a multipass update.
*/
function content_update_6004(&$sandbox) {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
// Do nothing if the indexes were already created by D5's content_update_1009.
if (variable_get('content_update_1009', FALSE)) {
return $ret;
}
// Gather list of tables.
if (!isset($sandbox['tables'])) {
drupal_load('module', 'content');
$sandbox['tables'] = array();
$result = db_query('SELECT * FROM {'. content_instance_tablename() .'} nfi '.
' LEFT JOIN {'. content_field_tablename() .'} nf ON nf.field_name = nfi.field_name');
while ($field = db_fetch_array($result)) {
if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
$table = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
}
else {
$table = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
}
$sandbox['tables'][$table] = $table;
}
$sandbox['count'] = count($sandbox['tables']);
}
// One pass : add index on one table.
if ($table = array_shift($sandbox['tables'])) {
db_add_index($ret, $table, 'nid', array('nid'));
}
if ($sandbox['count']) {
$ret['#finished'] = 1 - count($sandbox['tables']) / $sandbox['count'];
}
variable_set('content_schema_version', 6004);
return $ret;
}
/**
* Add 'locked' property for fields.
*/
function content_update_6005() {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
drupal_load('module', 'content');
db_add_field($ret, content_field_tablename(), 'locked', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
variable_set('content_schema_version', 6005);
return $ret;
}
/**
* Make sure the 'locked' column is NOT NULL (error in previous content_update_6005().
*/
function content_update_6006() {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
drupal_load('module', 'content');
db_change_field($ret, content_field_tablename(), 'locked', 'locked', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
variable_set('content_schema_version', 6006);
return $ret;
}
/**
* Dummy update function to make sure the theme registry and css / JS aggregated files
* are updated.
*/
function content_update_6007() {
if ($abort = content_check_update()) {
return $abort;
}
variable_set('content_schema_version', 6007);
return array();
}
/**
* Dummy update function to make sure schema version gets updated.
*/
function content_update_6008() {
if ($abort = content_check_update()) {
return $abort;
}
variable_set('content_schema_version', 6008);
return array();
}
/**
* Add the 'exclude from $content' display setting to all existing field instances.
*/
function content_update_6009() {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
$result = db_query("SELECT * FROM {content_node_field_instance}");
while ($type = db_fetch_array($result)) {
$new_settings = array();
$display_settings = unserialize($type['display_settings']);
if (!empty($display_settings)) {
foreach ($display_settings as $key => $val) {
$new_settings[$key] = $val;
if ($key !== 'label' && is_array($val)) {
$new_settings[$key]['exclude'] = 0;
}
}
}
else {
$new_settings = array(
'label' => array('format' => 'above'),
'full' => array('format' => 'default', 'exclude' => 0),
'teaser' => array('format' => 'default', 'exclude' => 0),
);
}
db_query("UPDATE {content_node_field_instance} SET display_settings='%s' WHERE field_name='%s' AND type_name='%s'", serialize($new_settings), $type['field_name'], $type['type_name']);
}
variable_set('content_schema_version', 6009);
return $ret;
}
/**
* Fix multiple serialization caused by per-field to per-type migration.
* See http://drupal.org/node/407446.
*/
function content_update_6010(&$sandbox) {
if ($abort = content_check_update()) {
return $abort;
}
$ret = array();
drupal_load('module', 'content');
// Gather list of tables and columns that need to be updated.
if (!isset($sandbox['tables'])) {
$sandbox['tables'] = array();
$fields = content_fields();
foreach ($fields as $name => $field) {
$db_info = content_database_info($field);
foreach ($db_info['columns'] as $column => $attributes) {
if (isset($attributes['serialize']) && $attributes['serialize']) {
$sandbox['tables'][$db_info['table']]['table'] = $db_info['table'];
$sandbox['tables'][$db_info['table']]['columns'][] = $attributes['column'];
$sandbox['tables'][$db_info['table']]['multiple'] = $field['multiple'];
}
}
}
$sandbox['count'] = count($sandbox['tables']);
$sandbox['current_vid'] = 0;
$sandbox['current_delta'] = 0;
}
// Number of rows to fix in one pass.
$limit = 500;
// Start correcting data.
if ($table_info = array_shift($sandbox['tables'])) {
$table = $table_info['table'];
$columns = $table_info['columns'];
if ($table_info['multiple']) {
$query = "SELECT * FROM {" . $table . "} WHERE (vid = %d AND delta > %d) OR (vid > %d) ORDER BY vid ASC, delta ASC";
$args = array($sandbox['current_vid'], $sandbox['current_delta'], $sandbox['current_vid']);
}
else {
$query = "SELECT * FROM {" . $table . "} WHERE vid > %d ORDER BY vid ASC";
$args = array($sandbox['current_vid']);
}
$result = db_query_range($query, $args, 0, $limit);
$count = 0;
while ($row = db_fetch_array($result)) {
$update_query = $update_args = array();
foreach ($columns as $column) {
$data = $row[$column];
// No need to do anything if the data is NULL.
if (!empty($data)) {
// Unserialize until we get something that is not a string
while (is_string($data)) {
$unserialized = @unserialize($data);
if ($unserialized !== FALSE) {
$data = $unserialized;
}
else {
// TODO : test with a serialized string, just in case...
break;
}
}
// Re-serialize once.
$data = serialize($data);
// If we end up with something different than what we started with, update.
if ($data !== $row[$column]) {
$update_query[] = "$column = '%s'";
$update_args[] = $data;
}
}
}
if ($update_query) {
$update_args[] = $row['vid'];
db_query("UPDATE {" . $table . "} SET ". implode(', ', $update_query) ." WHERE vid = %d", $update_args);
}
$sandbox['current_vid'] = $row['vid'];
$sandbox['current_delta'] = isset($row['delta']) ? $row['delta'] : 0;
$count++;
}
if ($count == $limit) {
// Add the table back into the list of tables to be processed if rows remain.
array_unshift($sandbox['tables'], $table_info);
}
else {
// Done with this table: reset vid and delta markers.
$sandbox['current_vid'] = 0;
$sandbox['current_delta'] = 0;
$ret[] = array('success' => TRUE, 'query' => "Fixed serialized values in table $table");
}
}
if ($sandbox['count']) {
$ret['#finished'] = 1 - count($sandbox['tables']) / $sandbox['count'];
}
return $ret;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
<p>Using a field across several content type can be handy if a piece of data
is relevant for several content types. A typical use case is a 'Phone number'
field, used in both 'Restaurant' and 'Hotel' content types, even if hotels
and restaurants are different enough to deserve their own specific set of
fields and thus their own dedicated content types.</p>
<p>When a field has been added to more than one content type, we also say it is
"shared", and that it as "several instances".</p>
<p>At the bottom of the <strong>Manage fields</strong> page for a content type,
you'll find this:</p>
<img src="&path&add-existing-field.png">
<p>In order to add a new instance of an existing field to a content type, you
need to provide the following information:</p>
<dl>
<dt><strong>Label:</strong></dt>
<dd>
A human-readable name for the field. It will be used in input forms and
on displayed content.<br/>
All characters are allowed, including spaces, accentuated or non-european
characters.<br/>
</dd>
<dt><strong>Field:</strong></dt>
<dd>
The field to be shared.<br/>
A field cannot appear more than once in each content type. Thus, only
fields that are not already present in the current content type will be
proposed as "shareable". If none, the <strong>Add existing field</strong>
option is not available on the <strong>Manage fields</strong> page for
this content type.<br/>
Selecting a field automatically populates the <strong>Label</strong> and
<strong>Widget</strong> values with the ones used by the previous field
instance, but you can change them if needed before submitting the form.
</dd>
<dt><strong>Widget:</strong></dt>
<dd>
The form element that will be used to input data for this field on
content forms : text input, select list, etc...<br/>
Each field type has its own list of available widgets. When selecting a
field to share, the list of widgets you can select is automatically updated.
</dd>
</dl>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,57 @@
<p>At the bottom of the <strong>Manage fields</strong> page for a content type,
you'll find this:</p>
<img src="&path&add-new-field.png">
<p>In order to add a new field to a content type, you need to provide the
following information:</p>
<dl>
<dt>Label:</dt>
<dd>
A human-readable name for the field. It will be used in input forms and
on displayed content.<br/>
All characters are allowed, including spaces, accentuated or non-european
characters.
</dd>
<dt>Field name:</dt>
<dd>
A machine-readable name for the field. It is used internally to identify
the field and handle database storage. When doing custom theming, it is
this identifier that you'll use to refer to that field.<br/>
<strong>Important:</strong> The field name cannot be changed once the field has been
created.<br/>
Allowed characters: a-z (unaccentuated), 0-9 and the underscore (_).<br/>
The length of the field name cannot exceed 32 characters (including the
'field_' prefix that gets added automatically - that is, 26 free characters)<br/>
</dd>
<dt>Field type:</dt>
<dd>
The type of data to be stored in that field.<br/>
<strong>Important:</strong> The field type cannot be changed once the field has been
created.<br/>
The available field types depend on the modules you have enabled on your site. CCK comes with 6
basic field types :
<ul>
<li>Text</li>
<li>Integer</li>
<li>Float</li>
<li>Decimal</li>
<li>Node reference</li>
<li>User reference</li>
</ul>
Additional modules can be downloaded to handle other field types such as
dates, files, images... Look at the
<a href="http://www.drupal.org/project/cck" target="_blank"> CCK project page</a>
and the <a href="http://drupal.org/project/Modules/category/88" target="_blank">complete list of CCK-related modules</a>.
</dd>
<dt>Widget:</dt>
<dd>
The form element that will be used to input data for this field on
content forms : text input, select list, etc...<br/>
Each field type has its own list of available widgets. When selecting a
field type, the list of widgets you can select is automatically updated.
</dd>
</dl>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,39 @@
<p>Field groups are used to visually gather several fields that are associated
by some sort of logic, for instance several text fields that hold the different
parts of an 'Address'. On input forms and displayed content, the corresponding
fields are enclosed inside an HTML fieldset.</p>
<img style="vertical-align:top" src="&path&group-node-edit-form.png">
<img style="vertical-align:top" src="&path&group-node-display.png">
<p>At the bottom of the <strong>Manage fields</strong> page for a content type,
you'll find this:</p>
<img src="&path&add-new-group.png">
<p>In order to add a new group to a content type, you need to provide the
following information:</p>
<dl>
<dt><strong>Label:</strong></dt>
<dd>
A human-readable name for the group. It will be used in input forms and
on displayed content.<br/>
All characters are allowed, including spaces, accentuated or non-european
characters.<br/>
</dd>
<dt>Group name:</dt>
<dd>
A machine-readable name for the group. It is used internally to identify
the group. When doing custom theming, it is this identifier that you'll use
to refer to that group.<br/>
<strong>Important:</strong> The group name cannot be changed once the group has been
created.<br/>
Allowed characters: a-z (unaccentuated), 0-9 and the underscore (_).<br/>
The length of the group name cannot exceed 32 characters (including the
'group_' prefix that gets added automatically - that is, 26 free characters)<br/>
</dd>
</dl>
<p>Once a group has been created, you can define what fields it will enclose by
<a href="topic:content/rearrange">rearranging fields and groups</a>.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1,15 @@
<p>The form elements at the bottom of the <strong>Manage fields</strong> page
let you add fields and groups to your content types.</p>
<img src="&path&add-new.png">
<p style="font-size:smaller">(The <strong>Add existing field</strong> row is
displayed only if there are fields available in other content types.
The <strong>Add new group</strong> rows is displayed only if Fieldgroup module
is enabled.)</p>
<p>Your fields and groups will be created after you click the <strong>Save</strong>
button at the bottom of the page. In subsequent pages you will be presented
with the settings form for each field you added.</p>
<p>You will find more details on the required informations in the
following pages:</p>

View file

@ -0,0 +1,59 @@
[advanced help settings]
name = CCK
index name = "CCK (Content Construction Kit)"
[fields]
title = Fields and Widgets
weight = -10
[manage-fields]
title = 'Manage fields' tab
[add]
title = Add fields and groups
parent = manage-fields
weight = 1
[add-new-field]
title = Add a new field
parent = add
weight = 1
[add-existing-field]
title = Add an existing field : share a field across content types
parent = add
weight = 2
[add-new-group]
title = Add a new group
parent = add
weight = 3
[rearrange]
title = Rearrange fields and groups
parent = manage-fields
weight = 2
[remove]
title = Remove fields and groups
parent = manage-fields
weight = 3
[theme]
title = Theming CCK data in nodes
[theme-node-templates]
title = Node templates
parent = theme
weight = 1
[theme-field-templates]
title = Field templates
parent = theme
weight = 2
[theme-formatters]
title = Formatter theme functions
parent = theme
weight = 3

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

View file

@ -0,0 +1 @@
<p>The Content Construction Kit (CCK) is composed of numerous field and widget modules that can be used to add fields to any content type.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,3 @@
<p>This page lets you manage the CCK fields in your content type : add fields and
groups, rearrange them, access their configuration pages, remove them from the
content type.</p>

View file

@ -0,0 +1,24 @@
<p>To change the order of fields, grab a drag-and-drop handle
<img src="&path&draggable.png"> and drag the field to a new location in the list
(grab a handle by clicking and holding the mouse while hovering over a handle
icon). Remember that your changes will not be saved until you click the
<strong>Save</strong> button at the bottom of the page.</p>
<p>The order you define will be used both on input forms (when creating or
editing a post), and on content display (teasers, content page, RSS items...)</p>
<p>You can also change the order of non-CCK 'fields' like <strong>Title</strong>
or <strong>File attachments</strong>. Depending on the 'field', this will
affect input forms and/or content display (some of those 'fields' are not
displayed in both contexts).</p>
<p>If your content type has groups (requires the Fieldgroup module), you can
move a field inside a group by dragging it below the row of the group, and
then slightly to the right before dropping it. Note that groups can also be
reordered, but can currently not be nested inside other groups.</p>
<img src="&path&drag-groups.png">
<p>When adding a field or a group, you can drag them directly to the
intended spot in the list of fields and groups that are already present in your
content type, before clicking <strong>Save</strong>:</p>
<img src="&path&drag-new.png">

View file

@ -0,0 +1,16 @@
<h3>Removing a field</h3>
<p>When you remove a field from a content type, the data it holds are
<strong>permanently</strong> erased. You will be asked to confirm this action</p>
<p>You will have to manually update your Views, pathauto settings, etc... if
needed.</p>
<p>Note : if the field is shared across several content types, removing it from
one content type does <strong>not</strong> affect the data for the other content
types.</p>
<h3>Removing a group</h3>
<p>Removing a group from a content type does <strong>not</strong> remove the fields
it contains, and therefore erases no field data.</p>

View file

@ -0,0 +1,75 @@
<p>Field-level theming determines how the values of a given field are
displayed. The resulting output ends up in the <span class="code">$content</span>
and <span class="code">$&lt;FIELD_NAME&gt;_rendered</span> variables in the node
templates.</p>
<h3>Template files</h3>
<p>In order to customize field themeing:</p>
<ul>
<li>
Copy the <span class="code">content-field.tpl.php</span> template file into
your theme's root folder (please keep the contents of the
<span class="code">cck/theme</span> folder untouched. For the same reason,
need to copy the file instead of just moving it).
</li>
<li>
Edit that copy to your liking. See the comments in
<span class="code">cck/theme/content/content-field.tpl.php</span> for a list
of all variables available in this template.
</li>
</ul>
<h3>Template suggestions</h3>
<p>In addition, the theme layer will also look for field-specific variants
(suggestions), in the following order of precedence:</p>
<dl>
<dt>content-field-&lt;FIELD_NAME&gt;-&lt;CONTENT_TYPE_NAME&gt;.tpl.php</dt>
<dd>
ex: <span class="code">content-field-field_myfield-story.tpl.php</span> -
If present, will be used to theme the 'field_myfield' field when displaying
a 'story' node.
</dd>
<dt>content-field-&lt;CONTENT_TYPE_NAME&gt;.tpl.php</dt>
<dd>
ex: <span class="code">content-field-story.tpl.php</span> - If present,
will be used to theme all fields of 'story' nodes.
</dd>
<dt>content-field-&lt;FIELD_NAME&gt;.tpl.php</dt>
<dd>
ex: <span class="code">content-field-field_myfield.tpl.php</span> -
If present, will be used to theme all 'field_myfield' field in all the
content types it appears in.
</dd>
<dt>content-field.tpl.php</dt>
<dd>
If none of the above is present, the base template will be used.
</dd>
</dl>
<strong>Important:</strong>
<ul>
<li>
Suggestions work only if the theme also has the base template file.
If your theme has <span class="code">content-field-*.tpl.php</span> files,
it must also have a <span class="code">content-field.tpl.php</span> file.
</li>
<li>
Whenever you add new template files in your theme, you need to
rebuild the theme registry, or the theme engine won't see them.<br/>
You can do that by :<br/>
- visiting the <a href="&base_url&admin/build/modules">Administer modules</a> page<br/>
- or using <a href="http://www.drupal.org/project/devel">Devel module</a>'s
'clear cache' link.
</li>
</ul>
<p>See the <a href="http://drupal.org/node/223440">Working with template suggestions</a>
section of the <a href="http://drupal.org/theme-guide">Theme guide for Drupal 6</a>
for more informations about templates and template suggestions.</p>

View file

@ -0,0 +1,13 @@
<p>Formatters are used to turn the raw data for a single field value into html.
The <strong>Display Fields</strong> tab lets you chose which formatter you want to use
for each of your fields.</p>
<p>In CCK 2.0 for Drupal 6, all formatters now go through the theme layer.
Therefore, overriding a formatter's theme is another way you can alter how your
values are displayed (whereas changing <span class="code">content-field.tpl.php</span>
lets you change the html that "wraps" the values).</p>
<p>Most formatters come as theme functions, but some might use templates instead.
Either way, you can override them using the usual Drupal 6 theme override
practices. For more informations, see the <a href="http://drupal.org/theme-guide">Theme guide for Drupal 6</a>,
and more specifically the <a href="http://drupal.org/node/173880">Overriding themable output</a> section.</p>

View file

@ -0,0 +1,130 @@
<h3>Template files</h3>
<p>All themes usually come with a default <span class="code">node.tpl.php</span>
template. Drupal core lets you use the following variant (suggestion):</p>
<dl>
<dt>node-&lt;CONTENT_TYPE_NAME&gt;.tpl.php</dt>
<dd>
ex: <span class="code">node-story.tpl.php</span> - If present, will be used
to theme a 'story' node.
</dd>
</dl>
<p><strong>Important:</strong> whenever you add new template files in your theme, you
need to rebuild the theme registry, or the theme engine won't see them.<br/>
You can do that by :<br/>
- visiting the <a href="&base_url&admin/build/modules">Administer modules</a> page<br/>
- or using <a href="http://www.drupal.org/project/devel">Devel module</a>'s
'clear cache' link.</p>
<h3>Template variables</h3>
<p>CCK makes the following variables available in your theme's node templates:</p>
<dl>
<dt>$&lt;FIELD_NAME&gt;_rendered</dt>
<dd>
Contains the rendered html for the field, including the label and all the
field's values, with the settings defined on the <strong>Display fields</strong> tab.
</dd>
<dt>$&lt;GROUP_NAME&gt;_rendered</dt>
<dd>
Contains the rendered html for the fieldgroup (if any), including the label
and all the group's fields, with the settings defined on the <strong>Display
fields</strong> tab.<br/>
This variable therefore includes the html contained in all the
<span class="code">$&lt;FIELD_NAME&gt;_rendered</span> variables for the
group's fields.
</dd>
<dt>$FIELD_NAME</dt>
<dd>
Contains the raw values of the fields, in the usual array-format used
internally by CCK. What you find in there depends on the field type.<br/>
Each value also contains a <span class="code">'view'</span> element, that
holds the ready-to-display value as rendered by the formatter. For instance:
<pre>
array(
0 => array(
'nid' => 5,
'view' => '&lt;a href="node/5"&gt;Title of node 5&lt;/a&gt;',
),
);</pre>
<strong>Raw data are not sanitized for output, it is therefore not
advised to use them directly</strong>. Use the <span class="code">'view'</span>
value, or run the values through <span class="code">content_format()</span>.
</dd>
</dl>
<h3>Excluding fields from the $content variable</h3>
<p>By default, the <span class="code">$content</span> variable used in node
templates contains the rendered html for the whole node : CCK fields and
fieldgroups, but also body, file attachments, fivestar widgets, ...</p>
<p>If for some fields you want to use the more fine-grained variables described
above, you might want to use the <strong>Exclude</strong> checkboxes on the <strong>Display
fields</strong> screen, so that the output of those fields is excluded from the
<span class="code">$content</span> variable.</p>
<p>You can then customize the display and layout of some CCK fields or groups
using the <span class="code">$&lt;FIELD_NAME&gt;_rendered</span> /
<span class="code">$&lt;GROUP_NAME&gt;_rendered</span> variables, and trust
<span class="code">$content</span> to display 'the rest' without getting
duplicate information.</p>
<h5>Advanced trick</h5>
<p>The <strong>Exclude</strong> checkboxes affect all active themes. On sites with multiple
themes, however, the list of fields to exclude from <span class="code">$content</span>
might need to be different across the themes, depending on how their respective
node templates are structured.</p>
<p>A theme can bypass those settings by overriding the <span class="code">theme_content_exclude()</span>
function to specify the list of fields to exclude for this theme (see the
PHPDoc of the function for more information).</p>
<h3>Special case : nodes in nodereference fields</h3>
<p>In addition to the above, the following suggestions will be looked for
in priority for nodes that are displayed as values of a nodereference field using
the 'teaser' or 'full node' formatters:</p>
<dl>
<dt>node-nodereference-&lt;REFERRING_FIELD_NAME&gt;-&lt;TYPE_NAME&gt;.tpl.php</dt>
<dd>
ex: <span class="code">node-nodereference-field_noderef-story.tpl.php</span> -
If present, will be used to theme a 'story' node when refererenced in the
'field_noderef' field.
</dd>
<dt>node-nodereference-&lt;TYPE_NAME&gt;.tpl.php</dt>
<dd>
ex: <span class="code">node-nodereference-story.tpl.php</span> - If present,
will be used to theme a 'story' node when refererenced in any nodereference
field.
</dd>
<dt>node-nodereference-&lt;REFERRING_FIELD_NAME&gt;.tpl.php</dt>
<dd>
ex: <span class="code">node-nodereference-field_noderef.tpl.php</span> - If
present, will be used to a node refererenced in the 'field_noderef' field.
</dd>
<dt>node-nodereference.tpl.php</dt>
<dd>
If present, will be used to theme nodes referenced in nodereference fields.
</dd>
</dl>
<p>The following additional variables are available in templates for referenced nodes:</p>
<dl>
<dt>$referring_field</dt>
<dd>The nodereference field that references the current node.</dd>
<dt>$referring_node</dt>
<dd>The node referencing the current node.</dd>
</dl>

View file

@ -0,0 +1,9 @@
<p><strong>Note:</strong> these instructions assume you are familiar with the basic concepts
of Drupal 6 theming. For more informations, see the <a href="http://drupal.org/theme-guide">Theme guide for Drupal 6</a>,
and more specifically the <a href="http://drupal.org/node/173880">Overriding themable output</a>
section.</p>
<p>There are 3 levels where you can customize how the data in CCK fields
is displayed in nodes:</p>
<img src="&path&theme.png" class="content-border">

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,694 @@
<?php
/**
* @file
* Create/Read/Update/Delete functions for CCK-defined object types.
*
* The content module field API will allow $field arguments to
* be input either in the field => widget nested array that is used
* by the content module, or in flattened $form_values arrays, by
* converting flattened arrays to the nested format.
*
* A hook_content_fieldapi() is available for each field instance action,
* and each hook receives the nested field => widget array as an argument.
*
* The hook_content_fieldapi() $ops include:
*
* - create instance
* - read instance
* - update instance
* - delete instance
*
* Another function, content_module_delete($module) will clean up
* after a module that has been deleted by removing all data and
* settings information that was created by that module.
*/
/**
* Create an array of default values for a field type.
*/
function content_field_default_values($field_type) {
$field_types = _content_field_types();
$module = $field_types[$field_type]['module'];
$field = array(
'module' => $module,
'type' => $field_type,
'active' => 0,
);
if (module_exists($module)) {
$field['active'] = 1;
}
$field['columns'] = (array) module_invoke($module, 'field_settings', 'database columns', $field);
// Ensure columns always default to NULL values.
foreach ($field['columns'] as $column_name => $column) {
$field['columns'][$column_name]['not null'] = FALSE;
}
$field['required'] = 0;
$field['multiple'] = 0;
$field['db_storage'] = CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
// Make sure field settings all have an index in the array.
$setting_names = (array) module_invoke($module, 'field_settings', 'save', $field);
drupal_alter('field_settings', $setting_names, 'save', $field);
foreach ($setting_names as $setting) {
$field[$setting] = NULL;
}
return $field;
}
/**
* Create an array of default values for a field instance.
*/
function content_instance_default_values($field_name, $type_name, $widget_type) {
$widget_types = _content_widget_types();
$module = $widget_types[$widget_type]['module'];
$widget = array(
'field_name' => $field_name,
'type_name' => $type_name,
'weight' => 0,
'label' => $field_name,
'description' => '',
'widget_type' => $widget_type,
'widget_module' => $module,
'display_settings' => array(),
'widget_settings' => array(),
);
if (module_exists($module)) {
$widget['widget_active'] = 1;
}
$settings_names = array_merge(array('label'), array_keys(content_build_modes()));
$widget['display_settings'] = array();
foreach ($settings_names as $name) {
$widget['display_settings'][$name]['format'] = ($name == 'label') ? 'above' : 'default';
$widget['display_settings'][$name]['exclude'] = 0;
}
// Make sure widget settings all have an index in the array.
$settings_names = (array) module_invoke($module, 'widget_settings', 'save', $widget);
drupal_alter('widget_settings', $settings_names, 'save', $widget);
$widget['widget_settings'] = array();
foreach ($settings_names as $name) {
$widget['widget_settings'][$name] = NULL;
}
return $widget;
}
/**
* Expand field info to create field => widget info.
*/
function content_field_instance_expand($field) {
if (isset($field['widget'])) {
return $field;
}
$field['widget'] = !empty($field['widget_settings']) ? $field['widget_settings'] : array();
$field['widget']['label'] = !empty($field['label']) ? $field['label'] : $field['field_name'];
$field['widget']['weight'] = !empty($field['weight']) ? $field['weight'] : 0;
$field['widget']['description'] = !empty($field['description']) ? $field['description'] : '';
if (!empty($field['widget_type'])) {
$field['widget']['type'] = $field['widget_type'];
$widget_types = _content_widget_types();
$field['widget']['module'] = isset($widget_types[$field['widget_type']]['module']) ? $widget_types[$field['widget_type']]['module'] : $field['widget_module'];
}
elseif (!empty($field['widget_module'])) {
$field['widget']['module'] = $field['widget_module'];
}
unset($field['widget_type']);
unset($field['weight']);
unset($field['label']);
unset($field['description']);
unset($field['widget_module']);
unset($field['widget_settings']);
// If content.module is handling the default value,
// initialize $widget_settings with default values,
if (isset($field['default_value']) && isset($field['default_value_php']) &&
content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) {
$field['widget']['default_value'] = !empty($field['default_value']) ? $field['default_value'] : NULL;
$field['widget']['default_value_php'] = !empty($field['default_value_php']) ? $field['default_value_php'] : NULL;
unset($field['default_value']);
unset($field['default_value_php']);
}
return $field;
}
/**
* Collapse field info from field => widget to flattened form values.
*/
function content_field_instance_collapse($field) {
if (!isset($field['widget'])) {
return $field;
}
$field['widget_settings'] = !empty($field['widget']) ? $field['widget'] : array();
$field['widget_type'] = !empty($field['widget']['type']) ? $field['widget']['type'] : '';
$field['weight'] = !empty($field['widget']['weight']) ? $field['widget']['weight'] : 0;
$field['label'] = !empty($field['widget']['label']) ? $field['widget']['label'] : $field['field_name'];
$field['description'] = !empty($field['widget']['description']) ? $field['widget']['description'] : '';
$field['type_name'] = !empty($field['type_name']) ? $field['type_name'] : '';
if (!empty($field['widget']['module'])) {
$widget_module = $field['widget']['module'];
}
elseif (!empty($field['widget']['type'])) {
$widget_types = _content_widget_types();
$widget_module = $widget_types[$field['widget']['type']]['module'];
}
else {
$widget_module = '';
}
$field['widget_module'] = $widget_module;
unset($field['widget_settings']['type']);
unset($field['widget_settings']['weight']);
unset($field['widget_settings']['label']);
unset($field['widget_settings']['description']);
unset($field['widget_settings']['module']);
unset($field['widget']);
return $field;
}
/**
* Create a new field instance.
*
* @param $field
* An array of properties to create the field with, input either in
* the field => widget format used by the content module or as an
* array of form values.
*
* Required values:
* - field_name, the name of the field to be created
* - type_name, the content type of the instance to be created
*
* If there is no prior instance to create this from, we also need:
* - type, the type of field to create
* - widget_type, the type of widget to use
* @param $rebuild
* TRUE to clear content type caches and rebuild menu (default).
* FALSE allows the caller to process several fields at a time quickly, but then
* the caller is reponsible to clear content type caches and rebuild menu as soon
* as all fields have been processed. For example:
* @code
* // Create several fields at a time.
* foreach ($fields as $field) {
* content_field_instance_create($field, FALSE);
* }
* // Clear caches and rebuild menu.
* content_clear_type_cache(TRUE);
* menu_rebuild();
* @endcode
* @see content_clear_type_cache()
* @see menu_rebuild()
*/
function content_field_instance_create($field, $rebuild = TRUE) {
include_once('./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
$form_values = $field;
$field = content_field_instance_expand($field);
// If there are prior instances, fill out missing values from the prior values,
// otherwise get missing values from default values.
$prior_instances = content_field_instance_read(array('field_name' => $field['field_name']));
if (!empty($prior_instances) && is_array($prior_instances)) {
$prev_field = content_field_instance_expand($prior_instances[0]);
// Weight, label, and description may have been forced into the $field
// by content_field_instance_expand(). If there is a previous instance to
// get these values from and there was no value supplied originally, use
// the previous value.
$field['widget']['weight'] = isset($form_values['weight']) ? $form_values['weight'] : $prev_field['widget']['weight'];
$field['widget']['label'] = isset($form_values['label']) ? $form_values['label'] : $prev_field['widget']['label'];
$field['widget']['description'] = isset($form_values['description']) ? $form_values['description'] : $prev_field['widget']['description'];
}
else {
$prev_field = array('widget' => array());
}
// If we have a field type, we can build default values for this field type.
$default_values = array('widget' => array());
if (isset($field['type'])) {
$default_values = content_field_default_values($field['type']);
$default_instance_values = content_instance_default_values($field['field_name'], $field['type_name'], $field['widget']['type']);
$default_values = content_field_instance_expand(array_merge($default_values, $default_instance_values));
}
// Merge default values, previous values, and current values to create
// a complete field array.
$widget = array_merge($default_values['widget'], $prev_field['widget'], $field['widget']);
$field = array_merge($default_values, $prev_field, $field);
$field['widget'] = $widget;
// Make sure we know what module to invoke for field info.
if (empty($field['module']) && !empty($field['type'])) {
$field_types = _content_field_types();
$field['module'] = $field_types[$field['type']]['module'];
}
// The storage type may need to be updated.
$field['db_storage'] = content_storage_type($field);
// Get a fresh copy of the column information whenever a field is created.
$field['columns'] = (array) module_invoke($field['module'], 'field_settings', 'database columns', $field);
if (empty($prev_field['widget']) || $prior_instances < 1) {
// If this is the first instance, create the field.
$field['db_storage'] = $field['multiple'] > 0 ? CONTENT_DB_STORAGE_PER_FIELD : CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
_content_field_write($field, 'create');
}
elseif (!empty($prev_field['widget']) && $prev_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE && count($prior_instances) > 0) {
// If the database storage has changed, update the field and previous instances.
$field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD;
foreach ($prior_instances as $instance) {
$new_instance = $instance;
$new_instance['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD;
// Invoke hook_content_fieldapi().
module_invoke_all('content_fieldapi', 'update instance', $new_instance);
content_alter_schema($instance, $new_instance);
}
}
// Invoke hook_content_fieldapi().
module_invoke_all('content_fieldapi', 'create instance', $field);
// Update the field and the instance with the latest values.
_content_field_write($field, 'update');
_content_field_instance_write($field, 'create');
content_alter_schema(array(), $field);
if ($rebuild) {
content_clear_type_cache(TRUE);
menu_rebuild();
}
return $field;
}
/**
* Update an existing field instance.
*
* @param $field
* An array of properties to update the field with, input either in
* the field => widget format used by the content module or as an
* array of form values.
* @param $rebuild
* TRUE to clear content type caches and rebuild menu (default).
* FALSE allows the caller to process several fields at a time quickly, but then
* the caller is reponsible to clear content type caches and rebuild menu as soon
* as all fields have been processed. For example:
* @code
* // Update several fields at a time.
* foreach ($fields as $field) {
* content_field_instance_update($field, FALSE);
* }
* // Clear caches and rebuild menu.
* content_clear_type_cache(TRUE);
* menu_rebuild();
* @endcode
* @see content_clear_type_cache()
* @see menu_rebuild()
*/
function content_field_instance_update($field, $rebuild = TRUE) {
include_once('./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
// Ensure the field description is in the 'expanded' form.
$field = content_field_instance_expand($field);
// Get the previous value from the table.
$previous = content_field_instance_read(array('field_name' => $field['field_name'], 'type_name' => $field['type_name']));
$prev_field = array_pop($previous);
// Create a complete field array by merging the previous and current values,
// letting the current values overwrite the previous ones.
$widget = array_merge($prev_field['widget'], $field['widget']);
$field = array_merge($prev_field, $field);
$field['widget'] = $widget;
// Make sure we know what module to invoke for field info.
if (empty($field['module']) && !empty($field['type'])) {
$field_types = _content_field_types();
$field['module'] = $field_types[$field['type']]['module'];
}
// The storage type may need to be updated.
$field['db_storage'] = content_storage_type($field);
// Changes in field values may affect columns, or column
// information may have changed, get a fresh copy.
$field['columns'] = (array) module_invoke($field['module'], 'field_settings', 'database columns', $field);
// If the database storage has changed, update the field and previous instances.
$prior_instances = content_field_instance_read(array('field_name' => $field['field_name']));
if ($prev_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE && count($prior_instances) > 1) {
// Update the field's data storage.
$field['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD;
// Update the schema for prior instances to adapt to the change in db storage.
foreach ($prior_instances as $instance) {
if ($instance['type_name'] != $field['type_name']) {
$new_instance = $instance;
$new_instance['db_storage'] = CONTENT_DB_STORAGE_PER_FIELD;
// Invoke hook_content_fieldapi().
module_invoke_all('content_fieldapi', 'update instance', $new_instance);
content_alter_schema($instance, $new_instance);
}
}
}
// Invoke hook_content_fieldapi().
module_invoke_all('content_fieldapi', 'update instance', $field);
// Update the field and the instance with the latest values.
_content_field_write($field, 'update');
_content_field_instance_write($field, 'update');
content_alter_schema($prev_field, $field);
if ($rebuild) {
content_clear_type_cache(TRUE);
// The label is in the menu tree, so we need a menu rebuild
// if the label changes.
if ($prev_field['widget']['label'] != $field['widget']['label']) {
menu_rebuild();
}
}
return $field;
}
/**
* Write a field record.
*
* @param $field
* The field array to process.
*/
function _content_field_write($field, $op = 'update') {
// Rearrange the data to create the global_settings array.
$field['global_settings'] = array();
$setting_names = (array) module_invoke($field['module'], 'field_settings', 'save', $field);
drupal_alter('field_settings', $setting_names, 'save', $field);
foreach ($setting_names as $setting) {
// Unlike _content_field_instance_write() and 'widget_settings', 'global_settings'
// is never preexisting, so we take no particular precautions here.
$field['global_settings'][$setting] = isset($field[$setting]) ? $field[$setting] : '';
unset($field[$setting]);
}
// 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'.
$field['db_columns'] = $field['columns'];
switch ($op) {
case 'create':
drupal_write_record(content_field_tablename(), $field);
break;
case 'update':
drupal_write_record(content_field_tablename(), $field, 'field_name');
break;
}
unset($field['db_columns']);
return $field;
}
/**
* Write a field instance record.
*
* @param $field
* The field array to process.
*/
function _content_field_instance_write($field, $op = 'update') {
// Collapse the field => widget format, so that the values to be saved by
// drupal_write_record are on top-level.
$field = content_field_instance_collapse($field);
// Rearrange the data to create the widget_settings array.
$setting_names = (array) module_invoke($field['widget_module'], 'widget_settings', 'save', $field);
drupal_alter('widget_settings', $setting_names, 'save', $field);
foreach ($setting_names as $setting) {
// In some cases (when the updated $field was originally read from
// the db, as opposed to gathered from the values of a form), the values
// are already in the right place, we take care to not wipe them.
if (isset($field[$setting])) {
$field['widget_settings'][$setting] = $field[$setting];
unset($field[$setting]);
}
}
switch ($op) {
case 'create':
drupal_write_record(content_instance_tablename(), $field);
break;
case 'update':
drupal_write_record(content_instance_tablename(), $field, array('field_name', 'type_name'));
break;
}
return $field;
}
/**
* Load a field instance.
*
* @param $param
* An array of properties to use in selecting a field instance. Valid keys:
* - 'type_name' - The name of the content type in which the instance exists.
* - 'field_name' - The name of the field whose instance is to be loaded.
* if NULL, all instances will be returned.
* @param $include_inactive
* TRUE will return field instances that are 'inactive', because their field
* module or widget module is currently disabled.
* @return
* The field arrays.
*/
function content_field_instance_read($param = NULL, $include_inactive = FALSE) {
$cond = array();
$args = array();
if (is_array($param)) {
// Turn the conditions into a query.
foreach ($param as $key => $value) {
$cond[] = 'nfi.'. db_escape_string($key) ." = '%s'";
$args[] = $value;
}
}
if (!$include_inactive) {
$cond[] = 'nf.active = 1';
$cond[] = 'nfi.widget_active = 1';
}
$where = $cond ? ' WHERE '. implode(' AND ', $cond) : '';
$db_result = db_query("SELECT * FROM {". content_instance_tablename() ."} nfi ".
" JOIN {". content_field_tablename() ."} nf ON nfi.field_name = nf.field_name ".
"$where ORDER BY nfi.weight ASC, nfi.label ASC", $args);
$fields = array();
while ($instance = db_fetch_array($db_result)) {
// Unserialize arrays.
foreach (array('widget_settings', 'display_settings', 'global_settings', 'db_columns') as $key) {
$instance[$key] = (!empty($instance[$key])) ? (array) unserialize($instance[$key]) : array();
}
// 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'.
$instance['columns'] = $instance['db_columns'];
unset($instance['db_columns']);
// Unfold 'global_settings'.
foreach ($instance['global_settings'] as $key => $value) {
$instance[$key] = $value;
}
unset($instance['global_settings']);
// Put the field in the $field => 'widget' structure that is used
// all around content.module.
$field = content_field_instance_expand($instance);
// Invoke hook_content_fieldapi().
module_invoke_all('content_fieldapi', 'read instance', $field);
$fields[] = $field;
}
return $fields;
}
/**
* Delete an existing field instance.
*
* @param $field_name
* The field name to delete.
* @param $type_name
* The content type where the field instance is going to be deleted.
* @param $rebuild
* TRUE to clear content type caches and rebuild menu (default).
* FALSE allows the caller to process several fields at a time quickly, but then
* the caller is reponsible to clear content type caches and rebuild menu as soon
* as all fields have been processed. For example:
* @code
* // Delete several fields at a time.
* foreach ($fields as $field) {
* content_field_instance_delete($field['field_name'], $type_name, FALSE);
* }
* // Clear caches and rebuild menu.
* content_clear_type_cache(TRUE);
* menu_rebuild();
* @endcode
* @see content_clear_type_cache()
* @see menu_rebuild()
*/
function content_field_instance_delete($field_name, $type_name, $rebuild = TRUE) {
include_once('./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
// Get the previous field value.
$field = array_pop(content_field_instance_read(array('field_name' => $field_name, 'type_name' => $type_name)));
// Invoke hook_content_fieldapi().
module_invoke_all('content_fieldapi', 'delete instance', $field);
db_query("DELETE FROM {". content_instance_tablename() .
"} WHERE field_name = '%s' AND type_name = '%s'", $field['field_name'], $field['type_name']);
// If no instances remain, delete the field entirely.
$instances = content_field_instance_read(array('field_name' => $field_name));
if (sizeof($instances) < 1) {
db_query("DELETE FROM {". content_field_tablename() ."} WHERE field_name = '%s'", $field['field_name']);
content_alter_schema($field, array());
}
// If only one instance remains, we may need to change the database
// representation for this field.
elseif (sizeof($instances) == 1 && !($field['multiple'])) {
// Multiple-valued fields are always stored per-field-type.
$instance = $instances[0];
$new_instance = $instance;
$new_instance['db_storage'] = CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
_content_field_write($new_instance, 'update');
content_alter_schema($instance, $new_instance);
}
// If the deleted instance was the last field for the content type,
// we drop the per-type table. We also consider possibly inactive fields.
if (!content_field_instance_read(array('type_name' => $field['type_name']), TRUE)) {
$base_tablename = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
if (db_table_exists($base_tablename)) {
db_drop_table($ret, $base_tablename);
}
}
if ($rebuild) {
content_clear_type_cache(TRUE);
menu_rebuild();
}
return $field;
}
/**
* Delete all data related to a module.
*
* @param string $module
*/
function content_module_delete($module) {
// Delete the field data.
// If content module has been uninstalled first, all tables
// have already been dropped, and running that code will raise errors.
if (db_table_exists(content_instance_tablename())) {
$results = db_query("SELECT field_name, type_name FROM {". content_instance_tablename() ."} WHERE widget_module = '%s'", $module);
while ($field = db_fetch_array($results)) {
content_field_instance_delete($field['field_name'], $field['type_name'], FALSE);
}
// Force the caches and static arrays to update to the new info.
content_clear_type_cache(TRUE);
menu_rebuild();
}
}
/**
* Make changes needed when a content type is created.
*
* @param $info
* value supplied by hook_node_type()
*
* node_get_types() is still missing the new type at this point due to
* a static caching bug. We ask it to rebuild its cache so that
* content_clear_type_cache() can do its job properly.
*/
function content_type_create($info) {
node_get_types(NULL, NULL, TRUE);
content_clear_type_cache(TRUE);
}
/**
* Make changes needed when an existing content type is updated.
*
* @param $info
* value supplied by hook_node_type()
*/
function content_type_update($info) {
if (!empty($info->old_type) && $info->old_type != $info->type) {
// Rename the content type in all fields that use changed content type.
db_query("UPDATE {". content_instance_tablename() ."} SET type_name='%s' WHERE type_name='%s'", array($info->type, $info->old_type));
// Rename the content fields table to match new content type name.
$old_type = content_types($info->old_type);
$old_name = _content_tablename($old_type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
$new_name = _content_tablename($info->type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
if (db_table_exists($old_name)) {
$ret = array();
db_rename_table($ret, $old_name, $new_name);
watchdog('content', 'Content fields table %old_name has been renamed to %new_name and field instances have been updated.', array(
'%old_name' => $old_name, '%new_name' => $new_name));
}
// Rename the variable storing weights for non-CCK fields.
if ($extra = variable_get('content_extra_weights_'. $info->old_type, array())) {
variable_set('content_extra_weights_'. $info->type, $extra);
variable_del('content_extra_weights_'. $info->old_type);
}
}
// Reset all content type info.
// Menu needs to be rebuilt as well, but node types need to be rebuilt first.
// node_type_form_submit() takes care of this.
content_clear_type_cache(TRUE);
}
/**
* Make changes needed when a content type is deleted.
*
* @param $info
* value supplied by hook_node_type()
*
* TODO should we skip doing this entirely since core leaves the
* nodes in the database as orphans and wait until the nodes are
* deleted to respond?
*
*/
function content_type_delete($info) {
// Don't delete data for content-types defined by disabled modules.
if (!empty($info->disabled)) {
return;
}
// TODO : What about inactive fields ?
// Currently, content_field_instance_delete doesn't work on those...
$fields = content_field_instance_read(array('type_name' => $info->type));
foreach ($fields as $field) {
content_field_instance_delete($field['field_name'], $info->type, FALSE);
}
$table = _content_tablename($info->type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
if (db_table_exists($table)) {
$ret = array();
db_drop_table($ret, $table);
watchdog('content', 'The content fields table %name has been deleted.', array('%name' => $table));
}
// Menu needs to be rebuilt as well, but node types need to be rebuilt first.
// node_type_form_submit() takes care of this.
content_clear_type_cache(TRUE);
}

View file

@ -0,0 +1,217 @@
<?php
/**
* @file
* Functions needed for Devel module integration.
*
* TODO
* This is not really working correctly yet. It is getting called once
* for every field and then generating every field each time. But if
* you only process one field at a time the earlier ones lose their values.
* The current method works to create values, but is overly processor-
* intensive and needs to be reworked in a way that each field is
* only processed once and all values are retained.
*/
/**
* Enrich the $node that is about to be saved with arbitrary
* information in each of its CCK fields.
**/
function content_generate_fields(&$node, $field) {
$type_name = $node->type;
$type = content_types($type_name);
$field_types = _content_field_types();
if (!empty($type['fields'])) {
foreach ($type['fields'] as $field) {
$node_field = array();
// If module handles own multiples, then only call its hook once.
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_MODULE) {
$max = 0;
}
else {
switch ($field['multiple']) {
case 0:
$max = 0;
break;
case 1:
$max = rand(0, 3); //just an arbitrary number for 'unlimited'
break;
default:
$max = $field['multiple'];
break;
}
}
for ($i = 0; $i <= $max; $i++) {
$module = $field_types[$field['type']]['module'];
$function = $module .'_content_generate';
if (function_exists($function)) {
$result = $function($node, $field); // $items, $teaser, $page
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_MODULE) {
// Fields that handle their own multiples will add their own deltas.
$node_field = $result;
}
else {
// When multiples are handled by the content module, add a delta for each result.
$node_field[$i] = $result;
}
}
}
$node->{$field['field_name']} = $node_field;
}
}
}
/**
* A simple function to return multiple values for fields that use
* custom multiple value widgets but don't need any other special multiple
* values handling. This will call the field generation function
* a random number of times and compile the results into a node array.
*/
function content_devel_multiple($function, $node, $field) {
$node_field = array();
if (function_exists($function)) {
switch ($field['multiple']) {
case 0:
$max = 0;
break;
case 1:
$max = rand(0, 3); //just an arbitrary number for 'unlimited'
break;
default:
$max = $field['multiple'];
break;
}
for ($i = 0; $i <= $max; $i++) {
$node_field[$i] = $function($node, $field);
}
}
return $node_field;
}
if (module_exists('text')) {
function text_content_generate($node, $field) {
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_MODULE) {
return content_devel_multiple('_text_content_generate', $node, $field);
}
else {
return _text_content_generate($node, $field);
}
}
function _text_content_generate($node, $field) {
$node_field = array();
if ($field['widget']['type'] == 'text_textarea') {
$format = $field['text_processing'] ? rand(0, 3) : 0;
$node_field['value'] = devel_create_content($format);
$node_field['format'] = $format;
}
else {
$allowed_values = content_allowed_values($field);
if (!empty($allowed_values)) {
// Just pick one of the specified allowed values.
$node_field['value'] = array_rand($allowed_values);
}
else {
// Generate a value that respects max_length.
if (empty($field['max_length'])) {
$field['max_length'] = 12;
}
$node_field['value'] = user_password($field['max_length']);
}
}
return $node_field;
}
}
if (module_exists('number')) {
function number_content_generate($node, $field) {
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_MODULE) {
return content_devel_multiple('_number_content_generate', $node, $field);
}
else {
return _number_content_generate($node, $field);
}
}
function _number_content_generate($node, $field) {
$node_field = array();
$allowed_values = content_allowed_values($field);
if (!empty($allowed_values)) {
// Just pick one of the specified allowed values.
$node_field['value'] = array_rand($allowed_values);
}
else {
$min = is_numeric($field['min']) ? $field['min'] : 0;
switch ($field['type']) {
case 'number_integer':
$max = is_numeric($field['max']) ? $field['max'] : 10000;
$decimal = 0;
$scale = 0;
break;
case 'number_decimal':
$precision = is_numeric($field['precision']) ? $field['precision'] : 10;
$scale = is_numeric($field['scale']) ? $field['scale'] : 2;
$max = is_numeric($field['max']) ? $field['max'] : pow(10, ($precision - $scale));
$decimal = rand(0, (10 * $scale)) / 100;
break;
case 'number_float':
$precision = rand(10, 32);
$scale = rand(0, 2);
$decimal = rand(0, (10 * $scale)) / 100;
$max = is_numeric($field['max']) ? $field['max'] : pow(10, ($precision - $scale));
break;
}
$node_field['value'] = round((rand($min, $max) + $decimal), $scale);
}
return $node_field;
}
}
if (module_exists('nodereference')) {
function nodereference_content_generate($node, $field) {
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_MODULE) {
return content_devel_multiple('_nodereference_content_generate', $node, $field);
}
else {
return _nodereference_content_generate($node, $field);
}
}
function _nodereference_content_generate($node, $field) {
$node_field = array();
$allowed_values = nodereference_allowed_values($field);
unset($allowed_values[0]);
if (!empty($allowed_values)) {
// Just pick one of the specified allowed values.
$node_field['nid'] = array_rand($allowed_values);
}
return $node_field;
}
}
if (module_exists('userreference')) {
function userreference_content_generate($node, $field) {
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_MODULE) {
return content_devel_multiple('_userreference_content_generate', $node, $field);
}
else {
return _userreference_content_generate($node, $field);
}
}
function _userreference_content_generate($node, $field) {
$node_field = array();
$allowed_values = userreference_allowed_values($field);
if (isset($allowed_values['none'])) {
unset($allowed_values['none']);
}
if (!empty($allowed_values)) {
// Just pick one of the specified allowed values.
$node_field['uid'] = array_rand($allowed_values);
}
return $node_field;
}
}

View file

@ -0,0 +1,127 @@
<?php
/**
* @file hook_diff() implementations for CCK (especially fields).
*
* These should use a field-hook so the data for the diff is
* field-type specific.
*/
/**
* Implementation of hook_diff()
*/
function content_diff($old_node, $new_node) {
$result = array();
// Prevent against invalid 'nodes' built by broken 3rd party code.
if (isset($new_node->type)) {
$type = content_types($new_node->type);
$field_types = _content_field_types();
foreach ($type['fields'] as $field) {
// Ignore fields the current user is not allowed to view.
if (!content_access('view', $field, NULL, $new_node)) {
continue;
}
$function = $field_types[$field['type']]['module'] . '_content_diff_values';
$function = function_exists($function) ? $function : 'content_content_diff_values';
$old_values = array();
$new_values = array();
if (isset($old_node->$field['field_name'])) {
$old_values = $function($old_node, $field, $old_node->$field['field_name']);
}
if (isset($new_node->$field['field_name'])) {
$new_values = $function($new_node, $field, $new_node->$field['field_name']);
}
if ($old_values || $new_values) {
$result[$field['field_name']] = array(
'#name' => $field['widget']['label'],
'#old' => $old_values,
'#new' => $new_values,
'#weight' => $field['widget']['weight'],
'#format' => array(
'show_header' => FALSE,
),
);
}
}
}
return $result;
}
/**
* Default 'implementation' of hook_content_diff_values.
*
* Note that diff.module takes care of running check_plain on the output.
*/
function content_content_diff_values($node, $field, $items) {
$return = array();
foreach ($items as $item) {
foreach (explode("\n", $item['value']) as $i) {
$return[] = $i;
}
}
return $return;
}
if (module_exists('userreference')) {
/**
* Implementation of hook_content_diff_values.
*/
function userreference_content_diff_values($node, $field, $items) {
static $titles = array();
// Gather ids.
$ids = array();
foreach ($items as $item) {
if ($item['uid'] && is_numeric($item['uid'])) {
$ids[] = $item['uid'];
}
}
// Fetch titles we don't know yet.
$queried_ids = array_diff($ids, array_keys($titles));
if ($queried_ids) {
$result = db_query('SELECT uid, name FROM {users} WHERE uid IN ('. db_placeholders($queried_ids) .')', $queried_ids);
while ($row = db_fetch_array($result)) {
$titles[$row['uid']] = $row['name'];
}
}
// Return result.
$return = array();
foreach ($items as $item) {
if ($item['uid'] && isset($titles[$item['uid']])) {
$return[] = $titles[$item['uid']];
}
}
return $return;
}
}
if (module_exists('nodereference')) {
/**
* Implementation of hook_content_diff_values.
*/
function nodereference_content_diff_values($node, $field, $items) {
static $titles = array();
// Gather ids.
$ids = array();
foreach ($items as $item) {
if ($item['nid'] && is_numeric($item['nid'])) {
$ids[] = $item['nid'];
}
}
// Fetch titles we don't know yet.
$queried_ids = array_diff($ids, array_keys($titles));
if ($queried_ids) {
$result = db_query('SELECT nid, title FROM {node} WHERE nid IN ('. db_placeholders($queried_ids) .')', $queried_ids);
while ($row = db_fetch_array($result)) {
$titles[$row['nid']] = $row['title'];
}
}
// Return result.
$return = array();
foreach ($items as $item) {
if ($item['nid'] && isset($titles[$item['nid']])) {
$return[] = $titles[$item['nid']];
}
}
return $return;
}
}

View file

@ -0,0 +1,520 @@
<?php
/**
* @file
* Create fields' form for a content type.
*
* Each field defines its own component of the content entry form, via its
* chosen widget.
*/
function content_form(&$form, &$form_state) {
$type = content_types($form['type']['#value']);
foreach ($type['fields'] as $field_name => $field) {
$form['#field_info'][$field['field_name']] = $field;
$form += (array) content_field_form($form, $form_state, $field);
}
return $form;
}
/**
* Create a separate form element for each field.
*
* // TODO: $count param ? not used anymore ?
* Hook_widget() picks up two new values, $count and $delta, to help
* widgets know what information to return since multiple values are
* sometimes controlled by the content module.
*
* @param $form
* the form to add this field element to
* @param $form_state
* the form_state for the above form
* @param $field
* the field array to use to create the form element
* @param $get_delta
* use to get only a specific delta value of a multiple value field, otherwise
* function will return the entire $field form element.
*/
function content_field_form(&$form, &$form_state, $field, $get_delta = NULL) {
$form['#cache'] = FALSE;
$node = $form['#node'];
$addition = array();
$form_element = array();
$field_name = $field['field_name'];
$items = array();
// TODO: is the "if (function_exists($function)) {" needed ?
// defining the $function here makes it unclear where it is actually called
$function = $field['widget']['module'] .'_widget';
if (function_exists($function)) {
// Prepare the values to be filled in the widget.
// We look in the following places:
// - Form submitted values
// - Node values (when editing an existing node), or pre-filled values (when
// creating a new node translation)
// - Default values set for the field (when creating a new node).
if (!empty($form_state['values'][$field['field_name']])) {
$items = $form_state['values'][$field['field_name']];
// If there was an AHAH add more button in this field, don't save it.
unset($items[$field['field_name'] .'_add_more']);
}
elseif (!empty($node->$field['field_name'])) {
$items = $node->$field['field_name'];
}
elseif (empty($node->nid)) {
if (content_callback('widget', 'default value', $field) != CONTENT_CALLBACK_NONE) {
// If a module wants to insert custom default values here,
// it should provide a hook_default_value() function to call,
// otherwise the content module's content_default_value() function
// will be used.
$callback = content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] .'_default_value' : 'content_default_value';
if (function_exists($callback)) {
$items = $callback($form, $form_state, $field, 0);
}
}
}
// See if access to this form element is restricted,
// if so, skip widget processing and just set the value.
$access = content_access('edit', $field, NULL, $node);
if (!$access) {
$addition[$field_name] = array(
'#access' => $access,
'#type' => 'value',
'#value' => $items,
);
return $addition;
}
// If content module handles multiple values for this form element,
// and not selecting an individual $delta, process the multiple value form.
if (!isset($get_delta) && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
$form_element = content_multiple_value_form($form, $form_state, $field, $items);
}
// If the widget is handling multiple values (e.g optionwidgets),
// or selecting an individual element, just get a single form
// element and make it the $delta value.
else {
$delta = isset($get_delta) ? $get_delta : 0;
if ($element = $function($form, $form_state, $field, $items, $delta)) {
$title = check_plain(t($field['widget']['label']));
$description = content_filter_xss(t($field['widget']['description']));
$defaults = array(
'#required' => $get_delta > 0 ? FALSE : $field['required'],
'#columns' => array_keys($field['columns']),
'#title' => $title,
'#description' => $description,
'#delta' => $delta,
'#field_name' => $field['field_name'],
'#type_name' => $field['type_name'],
);
// If we're processing a specific delta value for a field where the
// content module handles multiples, set the delta in the result.
// For fields that handle their own processing, we can't make assumptions
// about how the field is structured, just merge in the returned value.
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
$form_element[$delta] = array_merge($element, $defaults);
}
else {
$form_element = array_merge($element, $defaults);
}
}
}
// Field name is needed at top level as well as the individual elements
// so the multiple values or other field level theme or processing can find it.
if ($form_element) {
$defaults = array(
'#field_name' => $field['field_name'],
'#tree' => TRUE,
'#weight' => $field['widget']['weight'],
'#access' => $access,
// TODO: what's the need for #count ? does not seem to be used anywhere ?
'#count' => count($form_element),
);
$addition[$field['field_name']] = array_merge($form_element, $defaults);
}
}
return $addition;
}
/**
* Special handling to create form elements for multiple values.
*
* Handles generic features for multiple fields:
* - number of widgets
* - AHAH-'add more' button
* - drag-n-drop value reordering
*/
function content_multiple_value_form(&$form, &$form_state, $field, $items) {
$field_name = $field['field_name'];
switch ($field['multiple']) {
case 0:
$deltas = array(0);
$max = 0;
break;
case 1:
$deltas = array_keys($items);
$current_item_count = max(1, (isset($form_state['item_count'][$field_name]) ? $form_state['item_count'][$field_name] : count($deltas)));
$max = (!empty($deltas) ? max($deltas) : -1);
while (count($deltas) < $current_item_count) {
$max++;
$deltas[] = $max;
}
break;
default:
$max = $field['multiple'] - 1;
$deltas = range(0, $max);
break;
}
$title = check_plain(t($field['widget']['label']));
$description = content_filter_xss(t($field['widget']['description']));
$form_element = array(
'#theme' => 'content_multiple_values',
'#title' => $title,
'#required' => $field['required'],
'#description' => $description,
);
$function = $field['widget']['module'] .'_widget';
foreach ($deltas as $delta) {
if ($element = $function($form, $form_state, $field, $items, $delta)) {
$defaults = array(
'#title' => ($field['multiple'] >= 1) ? '' : $title,
'#description' => ($field['multiple'] >= 1) ? '' : $description,
'#required' => ($field['multiple'] == 0 ? $field['required'] : FALSE),
'#weight' => $delta,
'#delta' => $delta,
'#columns' => array_keys($field['columns']),
'#field_name' => $field_name,
'#type_name' => $field['type_name'],
);
// Add an input field for the delta (drag-n-drop reordering), which will
// be hidden by tabledrag js behavior.
if ($field['multiple'] >= 1) {
// We name the element '_weight' to avoid clashing with column names
// defined by field modules.
$element['_weight'] = array(
'#type' => 'weight',
'#delta' => $max, // this 'delta' is the 'weight' element's property
'#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
'#weight' => 100,
);
}
// Add a checkbox to allow users remove a single delta item.
// See content_set_empty() and theme_content_multiple_values().
if ($field['multiple'] == 1) {
// We name the element '_remove' to avoid clashing with column names
// defined by field modules.
$element['_remove'] = array(
'#type' => 'checkbox',
'#attributes' => array('class' => 'content-multiple-remove-checkbox'),
'#default_value' => isset($items[$delta]['_remove']) ? $items[$delta]['_remove'] : 0,
);
}
$form_element[$delta] = array_merge($element, $defaults);
}
}
// Add an #after_build callback to prevent validation of fields that are
// flagged for removal and enforce field requirement settings.
if ($field['multiple'] >= 1) {
$form_element['#after_build'] = array('content_multiple_value_after_build_proxy');
}
// Add AHAH add more button, if not working with a programmed form.
if ($field['multiple'] == 1 && empty($form['#programmed'])) {
// Make sure the form is cached so ahah can work.
$form['#cache'] = TRUE;
$content_type = content_types($field['type_name']);
$field_name_css = str_replace('_', '-', $field_name);
$form_element[$field_name .'_add_more'] = array(
'#type' => 'submit',
'#name' => $field_name .'_add_more',
'#value' => t('Add another item'),
'#weight' => $field['widget']['weight'] + $max + 1,
// Submit callback for disabled JavaScript. drupal_get_form() might get
// the form from the cache, so we can't rely on content_form_alter()
// including this file. Therefore, call a proxy function to do this.
'#submit' => array('content_add_more_submit_proxy'),
'#ahah' => array(
'path' => 'content/js_add_more/'. $content_type['url_str'] .'/'. $field_name,
'wrapper' => $field_name_css .'-items',
'method' => 'replace',
'effect' => 'fade',
),
// When JS is disabled, the content_add_more_submit handler will find
// the relevant field using these entries.
'#field_name' => $field_name,
'#type_name' => $field['type_name'],
);
// Add wrappers for the fields and 'more' button.
$form_element['#prefix'] = '<div id="'. $field_name_css .'-items">';
$form_element['#suffix'] = '</div>';
$form_element[$field_name .'_add_more']['#prefix'] = '<div class="content-add-more clear-block">';
$form_element[$field_name .'_add_more']['#suffix'] = '</div>';
}
return $form_element;
}
/**
* After build callback for multiple value fields.
*/
function content_multiple_value_after_build($elements, &$form_state) {
$items_map = array();
foreach (element_children($elements) as $delta) {
// Find delta items for this field when the form if being processed for validation.
if (isset($elements[$delta]) && $elements[$delta] && is_numeric($delta) && !empty($elements[$delta]['#needs_validation'])) {
// Find items that have been flagged for removal.
if (isset($elements[$delta]['_remove']) && !empty($elements[$delta]['_remove']['#value'])) {
// Update the value in the #post attribute of the elements.
$post = &$elements[$delta]['#post'];
foreach ($elements[$delta]['#parents'] as $name) {
$post = &$post[$name];
}
$post = array('_weight' => $elements[$delta]['_weight']['#value'], '_remove' => 1);
// Alter the value of this element and children recursively.
content_multiple_value_after_build_recursive($elements[$delta], $elements[$delta]['#post']);
$items_map[$delta] = TRUE;
}
else {
$items_map[$delta] = FALSE;
}
}
}
// If the multiple values field is required, then make sure there's at
// least one item not flagged for removal. This is necessary to point
// the user to the correct form element when the validation error is
// issued from content_multiple_value_nodeapi_validate().
$items_count = count($items_map);
if (!empty($elements['#required']) && $items_count > 0) {
// If the number of removed items is equal to the total number of
// items, then we'll reset the '_remove' flag of the first item, and
// that will be used to point the user when the required field error
// is issued by content_multiple_value_nodeapi_validate().
if ($items_count == count(array_filter($items_map))) {
$delta = key($items_map);
if (isset($elements[$delta]['_remove'])) {
$elements[$delta]['_remove']['#value'] = 0;
}
}
}
return $elements;
}
/**
* Helper function to deal with items flagged for removal recursively.
*/
function content_multiple_value_after_build_recursive(&$elements, $post) {
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key] && !in_array($key, array('_weight', '_remove', '_error_element'))) {
// Recurse through all children elements.
content_multiple_value_after_build_recursive($elements[$key], $post);
}
}
// Remove values for items flagged for removal.
if (isset($elements['#value'])) {
$elements['#value'] = NULL;
form_set_value($elements, NULL, $form_state);
$elements['#post'] = $post;
}
}
/**
* Implementation of nodeapi('validate') for multiple value fields
* managed by content module itself.
*/
function content_multiple_value_nodeapi_validate(&$node, $field, &$items, $form) {
$field_name = $field['field_name'];
// Getting the field structure from the form allows other modules alter
// field properties such as the required attribute.
$field = $form['#field_info'][$field_name];
// Get rid of the add more items element.
unset($items[$field_name .'_add_more']);
// Reorder items to account for drag-n-drop reordering.
$items = _content_sort_items($field, $items);
// Create a copy of the items before filtering those that are flagged
// for removal. We need this copy later to obtain the error element.
$items_copy = $items;
// Filter out items flagged for removal.
$items = content_set_empty($field, $items);
// Enforce field requirement settings.
if ($field['required'] && empty($node->_content_ignore_required_fields[$field_name]) && content_access('edit', $field, NULL, $node)) {
// Count non-empty items.
$count = 0;
$function = $field['module'] .'_content_is_empty';
foreach ($items as $item) {
if (!$function($item, $field)) {
$count++;
}
}
// The field is required so we expect at least one non-empty item.
if ($count == 0) {
// Try to guess the element path in the form from the first item that
// is not flagged for removal. Defaults to first item.
$error_element_index = 0;
foreach ($items_copy as $index => $item) {
if (empty($item['_remove'])) {
$error_element_index = $index;
break;
}
}
$error_element = isset($items_copy[$error_element_index]) && is_array($items_copy[$error_element_index]) && isset($items_copy[$error_element_index]['_error_element']) ? $items_copy[$error_element_index]['_error_element'] : '';
form_set_error($error_element, t('%name field is required.', array('%name' => t($field['widget']['label']))));
}
}
}
/**
* Submit handler to add more choices to a content form. This handler is used when
* JavaScript is not available. It makes changes to the form state and the
* entire form is rebuilt during the page reload.
*/
function content_add_more_submit($form, &$form_state) {
// Set the form to rebuild and run submit handlers.
node_form_submit_build_node($form, $form_state);
$field_name = $form_state['clicked_button']['#field_name'];
$type_name = $form_state['clicked_button']['#type_name'];
// Make the changes we want to the form state.
if ($form_state['values'][$field_name][$field_name .'_add_more']) {
$form_state['item_count'][$field_name] = count($form_state['values'][$field_name]);
}
}
/**
* Menu callback for AHAH addition of new empty widgets.
*/
function content_add_more_js($type_name_url, $field_name) {
$content_type = content_types($type_name_url);
$type = content_types($type_name_url);
$field = content_fields($field_name, $type['type']);
if (($field['multiple'] != 1) || empty($_POST['form_build_id'])) {
// Invalid request.
drupal_json(array('data' => ''));
exit;
}
// Retrieve the cached form.
$form_state = array('submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);
if (!$form) {
// Invalid form_build_id.
drupal_json(array('data' => ''));
exit;
}
// We don't simply return a new empty widget to append to existing ones, because
// - ahah.js won't simply let us add a new row to a table
// - attaching the 'draggable' behavior won't be easy
// So we resort to rebuilding the whole table of widgets including the existing ones,
// which makes us jump through a few hoops.
// The form that we get from the cache is unbuilt. We need to build it so that
// _value callbacks can be executed and $form_state['values'] populated.
// We only want to affect $form_state['values'], not the $form itself
// (built forms aren't supposed to enter the cache) nor the rest of $form_data,
// so we use copies of $form and $form_data.
$form_copy = $form;
$form_state_copy = $form_state;
$form_copy['#post'] = array();
form_builder($_POST['form_id'], $form_copy, $form_state_copy);
// Just grab the data we need.
$form_state['values'] = $form_state_copy['values'];
// Reset cached ids, so that they don't affect the actual form we output.
form_clean_id(NULL, TRUE);
// Sort the $form_state['values'] we just built *and* the incoming $_POST data
// according to d-n-d reordering.
unset($form_state['values'][$field_name][$field['field_name'] .'_add_more']);
foreach ($_POST[$field_name] as $delta => $item) {
$form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
$form_state['values'][$field_name][$delta]['_remove'] = isset($item['_remove']) ? $item['_remove'] : 0;
}
$form_state['values'][$field_name] = _content_sort_items($field, $form_state['values'][$field_name]);
$_POST[$field_name] = _content_sort_items($field, $_POST[$field_name]);
// Build our new form element for the whole field, asking for one more element.
$delta = max(array_keys($_POST[$field_name])) + 1;
$form_state['item_count'] = array($field_name => count($_POST[$field_name]) + 1);
$form_element = content_field_form($form, $form_state, $field);
// Let other modules alter it.
// We pass an empty array as hook_form_alter's usual 'form_state' parameter,
// instead of $form_atate (for reasons we may never remember).
// However, this argument is still expected to be passed by-reference
// (and PHP5.3 will throw an error if it isn't.) This leads to:
$data = &$form_element;
$empty_form_state = array();
$data['__drupal_alter_by_ref'] = array(&$empty_form_state);
drupal_alter('form', $data, 'content_add_more_js');
// Add the new element at the right place in the (original, unbuilt) form.
$success = content_set_nested_elements($form, $field_name, $form_element[$field_name]);
// Save the new definition of the form.
$form_state['values'] = array();
form_set_cache($form_build_id, $form, $form_state);
// Build the new form against the incoming $_POST values so that we can
// render the new element.
$_POST[$field_name][$delta]['_weight'] = $delta;
$form_state = array('submitted' => FALSE);
$form += array(
'#post' => $_POST,
'#programmed' => FALSE,
);
$form = form_builder($_POST['form_id'], $form, $form_state);
// Render the new output.
$field_form = array_shift(content_get_nested_elements($form, $field_name));
// We add a div around the new content to receive the ahah effect.
$field_form[$delta]['#prefix'] = '<div class="ahah-new-content">'. (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : '');
$field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') .'</div>';
// Prevent duplicate wrapper.
unset($field_form['#prefix'], $field_form['#suffix']);
// If a newly inserted widget contains AHAH behaviors, they normally won't
// work because AHAH doesn't know about those - it just attaches to the exact
// form elements that were initially specified in the Drupal.settings object.
// The new ones didn't exist then, so we need to update Drupal.settings
// by ourselves in order to let AHAH know about those new form elements.
$javascript = drupal_add_js(NULL, NULL);
$output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');</script>' : '';
$output = theme('status_messages') . drupal_render($field_form) . $output_js;
// Using drupal_json() breaks filefield's file upload, because the jQuery
// Form plugin handles file uploads in a way that is not compatible with
// 'text/javascript' response type.
$GLOBALS['devel_shutdown'] = FALSE;
print drupal_to_js(array('status' => TRUE, 'data' => $output));
exit;
}

View file

@ -0,0 +1,347 @@
<?php
/**
* @file
* Provides basic rules module support.
*/
/**
* Implementation of hook_rules_action_info().
*/
function content_rules_action_info() {
$info = array();
$info['content_rules_action_populate_field'] = array(
'label' => t('Populate a field'),
'arguments' => array(
'node' => array(
'type' => 'node',
'label' => t('Content'),
),
),
'eval input' => array('code'),
'help' => t('You should make sure that the used field exists in the given content type.'),
'module' => 'CCK',
);
return $info;
}
/**
* Action: populate a field.
*/
function content_rules_action_populate_field($node, $settings, $element, &$state) {
// Get information about the field.
$field = content_fields($settings['field_name'], $node->type);
$value = _content_rules_get_field_value($settings, $state);
if (!empty($field) && is_array($value)) {
$node->$settings['field_name'] = $value;
return array('node' => $node);
}
}
/**
* Action "populate a field" configuration form.
* This is a multistep form!
*/
function content_rules_action_populate_field_form($settings, &$form, &$form_state) {
$settings += array('field_name' => '', 'code' => '', 'value' => NULL);
if (empty($settings['field_name'])) {
$form['settings']['field_name'] = array(
'#type' => 'select',
'#title' => t('Field'),
'#options' => content_rules_get_field_names_by_type(),
'#default_value' => $settings['field_name'],
'#description' => t('Select the machine-name of the field.'),
'#required' => TRUE,
);
// Hide some form elements in the first step.
$form['negate']['#access'] = FALSE;
$form['input_help']['#access'] = FALSE;
$form['weight']['#access'] = FALSE;
// Replace the usual submit handlers with a own handler.
$form['submit']['#submit'] = array('content_rules_action_populate_field_form_step_submit');
$form['submit']['#value'] = t('Continue');
}
else {
// Show the fields form here.
module_load_include('inc', 'content', 'includes/content.node_form');
$field = content_fields($settings['field_name']);
$form['#node'] = (object)array('type' => '', $settings['field_name'] => $settings['value']);
$form['#field_info'][$field['field_name']] = $field;
// We can't put it into $form['settings'] as this would break AHAH callbacks
$form += (array) content_field_form($form, $form_state, $field);
$form[ $settings['field_name'] ]['#weight'] = 4;
unset($form['#cache']);
// Advanced: PHP code.
$form['advanced_options'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced: Specify the fields value with PHP code'),
'#collapsible' => TRUE,
'#collapsed' => empty($settings['code']),
'#weight' => 5,
);
$db_info = content_database_info($field);
$columns = array_keys($db_info['columns']);
foreach ($columns as $key => $column) {
$columns[$key] = t("'@column' => value for @column", array('@column' => $column));
}
$sample = t("return array(\n 0 => array(@columns),\n // You'll usually want to stop here. Provide more values\n // if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns)));
$form['advanced_options']['code'] = array(
'#type' => 'textarea',
'#title' => t('Code'),
'#default_value' => $settings['code'],
'#rows' => 6,
'#description' => t('Advanced usage only: PHP code that returns the value to set. Should not include &lt;?php ?&gt; delimiters. If this field is filled out, the value returned by this code will override any value specified above. Expected format: <pre>!sample</pre>Using <a href="@link_devel">devel.module\'s</a> \'devel load\' tab on a content page might help you figure out the expected format.', array(
'!sample' => $sample,
'@link_devel' => 'http://www.drupal.org/project/devel',
)),
);
// Add this file to be included when the form is built by rules
// as it's needed by CCKs add more button.
// See rules_after_build_include_files().
$form['#includes'][] = './'. drupal_get_path('module', 'node') .'/node.pages.inc';
}
}
function content_rules_action_populate_field_form_step_submit($form, &$form_state) {
$form_state['element']['#settings']['field_name'] = $form_state['values']['settings']['field_name'];
}
/**
* Validate the chosen value or php code.
*/
function content_rules_action_populate_field_validate($form, &$form_state) {
if (!isset($form_state['element']['#settings']['field_name'])) {
//Just validate the last step.
return;
}
if (isset($form_state['values']['code']) && ($php = $form_state['values']['code'])) {
if (strpos($php, 'return') === FALSE) {
form_set_error('code', t('You have to return the default value in the expected format.'));
}
}
else {
// Validate the field.
$settings = $form_state['element']['#settings'];
$field = content_fields($settings['field_name']);
$field_types = _content_field_types();
$function = $field_types[$field['type']]['module'] .'_field';
if (function_exists($function)) {
$form['#node'] = (object)array('type' => '', $settings['field_name'] => $form_state['values'][$settings['field_name']]);
$items = isset($form['#node']->$field['field_name']) ? $form['#node']->$field['field_name'] : array();
//Make sure AHAH 'add more' button isn't sent to the fields
// for processing.
unset($items[$field['field_name'] .'_add_more']);
$function('validate', $form['#node'], $field, $items, $form, NULL);
content_field('validate', $form['#node'], $field, $items, $form, NULL);
}
}
}
function content_rules_action_populate_field_submit(&$settings, $form, &$form_state) {
// Take over field values and filter out private properties added by CCK
$settings['value'] = array_filter($form_state['values'][$settings['field_name']], 'is_array');
foreach ($settings['value'] as $key => $data) {
foreach (array_filter(array_keys($data)) as $col) {
if ($col[0] == '_') {
unset($settings['value'][$key][$col]);
}
}
if ($key && count(array_filter($settings['value'][$key])) == 0) {
// For multi-valued fields don't check for any additional empty values.
unset($settings['value'][$key]);
}
}
$settings['code'] = $form_state['values']['code'];
if (function_exists('rules_action_custom_php_submit')) {
// Support adding variables to the php code, if php module is present.
rules_action_custom_php_submit($settings, $form, $form_state);
}
// Add all values to the input evaluator, so that textfields / textares can
// make use of it.
$names = array('code');
foreach ($settings['value'] as $key => $data) {
foreach (array_filter($data, 'is_string') as $col => $value) {
$names[] = "value|$key|$col";
}
}
$form_state['element']['#info']['eval input'] = $names;
}
/**
* Label callback: Improve the label of the action.
*/
function content_rules_action_populate_field_label($settings, $argument_labels) {
return t("Populate @node's field '@field'", array('@field' => $settings['field_name']) + $argument_labels);
}
function workflow_ng_action_populate_field_upgrade(&$element) {
$element['#name'] = 'content_rules_action_populate_field';
$element['#settings']['code'] = $element['#settings']['default_value_php'];
$element['#settings'][$element['#settings']['field_name']] = array();
unset($element['#settings']['default_value_php']);
}
/**
* Implementation of hook_rules_condition_info().
*/
function content_rules_condition_info() {
$info = array();
$info['content_rules_field_has_value'] = array(
'label' => t('Field has value'),
'arguments' => array(
'node' => array('type' => 'node', 'label' => t('Content')),
),
'eval input' => array('code'),
'help' => t('You should make sure that the used field exists in the given content type. The condition returns TRUE, if the selected field has the given value.'),
'module' => 'CCK',
);
$info['content_rules_field_changed'] = array(
'label' => t('Field has changed'),
'arguments' => array(
'node' => array('type' => 'node', 'label' => t('Content containing changes')),
'node_unchanged' => array('type' => 'node', 'label' => t('Content not containing changes')),
),
'help' => t('You should make sure that the used field exists in the given content type.'),
'module' => 'CCK',
);
return $info;
}
/**
* Condition: Check the value of a field.
*/
function content_rules_field_has_value($node, $settings) {
// Get information about the field.
$field = content_fields($settings['field_name'], $node->type);
$value = _content_rules_get_field_value($settings, $state);
if (empty($field) || !is_array($value)) {
return FALSE;
}
return _content_rules_field_has_value($node->$settings['field_name'], $value);
}
/**
* Use the same configuration form as the "populate field" action.
*/
function content_rules_field_has_value_form($settings, &$form, &$form_state) {
content_rules_action_populate_field_form($settings, $form, $form_state);
}
function content_rules_field_has_value_validate($form, &$form_state) {
content_rules_action_populate_field_validate($form, $form_state);
}
function content_rules_field_has_value_submit(&$settings, $form, &$form_state) {
content_rules_action_populate_field_submit($settings, $form, $form_state);
}
function content_rules_field_has_value_label($settings, $argument_labels) {
return t("@node's field '@field' has value", array('@field' => $settings['field_name']) + $argument_labels);
}
/**
* Condition: Check if the field has changed.
*/
function content_rules_field_changed($node1, $node2, $settings) {
// Get information about the field.
$field = content_fields($settings['field_name'], $node1->type);
return !empty($field) && !_content_rules_field_has_value($node1->$settings['field_name'], $node2->$settings['field_name']);
}
function content_rules_field_changed_form($settings, &$form, &$form_state) {
$settings += array('field_name' => '');
$form['settings']['field_name'] = array(
'#type' => 'select',
'#title' => t('Field'),
'#options' => content_rules_get_field_names_by_type(),
'#default_value' => $settings['field_name'],
'#description' => t('Select the machine-name of the field to look at.'),
'#required' => TRUE,
);
}
function content_rules_field_changed_label($settings, $argument_labels) {
return t("@node's field '@field' has been changed", array('@field' => $settings['field_name']) + $argument_labels);
}
/**
* Returns the fields of a given field type only.
* Suitable for using it with #options.
*/
function content_rules_get_field_names_by_type($type = NULL) {
$fields = array();
foreach (content_fields() as $field) {
if (!isset($type) || $field['type'] == $type) {
$fields[$field['field_name']] = $field['field_name'];
}
}
asort($fields);
return $fields;
}
function _content_rules_get_field_value($settings, &$state) {
if ($settings['code']) {
if (function_exists('rules_input_evaluator_php_apply')) {
// Support adding variables to the php code, if php module is present.
$value = rules_input_evaluator_php_apply($settings['code'], $settings['vars'], $state, FALSE);
}
else {
ob_start();
$value = eval($settings['code']);
ob_end_clean();
}
}
else {
$value = $settings['value'];
}
return $value;
}
/**
* Checks whether both field values match in a robust way.
*
* It returns TRUE, only if the number of multiple values matches and
* each property of the cck field's value is the same in the node.
*
* @param $node_value The value present in the node.
* @param $value The value to check for.
*/
function _content_rules_field_has_value($node_value, $value) {
if (count($value) != count($node_value)) {
return FALSE;
}
// Loop over multiple fields
foreach ($value as $delta => $sub_value) {
// Check if all properties of the value are there in the node value too
if (is_array($sub_value) && is_array($node_value[$delta])) {
if (count(array_diff_assoc($sub_value, $node_value[$delta])) != 0) {
return FALSE;
}
}
elseif ($sub_value !== $node_value[$delta]) {
return FALSE;
}
}
return TRUE;
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Implementation of hook_content_build_modes
* (on behalf of token.module)
*/
function token_content_build_modes() {
return array(
'token' => array(
'title' => t('Token'),
'build modes' => array(
'token' => array(
'title' => t('Token'),
'views style' => FALSE,
),
),
),
);
}
// Two helper functions that generate appropriate tokens for CCK-added fields.
function content_token_values($type, $object = NULL, $options = array()) {
$tokens = array();
if ($type == 'node') {
// Prevent against invalid 'nodes' built by broken 3rd party code.
if (isset($object->type)) {
// Let PHP free the $node object when we are done. Working directly on the
// incoming $object causes memory leak issues on long-running scripts such
// as migrations. See http://drupal.org/node/736440.
$node = drupal_clone($object);
$content_type = content_types($node->type);
$node->build_mode = 'token';
$node->content = array();
content_view($node);
// The formatted values will only be known after the content has been rendered.
drupal_render($node->content);
content_alter($node);
$field_types = _content_field_types();
foreach ($content_type['fields'] as $field_name => $field) {
$items = isset($node->{$field_name}) ? $node->{$field_name} : array();
$function = $field_types[$field['type']]['module'] . '_token_values';
if (!empty($items) && function_exists($function)) {
$token_values = (array) $function('field', $items, $options);
foreach ($token_values as $token => $value) {
$tokens[$field_name .'-'. $token] = $value;
}
}
}
}
}
return $tokens;
}
function content_token_list($type = 'all') {
if ($type == 'node' || $type == 'all') {
$list = array();
$field_types = _content_field_types();
foreach (content_fields() as $field) {
$sub_list = array();
$function = $field_types[$field['type']]['module'] . '_token_list';
if (function_exists($function)) {
$sub_list = $function('field');
foreach ($sub_list as $category => $token_list) {
foreach ($token_list as $token => $description) {
$list['CCK '. $category][$field['field_name'] .'-'. $token] = $description;
}
}
}
}
return $list;
}
}

View file

@ -0,0 +1,214 @@
<?php
/**
* @file
* This file provides a CTools content type for fields.
*/
/**
* Callback function to supply a list of content types.
*/
function content_content_field_ctools_content_types() {
return array(
'title' => t('Content field'),
'defaults' => array('label' => '', 'formatter' => ''),
'content type' => 'content_content_field_content_type_content_type',
);
}
/**
* Return all field content types available.
*/
function content_content_field_content_type_content_types() {
// This will hold all the individual field content types.
$types = array();
// Get all fields on the site.
$field_types = _content_field_types();
foreach (content_types() as $type_name => $type) {
foreach ($type['fields'] as $field_name => $field) {
if (!isset($types[$field_name])) {
$types[$field_name] = array(
'category' => t('Node'),
'icon' => 'icon_cck_field.png',
'title' => t('Field: @widget_label (@field_name) - @field_type', array(
'@widget_label' => t($field['widget']['label']),
'@field_name' => $field_name,
'@field_type' => t($field_types[$field['type']]['label']),
)),
'description' => t('Field on the referenced node.'),
'types' => array(),
);
if (isset($field_types[$field['type']]['content_icon'])) {
$types[$field_name]['icon'] = $field_types[$field['type']]['content_icon'];
}
}
$types[$field_name]['types'][$type_name] = $type['name'];
}
}
// Create the required context for each field related to the content types.
foreach ($types as $field_name => $field_content_type) {
$types[$field_name]['required context'] = new ctools_context_required(t('Node'), 'node', array(
'type' => array_keys($types[$field_name]['types']),
));
unset($types[$field_name]['types']);
}
return $types;
}
/**
* Just one subtype.
*
* Ordinarily this function is meant to get just one subtype. However, we are
* using it to deal with the fact that we have changed the subtype names. This
* lets us translate the name properly.
*/
function content_content_field_content_type_content_type($subtype) {
// Previous versions of CCK included the content type as part of the subtype.
// This allows those to continue to sort of work, at least during render.
if (strpos($subtype, ':') !== FALSE) {
list($content_type, $subtype) = explode(':', $subtype, 2);
}
$types = content_content_field_content_type_content_types();
if (isset($types[$subtype])) {
return $types[$subtype];
}
}
/**
* Output function for the 'field' content type.
*/
function content_content_field_content_type_render($subtype, $conf, $panel_args, $context) {
// Previous versions of CCK included the content type as part of the subtype.
// This allows those to continue to sort of work, at least during render.
if (strpos($subtype, ':') !== FALSE) {
list($content_type, $subtype) = explode(':', $subtype, 2);
}
if (is_array($context)) {
$context = array_pop($context);
}
// If we do not have a node, then we cannot generate output.
if (!isset($context->data)) {
return;
}
$node = drupal_clone($context->data);
// Extract the node type from the node in context, the field name from the
// panels content type subtype, and get the content field structure.
$field_name = $subtype;
$field = content_fields($field_name, $node->type);
// Get the formatter that was selected in the settings dialog.
$formatter = $conf['formatter'];
// Check view access to the field.
if (!content_access('view', $field, NULL, $node)) {
return;
}
// Force panel settings into the field's display settings.
$field['display_settings']['label']['format'] = $conf['label'] == 'normal' || !empty($conf['override_title']) ? 'hidden' : $conf['label'];
$field['display_settings']['full']['format'] = $formatter;
$node->build_mode = NODE_BUILD_NORMAL;
// TODO : allow panel-specific template suggestions for content-field.tpl.php ?
$output = content_view_field($field, $node);
$block = new stdClass();
$block->module = 'content';
$block->delta = $field_name;
if ($conf['label'] == 'normal') {
$block->title = t($field['widget']['label']);
}
$block->content = $output;
return $block;
}
/**
* Returns a settings form for the custom type.
*/
function content_content_field_content_type_edit_form(&$form, &$form_state) {
$conf = $form_state['conf'];
$form['label'] = array(
'#type' => 'select',
'#title' => t('Field label'),
'#default_value' => isset($conf['label']) ? $conf['label'] : '',
'#options' => array(
'normal' => t('Block title'),
'above' => t('Above'),
'inline' => t('Inline'),
),
'#description' => t('Configure how the label is going to be displayed. This option takes no effect when "Override title" option is enabled, the specified block title is displayed instead.'),
);
// Extract the field name from the panels content type subtype.
$field_name = $form_state['subtype_name'];
// Previous versions of CCK included the content type as part of the subtype.
// This allows those to continue to sort of work.
if (strpos($field_name, ':') !== FALSE) {
list($content_type, $field_name) = explode(':', $field_name, 2);
}
// Get all the information about our field.
$field = content_fields($field_name);
// Get information about all the field types on the site.
$field_types = _content_field_types();
// Get the information about the type that our field is.
$type_info = $field_types[$field['type']];
// Put the possible formatters for our type into an array.
$options = array();
foreach ($type_info['formatters'] as $formatter_name => $formatter) {
$options[$formatter_name] = $formatter['label'];
}
$form['formatter'] = array(
'#type' => 'select',
'#title' => t('Field formatter'),
'#default_value' => isset($conf['formatter']) ? $conf['formatter'] : 'default',
'#options' => $options,
'#description' => t('Select a formatter.'),
'#required' => TRUE,
);
}
function content_content_field_content_type_edit_form_submit(&$form, &$form_state) {
// Copy everything from our defaults.
foreach (array_keys($form_state['plugin']['defaults']) as $key) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
/**
* Admin title for field content type.
*/
function content_content_field_content_type_admin_title($subtype, $conf, $context) {
// Previous versions of CCK included the content type as part of the subtype.
// This allows those to continue to sort of work, at least during render.
if (strpos($subtype, ':') !== FALSE) {
list($content_type, $subtype) = explode(':', $subtype, 2);
}
// Get all fields on the site.
$field_types = _content_field_types();
// Get all the information about our field.
$field = content_fields($subtype);
return t('"@s" field: @widget_label (@field_name) - @field_type', array(
'@s' => $context->identifier,
'@widget_label' => t($field['widget']['label']),
'@field_name' => $subtype,
'@field_type' => t($field_types[$field['type']]['label']),
));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

View file

@ -0,0 +1,374 @@
<?php
/**
* @file
* Interface between content.module and views.module.
*/
// Include the files defining the classes we extend.
// This is needed in case the /cck folder lives in the main
// /modules folder (views_module_include() will then load
// content.views.inc before node.views.inc)
module_load_include('inc', 'views', 'modules/node.views');
/**
* Implementation of hook_views_handlers().
*/
function content_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'content') . '/includes/views/handlers',
),
'handlers' => array(
// argument handlers
'content_handler_argument' => array(
'parent' => 'views_handler_argument',
),
'content_handler_argument_string' => array(
'parent' => 'views_handler_argument_string',
),
'content_handler_argument_numeric' => array(
'parent' => 'views_handler_argument_numeric',
),
'content_handler_argument_reference' => array(
'parent' => 'content_handler_argument_numeric',
),
'content_handler_argument_many_to_one' => array(
'parent' => 'views_handler_argument_many_to_one',
),
// field handlers
'content_handler_field' => array(
'parent' => 'views_handler_field_node',
),
'content_handler_field_multiple' => array(
'parent' => 'content_handler_field',
),
// filter handlers
'content_handler_filter_string' => array(
'parent' => 'views_handler_filter_string',
),
'content_handler_filter_numeric' => array(
'parent' => 'views_handler_filter_numeric',
),
'content_handler_filter_float' => array(
'parent' => 'views_handler_filter_float',
),
'content_handler_filter_many_to_one' => array(
'parent' => 'views_handler_filter_many_to_one',
),
// relationship handlers
'content_handler_relationship' => array(
'parent' => 'views_handler_relationship',
),
// sort handlers
'content_handler_sort' => array(
'parent' => 'views_handler_sort',
),
),
);
}
/**
* Implementation of hook_views_plugins.
*
* Defines some plugins used by the Views modes for
* nodereference and userreference.
*/
function content_views_plugins() {
$plugins = array(
'module' => 'content', // This just tells our themes are elsewhere.
'display' => array(
'content_simple' => array(
'path' => drupal_get_path('module', 'content') . '/includes/views/handlers',
// Those strings are not translated for now.
// We'll need to change that if / when we remove 'no ui'
'title' => 'Simple', // TODO: better name ? (currently not displayed anyway)
'help' => 'Destination-agnostic display. Mostly useful for programmatic views.',
'handler' => 'content_plugin_display_simple',
'no ui' => TRUE, // Programmatic use only.
'uses hook menu' => FALSE,
'use ajax' => FALSE,
'use pager' => FALSE,
'accept attachments' => FALSE,
),
'content_references' => array(
'path' => drupal_get_path('module', 'content') . '/includes/views/handlers',
// Those strings are not translated for now.
// We'll need to change that if / when we remove 'no ui'
'title' => 'Simple - for reference fields', // TODO: better name ? (currently not displayed anyway)
'help' => 'Destination-agnostic display. Mostly useful for programmatic views.',
'parent' => 'content_simple',
'handler' => 'content_plugin_display_references',
'no ui' => TRUE, // Programmatic use only.
'uses hook menu' => FALSE,
'use ajax' => FALSE,
'use pager' => FALSE,
'accept attachments' => FALSE,
),
),
'style' => array(
'content_php_array_autocomplete' => array(
'path' => drupal_get_path('module', 'content') . '/includes/views/handlers',
// Those strings are not translated for now.
// We'll need to change that if / when we remove 'no ui'
'title' => 'Results array (with title)',
'help' => 'Returns the view as a PHP array of names + rendered rows.',
'handler' => 'content_plugin_style_php_array_ac',
'no ui' => TRUE, // Programmatic use only.
'uses row plugin' => TRUE,
'uses fields' => TRUE,
'type' => 'content_simple',
'even empty' => TRUE,
),
),
);
return $plugins;
}
/**
* Implementation of hook_views_data().
*
* Exposes all fields to the views system.
*/
function content_views_data() {
$data = array();
foreach (content_fields() as $field) {
$module = $field['module'];
$result = (array) module_invoke($module, 'field_settings', 'views data', $field);
drupal_alter('field_settings', $result, 'views data', $field);
if (empty($result)) {
$result = content_views_field_views_data($field);
}
if (is_array($result)) {
$data = array_merge($data, $result);
}
}
return $data;
}
function content_views_field_views_data($field) {
$field_types = _content_field_types();
// Check the field module is available.
// TODO: is this really how we should do it ?
if (isset($field_types[$field['type']])) {
$db_info = content_database_info($field);
// Field modules that do not store data in the database
// should not create views data tables.
if (empty($db_info['columns'])) {
return;
}
$table_alias = content_views_tablename($field);
$types = array();
foreach (content_types() as $type) {
if (isset($type['fields'][$field['field_name']])) {
// TODO : run check_plain here instead of on the imploded string below ?
$types[] = $type['name'];
}
}
$data = array();
$data['table']['group'] = t('Content');
$data['table']['join']['node'] = array(
'table' => $db_info['table'],
'left_field' => 'vid',
'field' => 'vid',
);
$data['table']['join']['node_revisions'] = array(
'table' => $db_info['table'],
'left_field' => 'vid',
'field' => 'vid',
);
// Build the list of columns enabled for default views integration.
$db_columns = array();
$additional_fields = array();
foreach ($db_info['columns'] as $column => $attributes) {
// Select explicitly enabled field columns.
if (!empty($attributes['views'])) {
$db_columns[$column] = $attributes;
}
// Ensure all columns are retrieved.
$additional_fields[$attributes['column']] = $attributes['column'];
}
// Pick up the first column when none has been explicitly enabled
// (pre CCK 2.2 backwards compatibility).
if (empty($db_columns)) {
// Can't use array_slice(), it won't work in PHP4 for assoc array.
foreach ($db_info['columns'] as $column => $attributes) {
$db_columns[$column] = $attributes;
break;
}
}
$columns = array();
$db_fields = array();
$arguments = array();
$filters = array();
foreach ($db_columns as $column => $attributes) {
$columns[] = $column;
$db_fields[] = $attributes['column'];
$sorts[] = !empty($attributes['sortable']) ? TRUE : FALSE;
// Identify likely filters and arguments for each column based on field type.
switch ($attributes['type']) {
case 'int':
case 'mediumint':
case 'tinyint':
case 'bigint':
case 'serial':
$filters[] = 'content_handler_filter_numeric';
$arguments[] = 'content_handler_argument_numeric';
break;
case 'numeric':
case 'float':
$filters[] = 'content_handler_filter_float';
$arguments[] = 'content_handler_argument_numeric';
break;
case 'text':
case 'blob':
// TODO add markup handlers for these types
default:
$filters[] = 'content_handler_filter_string';
$arguments[] = 'content_handler_argument_string';
break;
}
}
// Provide automatic filters, sorts, and arguments for each column, not just the first.
$db_fields_count = count($db_fields);
foreach ($db_fields as $i => $db_field) {
$label_truncated = truncate_utf8(t($field['widget']['label']), 10, TRUE);
if ($db_fields_count == 1) {
$title = t('@label (!name)', array('@label' => t($field['widget']['label']), '!name' => $field['field_name']));
$title_short = check_plain($label_truncated);
}
else {
$title = t('@label (!name) - !column', array('@label' => t($field['widget']['label']), '!name' => $field['field_name'], '!column' => $columns[$i]));
$title_short = t('@label-truncated - !column', array('@label-truncated' => $label_truncated, '!column' => $columns[$i]));
}
$data[$db_field] = array(
'group' => t('Content'),
'title' => $title,
'title short' => $title_short,
'help' => t($field_types[$field['type']]['label']) .' - '. t('Appears in: @types', array('@types' => implode(', ', $types))),
);
if ($i == 0) {
$data[$db_field]['field'] = array(
'title' => t('@label (!name)', array('@label' => t($field['widget']['label']), '!name' => $field['field_name'])),
'title short' => check_plain($label_truncated),
'field' => $db_field,
'table' => $db_info['table'],
'handler' => 'content_handler_field_multiple',
'click sortable' => $sorts[$i],
'additional fields' => $additional_fields,
'content_field_name' => $field['field_name'],
'access callback' => 'content_access',
'access arguments' => array('view', $field),
);
}
$data[$db_field]['argument'] = array(
'field' => $db_field,
'table' => $db_info['table'],
'handler' => $arguments[$i],
'additional fields' => $additional_fields,
'content_field_name' => $field['field_name'],
'empty field name' => t('<No value>'),
);
$data[$db_field]['filter'] = array(
'field' => $db_field,
'table' => $db_info['table'],
'handler' => $filters[$i],
'additional fields' => $additional_fields,
'content_field_name' => $field['field_name'],
'allow empty' => TRUE,
);
if (!empty($sorts[$i])) {
$data[$db_field]['sort'] = array(
'field' => $db_field,
'table' => $db_info['table'],
'handler' => 'content_handler_sort',
'additional fields' => $additional_fields,
'content_field_name' => $field['field_name'],
);
}
}
// Expose additional delta column for multiple value fields.
if ($field['multiple']) {
$title = t('@label (!name) - delta', array('@label' => t($field['widget']['label']), '!name' => $field['field_name']));
$title_short = t('@label-truncated - delta', array('@label-truncated' => $label_truncated));
$db_field = 'delta';
$data[$db_field] = array(
'group' => t('Content'),
'title' => $title,
'title short' => $title_short,
'help' => t('Delta - Appears in: @types', array('@types' => implode(', ', $types))),
);
$data[$db_field]['field'] = array(
'title' => $title,
'title short' => $title_short,
'field' => $db_field,
'table' => $db_info['table'],
'handler' => 'views_handler_field_numeric',
'click sortable' => TRUE,
'additional fields' => $additional_fields,
'access callback' => 'content_access',
'access arguments' => array('view', $field),
);
$data[$db_field]['argument'] = array(
'field' => $db_field,
'table' => $db_info['table'],
'handler' => 'views_handler_argument_numeric',
'additional fields' => $additional_fields,
'empty field name' => t('<No value>'),
);
$data[$db_field]['filter'] = array(
'field' => $db_field,
'table' => $db_info['table'],
'handler' => 'views_handler_filter_numeric',
'additional fields' => $additional_fields,
'allow empty' => TRUE,
);
$data[$db_field]['sort'] = array(
'field' => $db_field,
'table' => $db_info['table'],
'handler' => 'views_handler_sort',
'additional fields' => $additional_fields,
);
}
return array($table_alias => $data);
}
}
/**
* Helper function so it is possible to change the Views tablename
* in the future without re-writing code.
*/
function content_views_tablename($field) {
return 'node_data_'. $field['field_name'];
}
function theme_content_view_multiple_field($items, $field, $values) {
$output = '';
$i = 0;
foreach ($items as $item) {
if (!empty($item) || $item == '0') {
$output .= '<div class="field-item field-item-'. $i .'">'. $item .'</div>';
$i++;
}
}
return $output;
}

View file

@ -0,0 +1,71 @@
<?php
/**
* @file
* Field conversion for fields handled by this module.
*/
/**
* Implementation of hook_views_convert().
*
* Intervene to convert field values from the Views 1 format to the
* Views 2 format. Intervene only if $view->add_item() won't produce
* the right results, usually needed to set field options or values.
*/
function content_views_convert($display, $type, &$view, $views_field) {
static $views_fields;
if (empty($views_fields)) {
$views_fields = array();
$types = content_types();
foreach ($types as $ctype) {
foreach ($ctype['fields'] as $field) {
$module = $field['module'];
$result = (array) module_invoke($module, 'field_settings', 'views data', $field);
drupal_alter('field_settings', $result, 'views data', $field);
if (empty($result)) {
// The views field name had the column name appended,
// like field_name_value or field_username_uid.
$column = array_shift(array_keys($field['columns']));
$views_fields[$field['field_name'] .'_'. $column] = $field;
}
}
}
}
// Is this a field that CCK should handle? If not, return.
if (!in_array($views_field['field'], array_keys($views_fields))) {
return;
}
// Now update values, options, etc. to those selected in the imported view.
switch ($type) {
case 'field':
$view->display[$display]->display_options['fields'][$views_field['field']]['format'] = $views_field['options'];
if ($views_field['handler'] == 'content_views_field_handler_group') {
$view->display[$display]->display_options['fields'][$views_field['field']]['multiple']['group'] = 1;
}
else {
$view->display[$display]->display_options['fields'][$views_field['field']]['multiple']['group'] = 0;
}
return;
case 'filter':
// TODO
return;
case 'exposed_filter':
// TODO
return;
case 'argument':
// TODO
return;
case 'sort':
// TODO
break;
}
return;
}

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* The subclass simply adds properties,
* for field-specific subclasses to use if they need to.
*/
class content_handler_argument extends views_handler_argument {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* @file
* Handler for 'content_handler_argument_many_to_one' style.
*/
class content_handler_argument_many_to_one extends views_handler_argument_many_to_one {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
function summary_name($data) {
$options = $this->allowed_values();
$value = $data->{$this->name_alias};
if (isset($options[$value])) {
$value = $options[$value];
}
else {
$value = parent::summary_name($data);
}
return $value;
}
function title_query() {
$options = $this->allowed_values();
$values = $this->value;
foreach ($values as $key => $value) {
if (isset($options[$value])) {
$values[$key] = $options[$value];
}
}
return $values;
}
function allowed_values() {
$field = $this->content_field;
$function = $field['module'] .'_allowed_values';
$options = function_exists($function) ? $function($field) : content_allowed_values($field);
return (array) $options;
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* The subclass simply adds properties,
* for field-specific subclasses to use if they need to.
*/
class content_handler_argument_numeric extends views_handler_argument_numeric {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* @file
* Provide handler to replace reference with title.
*/
class content_handler_argument_reference extends content_handler_argument_numeric {
/**
* Override the behavior of title().
*/
function title_query() {
$titles = array();
$placeholders = implode(', ', array_fill(0, sizeof($this->value), '%d'));
$table_data = views_fetch_data($this->name_table);
$table = array_shift($table_data['table']['join']);
$result = db_query("SELECT $this->name_field AS title FROM {". $table['table'] ."} WHERE ". $table['field'] ." IN ($placeholders)", $this->value);
while ($row = db_fetch_object($result)) {
$titles[] = check_plain($row->title);
}
return $titles;
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* The subclass simply adds properties,
* for field-specific subclasses to use if they need to.
*/
class content_handler_argument_string extends views_handler_argument_string {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
}

View file

@ -0,0 +1,228 @@
<?php
/**
* @file
* The subclass adds basic field and formatter info,
* for field-specific subclasses to use if they need to.
*
* Fields could extend this class if they want field and formatter handling
* but don't want the multiple value grouping options created by
* content_handler_field_multiple.
*/
class content_handler_field extends views_handler_field_node {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
}
function init(&$view, $options) {
$field = $this->content_field;
parent::init($view, $options);
if ($field['multiple']) {
$this->additional_fields['delta'] = 'delta';
}
// Make sure we grab enough information to build a pseudo-node with enough
// credentials at render-time.
$this->additional_fields['type'] = array('table' => 'node', 'field' => 'type');
$this->additional_fields['nid'] = array('table' => 'node', 'field' => 'nid');
$this->additional_fields['vid'] = array('table' => 'node', 'field' => 'vid');
}
function option_definition() {
$options = parent::option_definition();
$field = $this->content_field;
// Override views_handler_field_node's default label
$options['label'] = array('default' => '', 'translatable' => TRUE);
$options['label_type'] = array('default' => 'widget');
$options['format'] = array('default' => 'default');
return $options;
}
/**
* Provide formatter option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// TODO: do we want the 'link to node' checkbox ?
// That's usually formatters business...
$field = $this->content_field;
$options = $this->options;
$form['label_type'] = array(
'#title' => t('Label'),
'#type' => 'radios',
'#options' => array(
'none' => t('None'),
'widget' => t('Widget label (@label)', array('@label' => $field['widget']['label'])),
'custom' => t('Custom'),
),
'#default_value' => $options['label_type'],
'#weight' => 2,
);
$form['label'] = array(
'#title' => t('Custom label'),
'#type' => 'textfield',
'#default_value' => $options['label'],
'#process' => array('views_process_dependency'),
'#dependency' => array('radio:options[label_type]' => array('custom')),
'#weight' => 3,
);
$field_types = _content_field_types();
$formatters = array();
if (is_array($field_types[$field['type']]['formatters'])) {
foreach ($field_types[$field['type']]['formatters'] as $name => $info) {
$formatters[$name] = $info['label'];
}
}
$form['format'] = array(
'#title' => t('Format'),
'#type' => 'select',
'#options' => $formatters,
'#required' => TRUE,
'#default_value' => $options['format'],
'#weight' => 4,
);
}
/**
* Make sure some value is stored as a label.
*
* Don't use t(), since Views' views_handler_field already has
* $this->options['label'] marked as a translatable field.
*
* @see http://drupal.org/node/285470
*/
function options_submit($form, &$form_state) {
switch ($form_state['values']['options']['label_type']) {
case 'none':
$form_state['values']['options']['label'] = '';
break;
case 'widget':
$form_state['values']['options']['label'] = $this->content_field['widget']['label'];
break;
}
}
/**
* @TODO
* Now that we save the label in the submit process above we could
* get rid of this function. Leave it here for now to be sure the
* label works for fields that haven't been updated since this
* change was made, since $this->options['label'] will be missing a
* value until it is updated in the view.
*
* Don't use t(), since Views' views_handler_field already has
* $this->options['label'] marked as a translatable field.
*/
function label() {
$field = $this->content_field;
switch ($this->options['label_type']) {
case 'none':
return '';
case 'widget':
return $field['widget']['label'];
default:
return $this->options['label'];
}
}
/**
* Return DIV or SPAN based upon the field's element type.
*/
function element_type($none_supported = FALSE, $default_empty = FALSE) {
// The 'element_type' property denotes Views 3.x ('semantic views'
// functionnality). If the property is set, and not set to '' ("default"),
// let the generic method handle the output.
if (isset($this->options['element_type']) && $this->options['element_type'] !== '') {
return parent::element_type($none_supported, $default_empty);
}
if ($default_empty) {
return '';
}
if (isset($this->definition['element type'])) {
return $this->definition['element type'];
}
// TODO Figure out exactly when to return a div or a <span>. Any field
// that ever needs to be shown inline in Views UI. It needs to return
// a div for textareas to prevent wrapping a <span> around a <p>.
// Earl says we need to be sure that other fields we don't know
// about won't end up wrapping a span around a block-level element.
if ($this->content_field['widget']['type'] == 'text_textarea') {
return 'div';
}
else {
return 'span';
}
}
function options_validate($form, &$form_state) { }
/**
* Provide text for the administrative summary
*/
function admin_summary() {
// Display the formatter name.
$field = $this->content_field;
$field_types = _content_field_types();
if (isset($field_types[$field['type']]['formatters'][$this->options['format']])) {
return t($field_types[$field['type']]['formatters'][$this->options['format']]['label']);
}
}
function render($values) {
// We're down to a single node here, so we can retrieve the actual field
// definition for the node type being considered.
$field = content_fields($this->content_field['field_name'], $values->{$this->aliases['type']});
// If the field does not appear in the node type, then we have no value
// to display, and can just return.
if (empty($field)) {
return '';
}
$options = $this->options;
$db_info = content_database_info($field);
// Build a pseudo-node from the retrieved values.
$node = drupal_clone($values);
$node->type = $values->{$this->aliases['type']};
$node->nid = $values->{$this->aliases['nid']};
$node->vid = $values->{$this->aliases['vid']};
// Some formatters need to behave differently depending on the build_mode
// (for instance: preview), so we provide one.
$node->build_mode = NODE_BUILD_NORMAL;
$item = array();
foreach ($db_info['columns'] as $column => $attributes) {
$item[$column] = $values->{$this->aliases[$attributes['column']]};
}
$item['#delta'] = $field['multiple'] ? $values->{$this->aliases['delta']} : 0;
// Render items.
$formatter_name = $options['format'];
if ($formatter = _content_get_formatter($formatter_name, $field['type'])) {
if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) {
// Single-value formatter.
$output = content_format($field, $item, $formatter_name, $node);
}
else {
// Multiple values formatter - we actually have only one value to display.
$output = content_format($field, array($item), $formatter_name, $node);
}
return $this->render_link($output, $values);
}
return '';
}
}

View file

@ -0,0 +1,321 @@
<?php
/**
* @file
* An extended subclass for field handling that adds multiple field grouping.
*
* Fields that want multiple value grouping options in addition to basic
* field and formatter handling can extend this class.
*/
class content_handler_field_multiple extends content_handler_field {
var $defer_query;
function init(&$view, $options) {
$field = $this->content_field;
parent::init($view, $options);
$this->defer_query = !empty($options['multiple']['group']) && $field['multiple'];
if ($this->defer_query) {
// Grouped field: ditch the existing additional_fields (field columns + delta).
// In the main query we'll only need:
// - vid, which will be used to retrieve the actual values in pre_render,
// - node type and nid, which wil be used in the pseudo-node used when
// rendering.
$this->additional_fields = array(
'type' => array('table' => 'node', 'field' => 'type'),
'nid' => array('table' => 'node', 'field' => 'nid'),
);
if ($view->base_table == 'node_revisions') {
$this->additional_fields['vid'] = array('table' => 'node_revisions', 'field' => 'vid');
}
else {
$this->additional_fields['vid'] = array('table' => 'node', 'field' => 'vid');
}
}
}
function option_definition() {
$options = parent::option_definition();
$options['multiple'] = array(
'contains' => array(
'group' => array('default' => TRUE),
'multiple_number' => array('default' => ''),
'multiple_from' => array('default' => ''),
'multiple_reversed' => array('default' => FALSE),
),
);
return $options;
}
/**
* Provide 'group multiple values' option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$field = $this->content_field;
$options = $this->options;
$form['multiple'] = array(
'#access' => $field['multiple'],
'#weight' => 1,
);
$form['multiple']['group'] = array(
'#title' => t('Group multiple values'),
'#type' => 'checkbox',
'#default_value' => $options['multiple']['group'],
'#description' => t('If unchecked, each item in the field will create a new row, which may appear to cause duplicates. This setting is not compatible with click-sorting in table displays.'),
);
// Make the string translatable by keeping it as a whole rather than
// translating prefix and suffix separately.
list($prefix, $suffix) = explode('@count', t('Show @count value(s)'));
$form['multiple']['multiple_number'] = array(
'#type' => 'textfield',
'#size' => 5,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $options['multiple']['multiple_number'],
'#prefix' => '<div class="container-inline">',
'#process' => array('views_process_dependency'),
'#dependency' => array('edit-options-multiple-group' => array(TRUE)),
);
list($prefix, $suffix) = explode('@count', t('starting from @count'));
$form['multiple']['multiple_from'] = array(
'#type' => 'textfield',
'#size' => 5,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $options['multiple']['multiple_from'],
'#process' => array('views_process_dependency'),
'#dependency' => array('edit-options-multiple-group' => array(TRUE)),
'#description' => t('(first item is 0)'),
);
$form['multiple']['multiple_reversed'] = array(
'#title' => t('Reversed'),
'#type' => 'checkbox',
'#default_value' => $options['multiple']['multiple_reversed'],
'#suffix' => '</div>',
'#process' => array('views_process_dependency'),
'#dependency' => array('edit-options-multiple-group' => array(TRUE)),
'#description' => t('(start from last values)'),
);
}
/**
* Determine if this field is click sortable.
*/
function click_sortable() {
$field = $this->content_field;
$options = $this->options;
// Grouped fields are not click-sortable.
return !empty($this->definition['click sortable']) && !$this->defer_query;
}
function query() {
// If this is not a grouped field, use the generic query().
if (!$this->defer_query) {
return parent::query();
}
// Grouped field: do NOT call ensure_my_table, only add additional fields.
$this->add_additional_fields();
$this->field_alias = $this->aliases['vid'];
}
function pre_render($values) {
// If there are no values to render (displaying a summary, or query returned no results),
// or if this is not a grouped field, do nothing specific.
if (isset($this->view->build_info['summary']) || empty($values) || !$this->defer_query) {
return parent::pre_render($values);
}
$field = $this->content_field;
$db_info = content_database_info($field);
$options = $this->options;
// Build the list of vids to retrieve.
// TODO: try fetching from cache_content first ??
$vids = array();
$this->field_values = array();
foreach ($values as $result) {
if (isset($result->{$this->field_alias})) {
$vids[] = $result->{$this->field_alias};
}
}
// It may happend that the multiple values field is related to a non
// required relation for which no node data related to the field being
// processed here is available.
if (empty($vids)) {
return parent::pre_render($values);
}
// List columns to retrieve.
$alias = content_views_tablename($field);
// Prefix aliases with '_' to avoid clashing with field columns names.
$query_columns = array(
'vid AS _vid',
"delta as _delta",
// nid is needed to generate the links for 'link to node' option.
'nid AS _nid',
);
// The actual field columns.
foreach ($db_info['columns'] as $column => $attributes) {
$query_columns[] = "$attributes[column] AS $column";
}
$query = 'SELECT '. implode(', ', $query_columns) .
' FROM {'. $db_info['table'] ."}".
" WHERE vid IN (". implode(',', $vids) .')'.
" ORDER BY _nid ASC, _delta ". ($options['multiple']['multiple_reversed'] ? 'DESC' : 'ASC');
$result = db_query($query);
while ($item = db_fetch_array($result)) {
// Clean up the $item from vid and delta. We keep nid for now.
$vid = $item['_vid'];
unset($item['_vid']);
$delta = !empty($item['_delta']) ? $item['_delta'] : 0;
$item['#delta'] = $item['_delta'];
unset($item['_delta']);
$this->field_values[$vid][$delta] = $item;
}
}
/**
* Return DIV or SPAN based upon the field's element type.
*
* Fields rendered with the 'group multiple' option use <div> markers,
* and thus shouldn't be wrapped in a <span>.
*/
function element_type($none_supported = FALSE, $default_empty = FALSE) {
// If this is not a grouped field, use the parent method.
if (!$this->defer_query) {
return parent::element_type($none_supported, $default_empty);
}
// The 'element_type' property denotes Views 3.x ('semantic views'
// functionnality). If the property is set, and not set to '' ("default"),
// let the generic method handle the output.
if (isset($this->options['element_type']) && $this->options['element_type'] !== '') {
return parent::element_type($none_supported, $default_empty);
}
if ($default_empty) {
return '';
}
if (isset($this->definition['element type'])) {
return $this->definition['element type'];
}
return 'div';
}
function render($values) {
// If this is not a grouped field, use content_handler_field::render().
if (!$this->defer_query) {
return parent::render($values);
}
// We're down to a single node here, so we can retrieve the actual field
// definition for the node type being considered.
$field = content_fields($this->content_field['field_name'], $values->{$this->aliases['type']});
// If the field does not appear in the node type, then we have no value
// to display, and can just return.
if (empty($field)) {
return '';
}
$options = $this->options;
$vid = $values->{$this->field_alias};
if (isset($this->field_values[$vid])) {
// Gather items, respecting the 'Display n values starting from m' settings.
$count_skipped = 0;
$items = array();
foreach ($this->field_values[$vid] as $item) {
if (empty($options['multiple']['multiple_from']) || ($count_skipped >= $options['multiple']['multiple_from'])) {
if (empty($options['multiple']['multiple_number']) || (count($items) < $options['multiple']['multiple_number'])) {
// Grab the nid - needed for render_link().
$nid = $item['_nid'];
unset($item['_nid']);
$items[] = $item;
}
else {
break;
}
}
$count_skipped++;
}
// Build a pseudo-node from the retrieved values.
$node = drupal_clone($values);
// content_format and formatters will need a 'type'.
$node->type = $values->{$this->aliases['type']};
$node->nid = $values->{$this->aliases['nid']};
$node->vid = $values->{$this->aliases['vid']};
// Some formatters need to behave differently depending on the build_mode
// (for instance: preview), so we provide one.
$node->build_mode = NODE_BUILD_NORMAL;
// Render items.
$formatter_name = $options['format'];
if ($items && ($formatter = _content_get_formatter($formatter_name, $field['type']))) {
$rendered = array();
if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) {
// Single-value formatter.
foreach ($items as $item) {
$output = content_format($field, $item, $formatter_name, $node);
if (!empty($output)) {
$rendered[] = $this->render_link($output, (object) array('nid' => $nid));
}
}
}
else {
// Multiple values formatter.
$output = content_format($field, $items, $formatter_name, $values);
if (!empty($output)) {
$rendered[] = $this->render_link($output, (object) array('nid' => $nid));
}
}
if (count($rendered) > 1) {
// TODO: could we use generic field display ?
return theme('content_view_multiple_field', $rendered, $field, $values);
}
elseif ($rendered) {
return $rendered[0];
}
}
}
return '';
}
function render_link($data, $values) {
if (!$this->defer_query) {
return parent::render_link($data, $values);
}
if (!empty($this->options['link_to_node']) && $data !== NULL && $data !== '') {
if (method_exists('render_as_link', 'views_handler_field')) {
// Views 2.3+
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = "node/" . $values->{$this->aliases['nid']};
}
else {
// Views up to 2.2
return l($data, "node/" . $values->nid, array('html' => TRUE));
}
}
else {
return $data;
}
}
}

View file

@ -0,0 +1,25 @@
<?php
/**
* @file
* The subclass simply adds properties,
* for field-specific subclasses to use if they need to.
*/
// Ensure compatibility with Views pre 2.4, where the
// views_handler_filter_float class lived in views_handler_filter_numeric.inc.
if (!class_exists('views_handler_filter_float')) {
// Manually include the parent class.
$definition = views_fetch_handler_data('views_handler_filter_numeric');
views_include_handler($definition, 'handler');
}
class content_handler_filter_float extends views_handler_filter_float {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* The subclass simply adds properties,
* for field-specific subclasses to use if they need to.
*/
class content_handler_filter_many_to_one extends views_handler_filter_many_to_one {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
$field = $this->content_field;
$this->value_title = $field['widget']['label'];
}
function get_value_options() {
$this->value_options = $this->allowed_values();
}
// Get allowed values from hook_allowed_values(), if any,
// or from content_allowed_values();
function allowed_values() {
$field = $this->content_field;
$function = $field['module'] .'_allowed_values';
if ($this->value_form_type == 'select') {
// Select elements accept multidimensional arrays to support optgroups.
$options = function_exists($function) ? $function($field) : content_allowed_values($field, FALSE);
// For selects, HTML should be filtered out and entities left unencoded.
// See content_allowed_values / content_filter_xss / filter_xss.
content_allowed_values_filter_html($options);
}
else {
$options = function_exists($function) ? $function($field) : content_allowed_values($field);
}
return (array) $options;
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* @file
* The subclass simply adds properties,
* for field-specific subclasses to use if they need to.
*/
class content_handler_filter_numeric extends views_handler_filter_numeric {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* @file
* The subclass simply adds properties,
* for field-specific subclasses to use if they need to.
*/
class content_handler_filter_string extends views_handler_filter_string {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Handles content relationships and deals properly with multiple
* values by allowing the views administrator to select deltas.
*/
class content_handler_relationship extends views_handler_relationship {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
}
function option_definition() {
$options = parent::option_definition();
$options['delta'] = array('default' => -1);
return $options;
}
/**
* Add a delta selector for multiple fields.
*/
function options_form(&$form, &$form_state) {
$field = $this->content_field;
parent::options_form($form, $form_state);
// Only add the form gadget if the field is multiple.
if ($field['multiple']) {
$max_delta = $field['multiple'];
// 1 means unlimited.
if ($max_delta == 1) {
$max_delta = 10;
}
$options = array('-1' => t('All'));
for ($i = 0; $i < $max_delta; $i++) {
$options[$i] = $i + 1;
}
$form['delta'] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => $this->options['delta'],
'#title' => t('Delta'),
'#description' => t('The delta allows you to select which item in a multiple value field to key the relationship off of. Select "1" to use the first item, "2" for the second item, and so on. If you select "All", each item in the field will create a new row, which may appear to cause duplicates.'),
);
}
}
function ensure_my_table() {
if (!isset($this->table_alias)) {
$join = $this->get_join();
if (!isset($join->extra)) {
$join->extra = array();
}
$delta = isset($this->options['delta']) ? $this->options['delta'] : -1;
if ($delta != -1) {
$join->extra[] = array(
'field' => 'delta',
'value' => $delta,
'numeric' => TRUE,
);
}
$this->table_alias = $this->query->add_table($this->table, $this->relationship, $join);
}
return $this->table_alias;
}
}

View file

@ -0,0 +1,73 @@
<?php
/**
* @file
* Handles sorts and deals properly with multiple
* values by allowing the views administrator to select deltas.
*/
class content_handler_sort extends views_handler_sort {
var $content_field;
function construct() {
parent::construct();
$this->content_field = content_fields($this->definition['content_field_name']);
$this->additional_fields = $this->definition['additional fields'];
}
function option_definition() {
$options = parent::option_definition();
$options['delta'] = array('default' => -1);
return $options;
}
/**
* Add a delta selector for multiple fields.
*/
function options_form(&$form, &$form_state) {
$field = $this->content_field;
parent::options_form($form, $form_state);
// Only add the form gadget if the field is multiple.
if ($field['multiple']) {
$max_delta = $field['multiple'];
// 1 means unlimited.
if ($max_delta == 1) {
$max_delta = 10;
}
$options = array('-1' => t('All'));
for ($i = 0; $i < $max_delta; $i++) {
$options[$i] = $i + 1;
}
$form['delta'] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => $this->options['delta'],
'#title' => t('Delta'),
'#description' => t('The delta allows you to select which item in a multiple value field will be used for sorting. Select "1" to use the first item, "2" for the second item, and so on. If you select "All", each item in the field will create a new row, which may appear to cause duplicates.'),
);
}
}
function ensure_my_table() {
if (!isset($this->table_alias)) {
$join = $this->get_join();
if (!isset($join->extra)) {
$join->extra = array();
}
$delta = isset($this->options['delta']) ? $this->options['delta'] : -1;
if ($delta != -1) {
$join->extra[] = array(
'field' => 'delta',
'value' => $delta,
'numeric' => TRUE,
);
}
$this->table_alias = $this->query->ensure_table($this->table, $this->relationship, $join);
}
return $this->table_alias;
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Handler for 'content_simple' display.
*/
class content_plugin_display_simple extends views_plugin_display {
function execute() {
return $this->view->render($this->display->id);
}
function render() {
return !empty($this->view->result) || !empty($this->view->style_plugin->definition['even empty']) ? $this->view->style_plugin->render($this->view->result) : '';
}
function uses_exposed() {
return FALSE;
}
}
class content_plugin_display_references extends content_plugin_display_simple {
function query() {
$options = $this->get_option('content_options');
if ($options['string'] !== '') {
$like = $GLOBALS["db_type"] == 'pgsql' ? "ILIKE" : "LIKE";
$match_clauses = array(
'contains' => "$like '%%%s%%'",
'equals' => "= '%s'",
'starts_with' => "$like '%s%%'",
);
$clause = isset($match_clauses[$options['match']]) ? $match_clauses[$options['match']] : $match_clauses['contains'];
$alias = $this->view->query->ensure_table($options['table']);
$this->view->query->add_where(NULL, "$alias.$options[field_string] $clause", $options['string']);
}
elseif ($options['ids']) {
$alias = $this->view->query->ensure_table($options['table']);
$this->view->query->add_where(NULL, "$alias.$options[field_id] IN (" . db_placeholders($options['ids']) . ')', $options['ids']);
}
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Handler for 'content_php_array_autocomplete' style.
*/
class content_plugin_style_php_array_ac extends views_plugin_style {
function render() {
$results = array();
// Group the rows according to the grouping field, if specified.
$sets = $this->render_grouping($this->view->result, $this->options['grouping']);
$base_field = $this->view->base_field;
$title_field = $this->display->display_options['content_title_field'];
$title_field_alias = $this->view->field[$title_field]->field_alias;
// TODO : We don't display grouping info for now.
// Could be useful for select widget, though.
$this->view->row_index = 0;
foreach ($sets as $title => $records) {
foreach ($records as $label => $row) {
$results[$row->{$base_field}] = array(
'title' => $row->{$title_field_alias},
'rendered' => $this->row_plugin->render($row),
);
$this->view->row_index++;
}
}
unset($this->view->row_index);
return $results;
}
}

View file

@ -0,0 +1,79 @@
Drupal.behaviors.cckManageFields = function(context) {
attachUpdateSelects(context);
};
function attachUpdateSelects(context) {
var widgetTypes = Drupal.settings.contentWidgetTypes;
var fields = Drupal.settings.contentFields;
// Store the default text of widget selects.
$('#content-field-overview .content-widget-type-select', context).each(function() {
this.initialValue = this.options[0].text;
});
// 'Field type' select updates its 'Widget' select.
$('#content-field-overview .content-field-type-select', context).each(function() {
this.targetSelect = $('.content-widget-type-select', $(this).parents('tr').eq(0));
$(this).change(function() {
var selectedFieldType = this.options[this.selectedIndex].value;
var options = (selectedFieldType in widgetTypes) ? widgetTypes[selectedFieldType] : [ ];
this.targetSelect.contentPopulateOptions(options);
});
// Trigger change on initial pageload to get the right widget options
// when field type comes pre-selected (on failed validation).
$(this).trigger('change');
});
// 'Existing field' select updates its 'Widget' select and 'Label' textfield.
$('#content-field-overview .content-field-select', context).each(function() {
this.targetSelect = $('.content-widget-type-select', $(this).parents('tr').eq(0));
this.targetTextfield = $('.content-label-textfield', $(this).parents('tr').eq(0));
$(this).change(function(e, updateText) {
var updateText = (typeof(updateText) == 'undefined') ? true : updateText;
var selectedField = this.options[this.selectedIndex].value;
var selectedFieldType = (selectedField in fields) ? fields[selectedField].type : null;
var selectedFieldWidget = (selectedField in fields) ? fields[selectedField].widget : null
var options = (selectedFieldType && (selectedFieldType in widgetTypes)) ? widgetTypes[selectedFieldType] : [ ];
this.targetSelect.contentPopulateOptions(options, selectedFieldWidget);
if (updateText) {
$(this.targetTextfield).attr('value', (selectedField in fields) ? fields[selectedField].label : '');
}
});
// Trigger change on initial pageload to get the right widget options
// and label when field type comes pre-selected (on failed validation).
$(this).trigger('change', false);
});
}
jQuery.fn.contentPopulateOptions = function(options, selected) {
return this.each(function() {
var disabled = false;
if (options.length == 0) {
options = [this.initialValue];
disabled = true;
}
// If possible, keep the same widget selected when changing field type.
// This is based on textual value, since the internal value might be
// different (optionwidgets_buttons vs. nodereference_buttons).
var previousSelectedText = this.options[this.selectedIndex].text;
var html = '';
jQuery.each(options, function(value, text) {
// Figure out which value should be selected. The 'selected' param
// takes precedence.
var is_selected = ((typeof(selected) !== 'undefined' && value == selected) || (typeof(selected) == 'undefined' && text == previousSelectedText));
html += '<option value="' + value + '"' + (is_selected ? ' selected="selected"' : '') +'>' + text + '</option>';
});
$(this)
.html(html)
.attr('disabled', disabled ? 'disabled' : '');
});
}

View file

@ -0,0 +1,254 @@
/**
* Private namespace for local methods.
*/
Drupal.contentRemoveButtons = Drupal.contentRemoveButtons || {};
/**
* Manipulation of content remove buttons.
*
* TableDrag objects for multiple value fields (and multigroups) are scanned
* to find 'remove' checkboxes. These checkboxes are hidden when javascript is
* enabled (using the Global CSS Killswitch, html.js, defined in drupal.js).
* A new 'remove' button is created here in place of these checkboxes aimed to
* provide a more user-friendly method to remove items.
*/
Drupal.behaviors.contentRemoveButtons = function(context) {
var self = Drupal.contentRemoveButtons;
$('table.content-multiple-table', context).not('.content-remove-buttons-processed').addClass('content-remove-buttons-processed').each(function() {
var table = this, tableDrag = Drupal.tableDrag[$(table).attr('id')];
// Replace remove checkboxes with buttons.
$('input.content-multiple-remove-checkbox', table).each(function() {
var $checkbox = $(this), $row = $checkbox.parents('tr:first');
var isRemoved = $checkbox.attr('checked');
var $button = $(Drupal.theme('contentRemoveButton', tableDrag.getRemoveButtonTitle(isRemoved)));
// Bind the onClick event to the remove button.
$button.bind('click', function(event) {
self.onClick($button, $checkbox, $row, tableDrag);
return false;
});
// Attach the new button to the DOM tree.
$checkbox.parent().append($button);
// If the row is removed, then hide the contents of the cells and show
// the removed warning on the cell next to the drag'n'drop cell.
if (isRemoved) {
self.getCellWrappers($row).hide();
self.showRemovedWarning($row, tableDrag);
// FAPI not rendering the form on errors - case #1:
// If the form has been submitted and any error was found, FAPI will
// send back the same exact form that was submitted to show the error
// messages, but it will not invoke the rendering engine which is where
// we actually assign the removed class to the row, so we need to check
// this situation here and add the class if it is not present.
if (!$row.hasClass('content-multiple-removed-row')) {
$row.addClass('content-multiple-removed-row');
}
}
else {
// FAPI not rendering the form on errors - case #2:
// Similar issue than #1, but this time caused when user removes an
// item, previews, FAPI renders the new form with the removed class,
// then user changes anything in the form that causes an error, and
// also restores the previously removed item. This time, FAPI will
// send the form validation error with the item not flagged for removal
// but having the removed class that was present when the form was
// rendered in the previous step. So we need to remove this class here,
// if present, because the item is not really flagged for removal.
if ($row.hasClass('content-multiple-removed-row')) {
$row.removeClass('content-multiple-removed-row');
}
}
});
});
};
/**
* onClick handler for remove buttons.
*
* @param $button
* The jQuery object of the remove button.
* @param $checkbox
* The jQuery object of the remove checkbox.
* @param $row
* The jQuery object of the table row.
* @param tableDrag
* The tableDrag object where the row is.
*/
Drupal.contentRemoveButtons.onClick = function($button, $checkbox, $row, tableDrag) {
var self = Drupal.contentRemoveButtons;
// Prevent the user from firing this event while another one is still being
// processed. This flag is (should be) restored at end of animations.
// Note that this technique is required because the browser may experience
// delays while performing the animation, for whatever reason, and if this
// process it fired more than once at the same time for the same row, then
// it may cause unexpected behavior because the state of the elements being
// manipulated would be unknown.
if ($row.animating) {
return;
}
$row.animating = true;
// Toggle the state of the checkbox.
var isRemoved = !$checkbox.attr('checked');
$checkbox.attr('checked', isRemoved);
// Toggle the row class.
if (isRemoved) {
$row.addClass('content-multiple-removed-row');
}
else {
$row.removeClass('content-multiple-removed-row');
}
// Toggle the button title.
$button.attr('title', tableDrag.getRemoveButtonTitle(isRemoved));
// Get the list of cell wrappers in this row.
var $cellWrappers = self.getCellWrappers($row);
// If for whatever reason this row doesn't have cells with elements,
// then we are done, but we still need to reset the global busy flag
// and display the tableDrag changed warning.
if (!$cellWrappers.size()) {
tableDrag.displayChangedWarning();
$row.animating = false;
return;
}
// Toggle the visible state of the row cells.
$cellWrappers.each(function() {
var $cellWrapper = $(this);
// Drop the removed warning during restore operation.
if (!isRemoved) {
self.hideRemovedWarning($row);
}
// Toggle the visibility state of the contents of cells.
$cellWrapper.animate({opacity: (isRemoved ? 'hide' : 'show')}, 'fast', function() {
var $cell = $cellWrapper.parent();
// Show the removed warning during remove operation.
if (isRemoved && $cell.prev(':first').hasClass('content-multiple-drag')) {
self.showRemovedWarning($row, tableDrag);
}
// Disable the busy flag when animation of last cell has finished.
if ($cell.next(':first').hasClass('delta-order')) {
tableDrag.displayChangedWarning();
$row.animating = false;
}
});
});
};
/**
* Show the removed warning on the given row.
*
* @param $row
* The jQuery object of the table row.
* @param tableDrag
* The tableDrag object where the row is.
*/
Drupal.contentRemoveButtons.showRemovedWarning = function($row, tableDrag) {
$('.content-multiple-drag', $row).next(':first').append(Drupal.theme('contentRemovedWarning', tableDrag.getRemovedWarning()));
};
/**
* Hide the removed warning from the given row.
*
* @param $row
* The jQuery object of the table row.
*/
Drupal.contentRemoveButtons.hideRemovedWarning = function($row) {
if ($('.content-multiple-removed-warning', $row).size()) {
$('.content-multiple-removed-warning', $row).remove();
}
};
/**
* Get cell wrappers for the given row.
*
* @param $row
* The jQuery object of the table row.
*/
Drupal.contentRemoveButtons.getCellWrappers = function($row) {
// Create cell wrappers if this row has not already been processed.
if (!$('.content-multiple-cell-content-wrapper', $row).size()) {
// Wrap the contents of all cells (except the drag'n'drop, weight and
// remove button cells) with a dummy block element. This operation makes
// animations faster because we just need to show/hide a single element
// per cell, and it also prevents from creating more than one warning
// element per row.
$row.children('td:not(.content-multiple-drag):not(.delta-order):not(.content-multiple-remove-cell)').each(function() {
var $cell = $(this);
$cell.wrapInner('<div class="content-multiple-cell-content-wrapper"/>');
});
}
return $('.content-multiple-cell-content-wrapper', $row);
};
/**
* Display table change warning when appropriate.
*/
Drupal.tableDrag.prototype.displayChangedWarning = function() {
if (this.changed == false) {
$(Drupal.theme('tableDragChangedWarning')).insertAfter(this.table).hide().fadeIn('slow');
this.changed = true;
}
};
/**
* Get the title of the remove button.
*
* This method is an extension of the tableDrag class. This means a separate
* module can override this method for a particular tableDrag instance. For
* example, the multigroup module can change the text to read 'Remove this
* group of items', another module could change it to 'Remove this image',
* and so on...
* To override this function:
*
* @code
* var tableId = $(table).attr('id');
* Drupal.tableDrag[tableId].getRemoveButtonTitle = function(isRemoved) {
* return (isRemoved ? Drupal.t('Restore this foo') : Drupal.t('Remove this foo'));
* };
* @endcode
*
* @param isRemoved
* A flag that indicates the state of the button.
*/
Drupal.tableDrag.prototype.getRemoveButtonTitle = function(isRemoved) {
return (isRemoved ? Drupal.t('Restore this item') : Drupal.t('Remove this item'));
};
/**
* Get the item removed warning.
*
* This method is an extension of the tableDrag class. It can be overridden by
* a separate module. See getRemoveButtonTitle() for further information.
*/
Drupal.tableDrag.prototype.getRemovedWarning = function() {
return Drupal.t('Removed');
};
/**
* Theme the remove button.
*/
Drupal.theme.prototype.contentRemoveButton = function(title) {
return '<a href="javascript:void(0)" class="content-multiple-remove-button" title="'+ title +'"></a>';
};
/**
* Theme the item removed warning.
*/
Drupal.theme.prototype.contentRemovedWarning = function(warning) {
return '<div class="content-multiple-removed-warning">'+ warning +'</div>';
};

View file

@ -0,0 +1,11 @@
name = Content Copy
description = Enables ability to import/export field definitions.
dependencies[] = content
package = CCK
core = 6.x
; Information added by Drupal.org packaging script on 2015-06-25
version = "6.x-3.0-alpha4"
core = "6.x"
project = "cck"
datestamp = "1435195385"

View file

@ -0,0 +1,648 @@
<?php
/**
* @file
* Adds capability to import/export CCK field data definitions.
*
* Emulates the import/export process introduced in Views to export the field settings arrays as text
* them copy and paste text to import the field settings back into another content type.
*
* Functions to create and playback macros borrowed from moshe weitzman's macro module.
* Macros created using drupal_execute() on the field settings form for each of the requested forms.
* Multi-part forms built using examples from Jeff Eaton's example at
* http://jeff.viapositiva.net/drupal/dynamic-forms.
*
* You can export one or two fields and/or groups from one content type and import them into
* another content type in the same or a different installation,
* or export a complete content type with all groups and fields and create it
* as an exact copy in another installation.
*
* Content type, group and field names will be imported exactly as exported.
* If the names are already in use, no import will be performed.
*
*
* Note: The "display fields" information is being handled a little differently than the rest of the
* data that's imported and exported. Instead of calling through the create and playback macros,
* we get and set the data directly from/into the database. the reason for this is that the
* playback macro method does not lend itself well to the display fields.
*/
/**
* Implementation of hook_menu().
*/
function content_copy_menu() {
$items = array();
$items['admin/content/types/export'] = array(
'title' => 'Export',
'page callback' => 'drupal_get_form',
'page arguments' => array('content_copy_export_form'),
'access arguments' => array('administer content types'),
'type' => MENU_LOCAL_TASK,
'weight' => 3,
);
$items['admin/content/types/import'] = array(
'title' => 'Import',
'page callback' => 'drupal_get_form',
'page arguments' => array('content_copy_import_form'),
'access arguments' => array('administer content types'),
'type' => MENU_LOCAL_TASK,
'weight' => 4,
);
return $items;
}
/**
* Implementation of hook_theme().
*/
function content_copy_theme() {
return array(
'content_copy_export_form' => array(
'template' => 'content_copy_export_form',
'arguments' => array('form' => NULL),
),
);
}
/**
* A form to export field definitions.
*/
function content_copy_export_form(&$form_state) {
include_once('./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
include_once('./'. drupal_get_path('module', 'node') .'/content_types.inc');
$form_values = isset($form_state['values']) ? $form_state['values'] : array();
$step = isset($form_state['storage']['step']) ? $form_state['storage']['step'] + 1 : 1;
$exportable_fields = array();
$groups = array();
$type_name = isset($form_values['type_name']) ? $form_values['type_name'] : '';
if ($type_name) {
$type = content_types($type_name);
$exportable_fields = content_copy_fields($type_name);
if (module_exists('fieldgroup')) {
$groups = fieldgroup_groups($type_name);
}
}
// If a content type has been selected and there are no fields or groups to select,
// jump straight to export.
if ($step == 2 && !($groups) && !($exportable_fields)) {
$step = 3;
}
$form['#step'] = $step;
$form['#prefix'] = t('This form will process a content type and one or more fields from that type and export the settings. The export created by this process can be copied and pasted as an import into the current or any other database. The import will add the fields to an existing content type or create a new content type that includes the selected fields.');
switch ($step) {
case 1: // Select a content type.
$types = content_copy_types();
$form['type_name'] = array(
'#title' => t('Types'),
'#type' => 'radios',
'#options' => $types,
'#description' => t('Select the content type to export.'),
);
break;
case 2: // Select groups and fields.
$form['type_name'] = array(
'#type' => 'hidden',
'#value' => $type_name,
);
$form += array(
'#fields' => $exportable_fields,
'#groups' => array_keys($groups),
);
$fields_options = $groups_options = array();
// Fields.
foreach ($exportable_fields as $field_name) {
$field = content_fields($field_name, $type_name);
$fields_options[$field_name] = '';
$weight = $field['widget']['weight'];
$form[$field_name] = array(
'human_name' => array('#value' => check_plain($field['widget']['label'])),
'field_name' => array('#value' => $field['field_name']),
'type' => array('#value' => $field['type']),
'weight' => array('#type' => 'value', '#value' => $weight),
'parent' => array('#type' => 'value', '#value' => ''),
'#row_type' => 'field',
);
}
$form['fields'] = array(
'#type' => 'checkboxes',
'#options' => $fields_options,
'#default_value' => array_keys($fields_options),
);
// Groups.
foreach ($groups as $name => $group) {
$groups_options[$name] = '';
$weight = $group['weight'];
$form[$name] = array(
'human_name' => array('#value' => check_plain($group['label'])),
'group_name' => array('#value' => $group['group_name']),
'weight' => array('#type' => 'value', '#value' => $weight),
'#row_type' => 'group',
);
foreach ($group['fields'] as $field_name => $field) {
// Do nothing for non-exportable (inactive) fields.
if (isset($form[$field_name])) {
$form[$field_name]['parent']['#value'] = $name;
}
}
}
if ($groups) {
$form['groups'] = array(
'#type' => 'checkboxes',
'#options' => $groups_options,
'#default_value' => array_keys($groups_options),
);
}
break;
case 3: // Display the export macro.
$GLOBALS['content_copy']['count'] = 0;
$form['export'] = array(
'#title' => t('Export data'),
'#type' => 'textarea',
'#cols' => 60,
'#value' => content_copy_export($form_values),
'#rows' => max(40, $GLOBALS['content_copy']['count']),
'#description' => t('Copy the export text and paste it into another content type using the import function.'),
);
// The calls to drupal_execute('content_field_edit_form') in
// content_copy_export() affect the page title,
drupal_set_title(t('Content types'));
break;
}
if ($step < 3) { // Omit submit button on the textarea block to display the export data.
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Export'),
);
}
$form['step'] = array(
'#type' => 'value',
'#value' => $step,
);
return $form;
}
function content_copy_export_form_submit($form, &$form_state) {
$form_state['rebuild'] = TRUE;
$form_state['storage']['step'] = $form_state['values']['step'];
}
/**
* Process the export, get field admin forms for all requested fields
* and save the form values as formatted text.
*/
function content_copy_export($form_values) {
// Set a global variable to tell when to intervene with form_alter().
$GLOBALS['content_copy']['status'] = 'export';
// Get the content type info by submitting the content type form.
$node_state = array('values' => array('type_name' => $form_values['type_name']));
module_load_include('inc', 'node', 'content_types');
drupal_execute('node_type_form', $node_state, node_get_types('type', $form_values['type_name']));
module_load_include('inc', 'content', 'includes/content.admin');
module_load_include('inc', 'content', 'includes/content.crud');
// Get an array of groups to export.
// Record a macro for each group by submitting the group edit form.
$groups = array();
if (!empty($form_values['groups']) && module_exists('fieldgroup')) {
$groups = array_filter($form_values['groups']);
foreach ($groups as $group) {
$group_state = array('values' => array('group_name' => $group));
drupal_execute('fieldgroup_group_edit_form', $group_state, $form_values['type_name'], $group, 'edit');
}
}
// Get an array of fields to export
// Record a macro for each field by submitting the field settings form.
// Omit fields from the export if their module is not currently installed
// otherwise the system will generate errors when the macro tries to execute their forms.
if (!empty($form_values['fields'])) {
$type = content_types($form_values['type_name']);
$fields = array_filter($form_values['fields']);
foreach ($fields as $field_name) {
$field = $type['fields'][$field_name];
$field_types = _content_field_types();
$field_module = $field_types[$field['type']]['module'];
$widget_types = _content_widget_types();
$widget_module = $widget_types[$field['widget']['type']]['module'];
if (!empty($field_module) && module_exists($field_module) && !empty($widget_module) && module_exists($widget_module)) {
$field_state = array('values' => content_field_instance_collapse($field));
$field_state['values']['op'] = t('Save field settings');
if (module_exists('fieldgroup')) {
// Avoid undefined index error by always creating this.
$field_state['values']['group'] = '';
$group_name = fieldgroup_get_group($form_values['type_name'], $field_name);
if (in_array($group_name, $groups)) {
$field_state['values']['group'] = $group_name;
}
}
drupal_execute('content_field_edit_form', $field_state, $form_values['type_name'], $field_name);
}
}
}
// Convert the macro array into formatted text.
$output = content_copy_get_macro();
// Add weights of non-CCK fields.
if ($extra = variable_get('content_extra_weights_'. $form_values['type_name'], array())) {
$output .= "\$content['extra'] = ". var_export((array) $extra, TRUE) .";\n";
}
return $output;
}
/**
* A form to import formatted text created with export.
*
* The macro can be filled from a file, if provided.
* Provide a type_name to force the fields to be added to a specific
* type, or leave out type_name to create a new content type.
*
* Example:
* // If Content Copy is enabled, offer an import link.
* if (module_exists('content_copy')) {
* $form['macro'] = array(
* '#type' => 'fieldset',
* '#title' => t('Create a content type'),
* '#description' => t('Follow this link to create automatically a content type and import preconfigured fields.'),
* '#collapsible' => TRUE,
* '#collapsed' => FALSE,
* );
* $form['macro']['link'] = array(
* '#type' => 'markup',
* '#value' => l(t('import'), 'admin/content/types/import', array(), 'type_name=event&macro_file='. drupal_get_path('module', 'my_module') .'/my_content_type.txt'),
* );
* }
*/
function content_copy_import_form(&$form_state, $type_name = '') {
include_once('./'. drupal_get_path('module', 'content') .'/includes/content.admin.inc');
include_once('./'. drupal_get_path('module', 'node') .'/content_types.inc');
$form['#prefix'] = t('This form will import field definitions exported from another content type or another database.<br/>Note that fields cannot be duplicated within the same content type, so imported fields will be added only if they do not already exist in the selected type.');
$form['type_name'] = array(
'#type' => 'select',
'#options' => array('<create>' => t('<Create>')) + content_copy_types(),
'#default_value' => $type_name,
'#title' => t('Content type'),
'#description' => t('Select the content type to import these fields into.<br/>Select &lt;Create&gt; to create a new content type to contain the fields.'),
);
$form['macro'] = array(
'#type' => 'textarea',
'#rows' => 40,
'#title' => t('Import data'),
'#required' => TRUE,
'#description' => t('Paste the text created by a content export into this field.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
// Read in a file if there is one and set it as the default macro value.
if (isset($_REQUEST['macro_file']) && $file = file_get_contents($_REQUEST['macro_file'])) {
$form['macro']['#default_value'] = $file;
if (isset($_REQUEST['type_name'])) {
$form['type_name']['#default_value'] = $_REQUEST['type_name'];
}
$form['#prefix'] .= '<p class="error">'. t('A file has been pre-loaded for import.') .'</p>';
}
return $form;
}
/**
* Submit handler for import form.
* For each submitted field:
* 1) add new field to the database
* 2) execute the imported field macro to update the settings to the imported values
*/
function content_copy_import_form_submit($form, &$form_state) {
$form_values = $form_state['values'];
// Get the content type we are importing into.
$type_name = $form_values['type_name'];
$type_label = node_get_types('name', $type_name);
$content = NULL;
// Convert the import formatted text back into a $content array.
// Return if errors generated or not an array.
// Use '@' to suppress errors about undefined constants in the macro.
@eval($form_values['macro']);
// Preliminary error trapping, must have valid arrays to work with.
if (!isset($content) || !isset($content['type']) || !is_array($content) || !is_array($content['type'])) {
form_set_error('macro', t('The import data is not valid import text.'));
return;
}
module_load_include('inc', 'content', 'includes/content.crud');
// Get all type and field info for this database.
$content_info = _content_type_info();
$imported_type = $content['type'];
$imported_type_name = $imported_type['type'];
$imported_type_label = $imported_type['name'];
// It is allowed to import a type with no fields,
// so the fields array could be empty and must be cast as an array.
$imported_fields = isset($content['fields']) ? $content['fields'] : array();
// Perform more pre-import error trapping.
// If there are potential problems, exit without doing the import.
$not_enabled = array();
// The groups array could be empty and still valid, make sure to cast it as an array.
// If there are groups in the import, make sure the fieldgroup module is enabled.
$imported_groups = array();
if (isset($content['groups']) && module_exists('fieldgroup')) {
$imported_groups = (array) $content['groups'];
}
elseif (isset($content['groups']) && is_array($content['groups'])) {
$not_enabled[] = 'fieldgroup';
}
// Make sure that all the field and widget modules in the import are enabled in this database.
foreach ($imported_fields as $import) {
$field = content_field_instance_collapse($import);
if (empty($field['module']) || empty($field['widget_module'])) {
$not_enabled[] = $field['field_name'];
}
else {
if (!module_exists($field['module'])) {
$not_enabled[] = $field['module'];
}
if (!module_exists($field['widget_module'])) {
$not_enabled[] = $field['widget_module'];
}
}
}
// If any required module is not enabled, set an error message and exit.
if ($not_enabled) {
form_set_error('macro', t('The following modules must be enabled for this import to work: %modules.', array(
'%modules' => implode(', ', array_unique($not_enabled))
)));
}
// Make sure the imported content type doesn't already exist in the database.
if ($form_values['type_name'] == '<create>') {
if (in_array($imported_type_name, array_keys($content_info['content types']))) {
form_set_error('macro', t('The content type %type already exists in this database.', array(
'%type' => $imported_type_name
)));
}
}
if (form_get_errors()) {
drupal_set_message(t('Exiting. No import performed.'), 'error');
return;
}
// Create the content type, if requested.
if ($form_values['type_name'] == '<create>') {
$type = (object) $imported_type;
$values = $imported_type;
// Prevent a warning in node/content_types.inc
$type->has_title = TRUE;
$type_form_state = array('values' => $values);
// There's no API for creating node types, we still have to use drupal_execute().
drupal_execute('node_type_form', $type_form_state, $type);
// Reset type and database values once new type has been added.
$type_name = $imported_type_name;
$type_label = node_get_types('name', $type_name);
content_clear_type_cache();
$content_info = _content_type_info();
if (form_get_errors() || !isset($content_info['content types']) || !is_array($content_info['content types'][$type_name])) {
drupal_set_message(t('An error has occurred adding the content type %type.<br/>Please check the errors displayed for more details.', array(
'%type' => $imported_type_name
)));
return;
}
}
// Create the groups for this type, if they don't already exist.
if (module_exists('fieldgroup') && $imported_groups) {
foreach ($imported_groups as $group) {
$group_name = $group['group_name'];
fieldgroup_save_group($type_name, $group);
}
// Reset the static variable in fieldgroup_groups() with new data.
fieldgroup_groups('', FALSE, TRUE);
}
// Iterate through the field forms in the import and execute each.
$rebuild = FALSE;
foreach ($imported_fields as $field) {
// Make sure the field doesn't already exist in the type.
// If so, do nothing, fields can't be duplicated within a content type.
$field_name = $field['field_name'];
// Might need to overwrite the content type name if a new type was created.
$field['type_name'] = $type_name;
if (!empty($field['field_name']) && isset($content_info['content types'][$type_name]['fields'][$field_name])) {
drupal_set_message(t('The imported field %field_label (%field_name) was not added to %type because that field already exists in %type.', array(
'%field_label' => $field['label'], '%field_name' => $field_name, '%type' => $type_label)));
}
else {
$field = content_field_instance_create($field, FALSE);
$rebuild = TRUE;
drupal_set_message(t('The field %field_label (%field_name) was added to the content type %type.', array(
'%field_label' => $field['widget']['label'], '%field_name' => $field_name, '%type' => $type_label)));
}
// Fieldgroup module erases all group related data when a module that
// provides a content type is disabled, but CCK does not remove the fields.
// In this case, we should ensure group data related to fields is properly
// restored. Hence, we need to update field group data for newly imported
// field, but also for fields that already exist.
if (module_exists('fieldgroup') && isset($imported_groups)) {
fieldgroup_update_fields($field);
}
}
// Clear caches and rebuild menu only if any field has been created.
if ($rebuild) {
content_clear_type_cache(TRUE);
menu_rebuild();
}
// Import weights of non-CCK fields.
if (isset($content['extra'])) {
variable_set('content_extra_weights_'. $type_name, $content['extra']);
}
}
/**
* Implementation of hook_form_alter().
* Intervene to run form through macro when doing export
*/
function content_copy_form_alter(&$form, $form_state, $form_id) {
$alter_forms = array('node_type_form', 'content_field_edit_form', 'fieldgroup_group_edit_form');
if (isset($GLOBALS['content_copy']) && isset($GLOBALS['content_copy']['status']) && $GLOBALS['content_copy']['status'] == 'export' && in_array($form_id, $alter_forms)) {
$form['#submit'][] = 'content_copy_record_macro';
}
}
/**
* Get all the *active* fields for a content type.
*/
function content_copy_fields($type_name) {
$fields = array();
if (!$type_name) {
return $fields;
}
$content_info = _content_type_info();
foreach ($content_info['content types'][$type_name]['fields'] as $field_name => $field) {
// Omit fields from the export if their module is not currently installed
// otherwise the system will generate errors when the macro tries to execute their forms.
$field_types = _content_field_types();
$field_module = $field_types[$field['type']]['module'];
$widget_types = _content_widget_types();
$widget_module = $widget_types[$field['widget']['type']]['module'];
if (!$field['locked'] && !empty($field_module) && module_exists($field_module) && !empty($widget_module) && module_exists($widget_module)) {
$fields[] = $field_name;
}
}
return $fields;
}
/**
* Get all content types.
*/
function content_copy_types() {
$types = array();
$content_info = _content_type_info();
foreach ($content_info['content types'] as $type_name => $val) {
$types[$type_name] = check_plain($val['name']) .' ('. $type_name .')';
}
return $types;
}
/**
* A handler that stores the form submissions into a $GLOBALS array
*/
function content_copy_record_macro($form, &$form_state) {
$edit = $form_state['values'];
$subs = isset($GLOBALS['content_copy']['submissions']) ? $GLOBALS['content_copy']['submissions'] : array();
// Get the form values and store them in a $GLOBALS['content_copy']['submissions'] array.
// Update $GLOBALS['content_copy']['count'] with an approximation of the number of rows in this item.
// Count is used to approximate necessary size of textarea in form.
$form_id = $form_state['values']['form_id'];
if (isset($edit['type_name']) || isset($edit['submit']) || isset($edit['delete']) || isset($edit['form_id'])) {
unset($edit['type_name'], $edit['submit'], $edit['delete'], $edit['form_id'], $edit['previous_field']);
}
switch ($form_id) {
case 'node_type_form':
$subs['type'] = $edit;
$GLOBALS['content_copy']['count'] += sizeof($edit) + 5;
break;
case 'fieldgroup_group_edit_form':
$subs['groups'][] = $edit;
$GLOBALS['content_copy']['count'] += sizeof($edit) + 5;
break;
default:
if (isset($edit['field_widget_type'])) {
$tmp = explode('-', $edit['field_widget_type']);
$field_name = $tmp[0];
}
else {
$field_name = isset($edit['field_name']) ? $edit['field_name'] : '';
}
// The display settings are being fetched directly from the DB. During import,
// we'll re-insert the data directly as well.
//
$query = 'SELECT display_settings FROM {'. content_instance_tablename() .'} WHERE field_name = \'%s\'';
$row_info = db_fetch_array(db_query($query, $field_name));
// If an error occurs, notify the user.
if ($db_err = db_error()) {
drupal_set_message(t("An error occurred when exporting the 'display settings' data for the field %field_name.<br/>The db error is: '%db_err'.", array(
'%field_name' => $field_name,
'%db_err' => $db_err
)));
}
else {
// The db fetch occurred successfully, unserialize the data blob and
// insert it into a new "display_settings" field of the data.
if ($display_settings = unserialize($row_info['display_settings'])) {
$edit['display_settings'] = $display_settings;
}
}
$subs['fields'][] = $edit;
$GLOBALS['content_copy']['count'] += sizeof($edit) + 5;
break;
}
$GLOBALS['content_copy']['submissions'] = $subs;
}
/**
* @return a code representation of the recorded macro.
*/
function content_copy_get_macro() {
// Define the indexes for the evaluated code.
$string = "";
if (array_key_exists('submissions', $GLOBALS['content_copy'])) {
foreach ($GLOBALS['content_copy']['submissions'] as $form_type => $form) {
$string .= "\$content['$form_type'] = ". var_export((array) $form, TRUE) .";\n";
}
return $string;
}
}
function template_preprocess_content_copy_export_form($vars) {
$form = &$vars['form'];
if ($form['#step'] == 2) {
$order = _content_overview_order($form, $form['#fields'], $form['#groups']);
$rows = array();
foreach ($order as $key) {
$element = &$form[$key];
$row = new stdClass();
$row->row_type = $element['#row_type'];
$checkbox_key = $element['#row_type'] == 'field' ? 'fields' : 'groups';
$row->checkbox = drupal_render($form[$checkbox_key][$key]);
foreach (element_children($element) as $child) {
$row->{$child} = drupal_render($element[$child]);
}
$row->label_class = in_array($key, $form['#groups']) ? 'label-group' : 'label-field';
$row->indentation = theme('indentation', isset($element['#depth']) ? $element['#depth'] : 0);
$rows[] = $row;
}
$vars['rows'] = $rows;
}
$vars['submit'] = drupal_render($form);
}

View file

@ -0,0 +1,41 @@
<?php
if ($form['#step'] == 2):
if ($rows): ?>
<table id="content-copy-export" class="sticky-enabled">
<thead>
<tr>
<th><?php print t('Export'); ?></th>
<th><?php print t('Label'); ?></th>
<th><?php print t('Name'); ?></th>
<th><?php print t('Type'); ?></th>
</tr>
</thead>
<tbody>
<?php
$count = 0;
foreach ($rows as $row): ?>
<tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?>">
<?php
switch ($row->row_type):
case 'field': ?>
<td><?php print $row->checkbox; ?></td>
<td><?php print $row->indentation; ?><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td>
<td><?php print $row->field_name; ?></td>
<td><?php print $row->type; ?></td>
<?php break;
case 'group': ?>
<td><?php print $row->checkbox; ?></td>
<td><?php print $row->indentation; ?><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td>
<td colspan="2"><?php print $row->group_name; ?></td>
<?php break;
endswitch; ?>
</tr>
<?php $count++;
endforeach; ?>
</tbody>
</table>
<?php endif;
endif;
print $submit; ?>

View file

@ -0,0 +1,99 @@
CONTENTS OF THIS FILE
=====================
- USING MULTIGROUPS
- FIELDS AND WIDGETS THAT WORK IN MULTIGROUPS
- VIEWS INTEGRATION
- TROUBLESHOOTING
USING MULTIGROUPS
=================
The Multigroup group treats all included fields like a single field, keeping
the related delta values of all included fields synchronized.
To use a Multigroup, create a new group, make it the 'Multigroup' type, set
the number of multiple values for all the fields in the Multigroup, and drag
into it the fields that should be included.
All fields in the Multigroup will automatically get the group setting for
multiple values. On the node form, the group is rearranged to keep the delta
values for each field in a single drag 'n drop group, by transposing the
normal array(group_name => field_name => delta => value) into
array(group_name => delta => field_name => value).
During validation and submission, the field values are restored to their
normal positions.
FIELDS AND WIDGETS THAT WORK IN MULTIGROUPS
===========================================
All fields that allow the Content module to handle their multiple values should
work here. Fields that handle their own multiple values will not be allowed
into Multigroups unless they implement hook_content_multigroup_allowed_widgets()
to add their widgets to the allowed widget list. Example:
@code
function MODULE_content_multigroup_allowed_widgets() {
return array('WIDGET_NAME_1', 'WIDGET_NAME_2', ...);
}
@endcode
All fields that allow the Content module to handle their multiple values
should work correctly when a field placed on a Multigroup is moved off, to a
normal field group, or to the top level of the form. Fields that handle their
own multiple values which may store different results in Multigroup and
standard groups should implement hook_content_multigroup_no_remove_widgets()
to add their widgets to the list of widgets that cannot be removed from
Multigroups. Example:
@code
function MODULE_content_multigroup_no_remove_widgets() {
return array('WIDGET_NAME_1', 'WIDGET_NAME_2', ...);
}
@endcode
The Content Taxonomy module [1] is an example where it implements the previous
hooks for a few widgets.
[1] http://drupal.org/project/content_taxonomy
If a simple array of widgets is not sufficient to test whether this action
will work, modules can implement hook_content_multigroup_allowed_in()
and hook_content_multigroup_allowed_out() to intervene. Both hooks should
return an array as in the following example:
@code
function MODULE_content_multigroup_allowed_in() {
return array(
'allowed' => FALSE,
'message' => t('This change is not allowed. Reason here...'),
);
}
@endcode
Custom code and modules that add fields to groups outside of the UI should
use content_multigroup_allowed_in() and content_multigroup_allowed_out() to
test whether fields are allowed in or out of a Multigroup. These functions
can be located in content_multigroup.admin.inc.
VIEWS INTEGRATION
=================
For each multigroup, there is a new filter under "Content multigroup" category
in Views that provides a method to synchronize fields by delta.
TROUBLESHOOTING
===============
The most likely cause of problems with field modules not working in multigroup
is if they wipe out #element_validate with their own validation functions, or
they hard-code assumptions into submit or validation processes that the form
is structured in the usual field => delta => value order instead of allowing
for the possibility of a different structure. See Nodereference for an example
of a field that handles validation without making assumptions about the form
structure.

View file

@ -0,0 +1,531 @@
<?php
/**
* @file
* Implementation of node type administration functions for content multigroup.
*/
/**
* Helper function to build the multiple values options for multigroups.
*/
function content_multigroup_multiple_values() {
return array(
//'' => t('N/A'),
1 => t('Unlimited'),
0 => 1) + drupal_map_assoc(range(2, 10));
}
/**
* Validation for creating/moving fields and groups on the
* Manage Fields screen.
*/
function content_multigroup_field_overview_form_validate($form, &$form_state) {
$form_values = $form_state['values'];
$type_name = $form['#type_name'];
$fields = array();
$groups = array();
$group = $form_values['_add_new_group'];
if (array_filter(array($group['label'], $group['group_name']))) {
$group['settings'] = field_group_default_settings($group['group_type']);
$validation = fieldgroup_validate_name($group, $form['#type_name']);
// If there's something wrong with the new group,
// don't bother doing any more validation, further
// processing will be stopped by the fieldgroup module.
if (!empty($validation['errors'])) {
return;
}
$group['group_name'] = $validation['group_name'];
$new_group_name = $group['group_name'];
$groups['_add_new_group'] = $group;
}
// See if we have fields moving into or out of a Multigroup.
// Set any fields to use the new name here so they will get processed
// correctly by the fieldgroup module when saved.
$group_rows = array();
foreach ($form_values as $key => $values) {
if ($values['parent'] == '_add_new_group') {
$values['parent'] = $new_group_name;
$form_values[$key] = $values;
}
if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'group') {
// Gather up info about all groups.
$group_name = $form_values[$key]['group']['group_name'];
$groups[$group_name] = $form_values[$key]['group'];
$group_rows[$group_name] = $group_name;
}
if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'field') {
if ($values['prev_parent'] != $values['parent']) {
// Gather up fields that have moved in or out of a group.
$fields[$key] = $form_values[$key]['field'];
}
}
}
$rebuild = FALSE;
// Test that a group was not moved into a multigroup, an invalid combination.
foreach ($groups as $key => $values) {
if (in_array($key, $group_rows)) {
$parent = $form_values[$key]['parent'];
$parent_info = !empty($parent) ? $form_values[$parent] : array();
if (!empty($parent) && $parent_info['group']['group_type'] == 'multigroup') {
$error_message = t('You cannot place any kind of group inside a multigroup. The group @name was moved back to where it started.', array('@name' => $key));
form_set_value($form[$key]['weight'], $form[$key]['weight']['#default_value'], $form_state);
form_set_value($form[$key]['parent'], $form[$key]['parent']['#default_value'], $form_state);
drupal_set_message($error_message, 'error');
}
}
}
// Synchronize the multiple value values for all fields in a group, they must be the same.
// Also ensure that fields moved into multigroups are fields that are allowed.
// In some cases, it may not be safe to move a field back out of a multigroup because
// it will behave differently elsewhere, so check that too.
foreach ($fields as $field_name => $field) {
$new_group = $form_values[$field_name]['parent'];
$old_group = $form_values[$field_name]['prev_parent'];
if (!empty($new_group) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') {
$allowed_in = content_multigroup_allowed_in($field, $groups[$new_group]);
if (!$allowed_in['allowed']) {
form_set_error($field_name, $allowed_in['message']);
}
else {
if (!empty($allowed_in['message'])) {
drupal_set_message($allowed_in['message']);
}
module_load_include('inc', 'content', 'includes/content.crud');
$content_type = content_types($type_name);
$group_multiple = $groups[$new_group]['settings']['multigroup']['multiple'];
$multiple_values = content_multigroup_multiple_values();
$field = $content_type['fields'][$field_name];
$field['multiple'] = $group_multiple;
$field = content_field_instance_collapse($field);
content_field_instance_update($field, FALSE);
$rebuild = TRUE;
drupal_set_message(t('The field %field has been updated to use %multiple values, to match the multiple value setting of the Multigroup %group.', array(
'%field' => $field['label'], '%multiple' => $multiple_values[$group_multiple], '%group' => $groups[$new_group]['label'])));
}
}
elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') {
$allowed_out = content_multigroup_allowed_out($field, $groups[$old_group]);
if (!$allowed_out['allowed']) {
form_set_error($field_name, $allowed_out['message']);
}
elseif (!empty($allowed_out['message'])) {
drupal_set_message($allowed_out['message']);
}
}
}
// Clear caches and rebuild menu only if any field has been updated.
if ($rebuild) {
content_clear_type_cache(TRUE);
menu_rebuild();
}
}
/**
* Helper function for deciding if a field is
* allowed into a Multigroup.
*/
function content_multigroup_allowed_in($field, $group) {
if ($group['group_type'] != 'multigroup') {
return array('allowed' => TRUE, 'message' => '');
}
// We can't allow fields with more multiple values than the group has
// to be moved into it.
$max_existing = content_max_delta($field['field_name']);
$group_multiple = $group['settings']['multigroup']['multiple'];
$multiple_values = content_multigroup_multiple_values();
if ($group_multiple != 1 && $max_existing > $group_multiple) {
return array(
'allowed' => FALSE,
'message' => t('This change is not allowed. The field %field already has %multiple values in the database but the group %group only allows %group_max. Making this change would result in the loss of data.', array('%field' => $field['widget']['label'], '%multiple' => $max_existing, '%group' => $group['label'], '%group_max' => $multiple_values[$group_multiple]))
);
}
// Fields that handle their own multiple values may not have the same values
// in Multigroup fields and normal fields. We don't know if they will work or not.
// Adding a hook here where widgets that handle their own multiple values
// that will work correctly in Multigroups can allow their fields in.
if (content_handle('widget', 'multiple values', $field) != CONTENT_HANDLE_CORE) {
$allowed_widgets = array(
'optionwidgets_select',
'optionwidgets_buttons',
'optionwidgets_onoff',
'nodereference_buttons',
'nodereference_select',
'userreference_buttons',
'userreference_select',
);
$allowed_widgets = array_merge($allowed_widgets, module_invoke_all('content_multigroup_allowed_widgets'));
if (!in_array($field['widget']['type'], $allowed_widgets)) {
return array(
'allowed' => FALSE,
'message' => t('This change is not allowed. The field %field handles multiple values differently than the Content module. Making this change could result in the loss of data.', array('%field' => $field['widget']['label']))
);
}
}
// Allow other modules to intervene.
// Any failure will prevent this action.
foreach (module_implements('content_multigroup_allowed_in') as $module) {
$function = $module .'_content_multigroup_allowed_in';
$result = $function($field, $group);
if ($result['allowed'] === FALSE) {
return array('allowed' => FALSE, 'message' => $result['message']);
}
}
$message = t('You are moving the field %field into a Multigroup.', array('%field' => $field['widget']['label']));
return array('allowed' => TRUE, 'message' => $message);
}
/**
* Helper function for deciding if a field is
* allowed out of a Multigroup.
*/
function content_multigroup_allowed_out($field, $group) {
if ($group['group_type'] != 'multigroup') {
return array('allowed' => TRUE, 'message' => '');
}
// Optionwidgets do not behave the same in a Multigroup field as out of it.
// In a Multigroup the same option can be selected multiple times,
// but that is not possible in a normal group.
// Adding a hook here where widgets that handle their own multiple values
// can indicate their fields should not be removed from Multigroups.
$max_existing = content_max_delta($field['field_name']);
$no_remove_widgets = array(
'optionwidgets_select',
'optionwidgets_buttons',
'optionwidgets_onoff',
'nodereference_buttons',
'nodereference_select',
'userreference_buttons',
'userreference_select',
);
$no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('content_multigroup_no_remove_widgets'));
if (in_array($field['widget']['type'], $no_remove_widgets) && $max_existing > 0) {
return array(
'allowed' => FALSE,
'message' => t('This change is not allowed. The field %field already has data created and uses a widget that stores data differently in a Standard group than in a Multigroup. Making this change could result in the loss of data.', array('%field' => $field['widget']['label']))
);
}
// Allow other modules to intervene.
// Any failure will prevent this action.
foreach (module_implements('content_multigroup_allowed_out') as $module) {
$function = $module .'_content_multigroup_allowed_out';
$result = $function($field, $group);
if ($result['allowed'] === FALSE) {
return array('allowed' => FALSE, 'message' => $result['message']);
}
}
$message = t('You are moving the field %field out of a Multigroup.', array('%field' => $field['widget']['label']));
return array('allowed' => TRUE, 'message' => $message);
}
/**
* Alter the basic field settings form.
*
* It should not be possible to choose a widget type that is not compatible
* with multigroups.
*/
function content_multigroup_field_basic_form(&$form, &$form_state) {
$field_name = $form['basic']['field_name']['#value'];
$type_name = $form['type_name']['#value'];
// Ignore this field if it is not part of a field group.
if (!($group_name = fieldgroup_get_group($type_name, $field_name))) {
return;
}
// Retrieve information about the group the field is in.
$groups = fieldgroup_groups($type_name);
$group = $groups[$group_name];
// Ignore this field if it is not part of a multigroup.
if ($group['group_type'] != 'multigroup') {
return;
}
// Retrieve information about the field itself.
$field = content_fields($field_name, $type_name);
// Check if the widget can be moved out of the multigroup.
$allowed_out = content_multigroup_allowed_out($field, $group);
if (!$allowed_out['allowed']) {
$form['basic']['widget_type']['#disabled'] = TRUE;
$form['basic']['widget_type']['#suffix'] = '<div class="warning">'. t('The widget type cannot be changed because the field %field already has data created and this widget stores data differently in a Standard group than in a Multigroup. Allowing this change could result in the loss of data.', array('%field' => $field['widget']['label'])) .'</div>';
return;
}
// Remove from the list of available widgets those that are not
// compatible with multigroups.
$widget_types = _content_widget_types();
foreach (array_keys($form['basic']['widget_type']['#options']) as $widget_type) {
if ($field['widget']['type'] != $widget_type) {
$field_copy = $field;
$field_copy['widget']['type'] = $widget_type;
$field_copy['widget']['module'] = $widget_types[$widget_type]['module'];
$allowed_in = content_multigroup_allowed_in($field_copy, $group);
if (!$allowed_in['allowed']) {
unset($form['basic']['widget_type']['#options'][$widget_type]);
}
}
}
}
/**
* Alter the "Display fields" form.
*
* Add an additional selector for setting multigroup field display format.
*/
function content_multigroup_display_overview_form(&$form, &$form_state) {
$type_name = $form['#type_name'];
$contexts_selector = $form['#contexts'];
// Gather type information.
$content_type = content_types($type_name);
// The content module stops building the form if the type has no fields.
if (empty($content_type['fields'])) {
return;
}
$groups = array();
$groups = fieldgroup_groups($type_name);
$contexts = content_build_modes($contexts_selector);
$all_contexts = content_build_modes();
// Multigroups, extra values.
$label_options = array(
'above' => t('Above'),
'hidden' => t('<Hidden>'),
);
$options = array(
'simple' => t('Simple'),
'fieldset' => t('Fieldset'),
'fieldset_collapsible' => t('Fieldset - collapsible'),
'fieldset_collapsed' => t('Fieldset - collapsed'),
'hr' => t('Horizontal line'),
'table-single' => t('Table - Single column'),
'table-multiple' => t('Table - Multiple columns'),
'ul' => t('Unordered List'),
);
foreach ($groups as $group_name => $group) {
if ($group['group_type'] != 'multigroup') {
continue;
}
$subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
$subgroup_name = $group_name .'_subgroup';
$form['#fields'] = array_merge(array($subgroup_name), $form['#fields']);
$form[$subgroup_name] = array(
'human_name' => array('#value' => t('[Subgroup format]')),
'weight' => array('#type' => 'value', '#value' => -20),
'parent' => array('#type' => 'value', '#value' => $group_name),
'subgroup' => array('#type' => 'value', '#value' => 1),
);
if ($contexts_selector == 'basic') {
$form[$subgroup_name]['label'] = array(
'#type' => 'select',
'#options' => $label_options,
'#default_value' => isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above',
);
}
// Allow a format selection for contexts on the current tab.
// Store other contexts as hidden values so they don't get lost.
foreach ($all_contexts as $key => $title) {
if (array_key_exists($key, $contexts)) {
$form[$subgroup_name][$key]['format'] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => isset($subgroup_settings[$key]['format']) ? $subgroup_settings[$key]['format'] : 'fieldset',
);
$form[$subgroup_name][$key]['exclude'] = array('#type' => 'value', '#value' => 0);
}
else {
$form[$subgroup_name][$key]['format'] = array(
'#type' => 'hidden',
'#value' => isset($subgroup_settings[$key]['format']) ? $subgroup_settings[$key]['format'] : 'fieldset',
);
$form[$subgroup_name][$key]['exclude'] = array('#type' => 'value', '#value' => 0);
}
}
}
$form['#submit'] = array_merge(array('content_multigroup_display_overview_form_submit'), $form['#submit']);
}
/**
* Submit handler for the display overview form.
*
* Do this in pre_save so we catch it before the content module
* tries to use our 'field'.
*/
function content_multigroup_display_overview_form_submit($form, &$form_state) {
$groups = fieldgroup_groups($form['#type_name']);
//$reset_cache = FALSE;
// Find any subgroups we inserted into the display fields form,
// save our settings, and remove them from $form_state.
foreach ($form_state['values'] as $key => $values) {
if (in_array($key, $form['#fields']) && !empty($values['parent']) && !empty($values['subgroup'])) {
$group_name = $values['parent'];
$group = $groups[$group_name];
unset($values['subgroup'], $values['parent']);
// We have some numeric keys here, so we can't use array_merge.
foreach ($values as $k => $v) {
$form_state['values'][$group_name]['settings']['multigroup']['subgroup'][$k] = $v;
}
// Remove the subgroup from $form_state.
unset($form_state['values'][$key]);
}
}
}
/**
* Alter the Fieldgroup edit form to add Multigroup settings.
*/
function content_multigroup_group_edit_form(&$form, &$form_state) {
$type_name = $form['#content_type']['type'];
$group_name = $form['group_name']['#default_value'];
$content_type = content_types($type_name);
$groups = fieldgroup_groups($type_name);
$group = $groups[$group_name];
if ($group['group_type'] != 'multigroup') {
return;
}
module_load_include('inc', 'content', 'includes/content.admin');
module_load_include('inc', 'content', 'includes/content.crud');
$form['group_type'] = array(
'#type' => 'hidden',
'#value' => $group['group_type'],
);
$form['settings']['multigroup'] = array(
'#type' => 'fieldset',
'#title' => t('Multigroup settings'),
'#collapsed' => FALSE,
'#collapsible' => TRUE,
);
if (isset($group['settings']['multigroup']['subgroup'])) {
// Preserve subgroup display settings.
$form['settings']['multigroup']['subgroup'] = array(
'#type' => 'value',
'#value' => $group['settings']['multigroup']['subgroup'],
);
}
$form['settings']['multigroup']['multiple-columns'] = array(
'#type' => 'checkbox',
'#title' => t('Multiple columns'),
'#default_value' => isset($group['settings']['multigroup']['multiple-columns']) ? $group['settings']['multigroup']['multiple-columns'] : 0,
'#description' => t('Enable this option to render each field on a separate column on the node edit form.'),
);
$form['settings']['multigroup']['required'] = array(
'#type' => 'checkbox',
'#title' => t('Required'),
'#default_value' => !empty($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 0,
'#description' => t('Enable this option to require a minimum of one collection of fields in this Multigroup.'),
);
$description = t('Number of times to repeat the collection of Multigroup fields.') .' ';
$description .= t("'Unlimited' will provide an 'Add more' button so the users can add items as many times as they like.") .' ';
$description .= t('All fields in this group will automatically be set to allow this number of values.');
$group_multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
$form['settings']['multigroup']['multiple'] = array(
'#type' => 'select',
'#title' => t('Number of repeats'),
'#options' => content_multigroup_multiple_values(),
'#default_value' => $group_multiple,
'#description' => $description,
);
$form['settings']['multigroup']['labels'] = array(
'#type' => 'fieldset',
'#title' => t('Labels'),
'#description' => t("Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."),
);
if ($group_multiple < 2) {
$group_multiple = 0;
}
for ($i = 0; $i < 10; $i++) {
$form['settings']['multigroup']['labels'][$i] = array(
'#type' => 'textfield',
'#title' => t('Subgroup %number label', array('%number' => $i + 1)),
'#default_value' => isset($group['settings']['multigroup']['labels'][$i]) ? $group['settings']['multigroup']['labels'][$i] : '',
);
}
$form['#validate'][] = 'content_multigroup_group_edit_form_validate';
$form['#submit'][] = 'content_multigroup_group_edit_form_submit';
}
/**
* Validate the Fieldgroup edit form.
*/
function content_multigroup_group_edit_form_validate($form, &$form_state) {
$form_values = $form_state['values'];
$group_type = $form_values['group_type'];
if ($group_type != 'multigroup') {
return;
}
$content_type = $form['#content_type'];
$groups = fieldgroup_groups($content_type['type']);
$group = $groups[$form_values['group_name']];
foreach ($group['fields'] as $field_name => $data) {
// Make sure we don't set the multiple values to a number that
// would result in lost data.
$max_existing = content_max_delta($field_name);
if ($form_values['settings']['multigroup']['multiple'] != 1
&& $max_existing > $form_values['settings']['multigroup']['multiple']) {
form_set_error('settings][multigroup][multiple', t('The field %field in this group already has %multiple values in the database. To prevent the loss of data you cannot set the number of Multigroup values to less than this.', array('%field' => $data['label'], '%multiple' => $max_existing)));
}
}
}
/**
* Submit the Fieldgroup edit form.
*
* Update multiple values of fields contained in Multigroups.
*/
function content_multigroup_group_edit_form_submit($form, &$form_state) {
$form_values = $form_state['values'];
$group_type = $form_values['group_type'];
if ($group_type != 'multigroup') {
return;
}
module_load_include('inc', 'content', 'includes/content.crud');
$content_type = $form['#content_type'];
$groups = fieldgroup_groups($content_type['type']);
$group = $groups[$form_values['group_name']];
$group_fields = array_intersect_key($content_type['fields'], $group['fields']);
if (!empty($group_fields)) {
foreach ($group_fields as $field_name => $field) {
$field['multiple'] = $form_values['settings']['multigroup']['multiple'];
$field = content_field_instance_collapse($field);
content_field_instance_update($field, FALSE);
}
content_clear_type_cache(TRUE);
menu_rebuild();
}
}

View file

@ -0,0 +1,41 @@
label.content-multigroup {
font-weight: bold;
}
/* Not styled by default, but available to style. */
hr.content-multigroup {
}
/* Inline field labels visible within the context of multigroups. */
.content-multigroup-wrapper .field .field-label-inline {
visibility: visible;
}
/**
* Hide field labels and description on the node edit form when the multiple
* columns option is enabled.
*/
.content-multigroup-edit-table-multiple-columns label,
.content-multigroup-edit-table-multiple-columns .description {
display: none;
}
/* Hide field labels when using 'table-multiple' display mode. */
.content-multigroup-display-table-multiple-columns .field .field-label,
.content-multigroup-display-table-multiple-columns .field .field-label-inline,
.content-multigroup-display-table-multiple-columns .field .field-label-inline-first {
display: none;
}
/* Display table with a row for each subgroup and all fields in a single column. */
.content-multigroup-display-table-single-column .content-multigroup-wrapper {
clear: both;
}
.content-multigroup-display-table-single-column .content-multigroup-wrapper label.content-multigroup {
display: block;
}
.content-multigroup-display-table-single-column .content-multigroup-wrapper .field {
float: left;
margin-right: 1em;
}

View file

@ -0,0 +1,14 @@
name = Content Multigroup
description = Combine multiple CCK fields into repeating field collections that work in unison.
dependencies[] = content
dependencies[] = fieldgroup
package = CCK
core = 6.x
; Information added by Drupal.org packaging script on 2015-06-25
version = "6.x-3.0-alpha4"
core = "6.x"
project = "cck"
datestamp = "1435195385"

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Module installation/uninstallation hooks.
*/
/**
* Implementation of hook_install().
*
* Notify content module when this module is installed.
*/
function content_multigroup_install() {
drupal_load('module', 'content');
content_notify('install', 'content_multigroup');
}
/**
* Implementation of hook_uninstall().
*
* Notify content module when this module is uninstalled.
*/
function content_multigroup_uninstall() {
drupal_load('module', 'content');
content_notify('uninstall', 'content_multigroup');
}
/**
* Implementation of hook_enable().
*
* Notify content module when this module is enabled.
*/
function content_multigroup_enable() {
drupal_load('module', 'content');
content_notify('enable', 'content_multigroup');
}
/**
* Implementation of hook_disable().
*
* Notify content module when this module is disabled.
*/
function content_multigroup_disable() {
drupal_load('module', 'content');
content_notify('disable', 'content_multigroup');
}

View file

@ -0,0 +1,195 @@
<?php
/**
* @file
* Create complex, repeating groups of CCK fields that work in unison.
*/
function content_multigroup_help($path, $arg) {
switch ($path) {
case 'admin/help#content_multigroup':
return t('The fields in a Standard group are independent of each other and each can have either single or multiple values. The fields in a Multigroup are treated as a repeating collection of single value fields.');
}
}
/**
* Implementation of hook_views_api().
*/
function content_multigroup_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'content_multigroup') . '/views',
);
}
/**
* Implementation of hook_ctools_plugin_directory().
*/
function content_multigroup_ctools_plugin_directory($module, $plugin) {
if ($module == 'ctools' && $plugin == 'content_types') {
return 'panels/' . $plugin;
}
}
/**
* Implementation of hook_menu().
*/
function content_multigroup_menu() {
$items = array();
// Callback for AHAH add more buttons.
$items['content_multigroup/js_add_more'] = array(
'page callback' => 'content_multigroup_add_more_js',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
'file' => 'content_multigroup.node_form.inc',
);
return $items;
}
/**
* Implementation of hook_theme().
*/
function content_multigroup_theme() {
return array(
'content_multigroup_node_form' => array(
'arguments' => array('element' => NULL),
'file' => 'content_multigroup.node_form.inc',
),
'content_multigroup_node_label' => array(
'arguments' => array('text' => NULL),
'file' => 'content_multigroup.node_form.inc',
),
'content_multigroup_add_more_label' => array(
'arguments' => array('group_name' => NULL),
'file' => 'content_multigroup.node_form.inc',
),
'content_multigroup_display_simple' => array(
'arguments' => array('element' => NULL),
'file' => 'content_multigroup.node_view.inc',
),
'content_multigroup_display_fieldset' => array(
'arguments' => array('element' => NULL),
'file' => 'content_multigroup.node_view.inc',
),
'content_multigroup_display_hr' => array(
'arguments' => array('element' => NULL),
'file' => 'content_multigroup.node_view.inc',
),
'content_multigroup_display_table_single' => array(
'arguments' => array('element' => NULL),
'file' => 'content_multigroup.node_view.inc',
),
'content_multigroup_display_table_multiple' => array(
'arguments' => array('element' => NULL),
'file' => 'content_multigroup.node_view.inc',
),
'content_multigroup_display_ul' => array(
'arguments' => array('element' => NULL),
'file' => 'content_multigroup.node_view.inc',
),
);
}
/**
* Implementation of hook_elements().
*/
function content_multigroup_elements() {
return array(
'content_multigroup_display_fieldset' => array('#value' => NULL),
);
}
/**
* Implementation of hook_fieldgroup_types().
*/
function content_multigroup_fieldgroup_types() {
return array('multigroup' => t('Multigroup'));
}
/**
* Implementation of hook_fieldgroup_default_settings().
*/
function content_multigroup_fieldgroup_default_settings($group_type) {
if ($group_type == 'multigroup') {
module_load_include('inc', 'content', 'includes/content.admin');
$settings = array('multigroup' => array('multiple' => 1));
foreach (array_keys(content_build_modes()) as $key) {
$settings['display'][$key]['format'] = 'fieldset';
}
return $settings;
}
}
/**
* Implementation of hook_form_alter().
*/
function content_multigroup_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'content_field_edit_form' && isset($form['widget'])) {
// If this is a field edit form and the field is in a Multigroup,
// override the multiple value settings.
$content_type = content_types($form['type_name']['#value']);
$groups = fieldgroup_groups($content_type['type']);
$group_name = _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']);
$group = isset($groups[$group_name]) ? $groups[$group_name] : array();
if (!empty($group) && $group['group_type'] == 'multigroup') {
$form['field']['multiple']['#value'] = $group['settings']['multigroup']['multiple'];
$form['field']['multiple']['#access'] = FALSE;
}
}
elseif ($form_id == 'content_field_edit_form' && isset($form_state['change_basic'])) {
// This is the basic field settings form. It should not be possible to
// choose a widget type that is not compatible with multigroups.
module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
content_multigroup_field_basic_form($form, $form_state);
}
elseif ($form_id == 'content_field_overview_form') {
// Validation for creating/moving fields and groups on the
// Manage Fields screen.
module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
$form['#validate'][] = 'content_multigroup_field_overview_form_validate';
}
elseif ($form_id == 'content_display_overview_form' && !empty($form['#groups'])) {
// Add an additional selector for setting multigroup field display
// format to the Display Fields screen.
module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
content_multigroup_display_overview_form($form, $form_state);
}
elseif ($form_id == 'fieldgroup_group_edit_form') {
// Alter the Fieldgroup edit form to add Multigroup settings.
module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
content_multigroup_group_edit_form($form, $form_state);
}
}
/**
* After build callback for multigroups in node form.
*
* This proxy function is necessary to prevent from breaking AHAH handlers.
*/
function content_multigroup_node_form_after_build($form, &$form_state) {
module_load_include('inc', 'content_multigroup', 'content_multigroup.node_form');
return _content_multigroup_node_form_after_build($form, $form_state);
}
/**
* Implementation of hook_fieldgroup_form().
*/
function content_multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) {
$group_name = $group['group_name'];
if ($group['group_type'] == 'multigroup' && !empty($form[$group_name])) {
if (!isset($form[$group_name]['#access']) || $form[$group_name]['#access']) {
module_load_include('inc', 'content_multigroup', 'content_multigroup.node_form');
_content_multigroup_fieldgroup_form($form, $form_state, $form_id, $group);
}
}
}
/**
* Implementation of hook_fieldgroup_view().
*/
function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) {
if ($group['group_type'] == 'multigroup') {
module_load_include('inc', 'content_multigroup', 'content_multigroup.node_view');
_content_multigroup_fieldgroup_view($node, $element, $group, $context);
}
}

View file

@ -0,0 +1,984 @@
<?php
/**
* @file
* Implementation of node edit functions for content multigroup.
*/
/**
* Implementation of hook_fieldgroup_form().
*
* Align the delta values of each field in the Multigroup.
*
* Swap the field name and delta for each Multigroup so we can
* d-n-d each collection of fields as a single delta item.
*/
function _content_multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) {
$node = $form['#node'];
$group_name = $group['group_name'];
$group_multiple = (int)$group['settings']['multigroup']['multiple'];
$content_type = content_types($group['type_name']);
// Build list of accessible fields in this group.
$group_fields = array();
foreach ($content_type['fields'] as $field_name => $field) {
if (isset($group['fields'][$field_name]) && isset($form[$group_name][$field_name])) {
if (!isset($form[$group_name][$field_name]['#access']) || $form[$group_name][$field_name]['#access']) {
$group_fields[$field_name] = $field;
}
}
}
// Quit if there are no field in the form for this group.
if (empty($group_fields)) {
return;
}
switch ($group_multiple) {
case 0:
$group_deltas = array(0);
$max_delta = 0;
break;
case 1:
// Compute unique deltas from all deltas used by fields in this multigroup.
$group_deltas = array();
$max_delta = -1;
foreach (array_keys($group_fields) as $field_name) {
if (!empty($node->$field_name) && is_array($node->$field_name)) {
foreach (array_keys($node->$field_name) as $delta) {
$group_deltas[$delta] = $delta;
}
sort($group_deltas);
$max_delta = max($max_delta, max($group_deltas));
}
}
$current_item_count = isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : max(1, count($group_deltas));
while (count($group_deltas) < $current_item_count) {
$max_delta++;
$group_deltas[] = $max_delta;
}
break;
default:
$max_delta = $group_multiple - 1;
$group_deltas = range(0, $max_delta);
break;
}
$form[$group_name]['#theme'] = 'content_multigroup_node_form';
$form[$group_name]['#item_count'] = count($group_deltas);
$form[$group_name]['#type_name'] = $group['type_name'];
$form[$group_name]['#group_name'] = $group_name;
$form[$group_name]['#group_label'] = $group['label'];
$form[$group_name]['#group_fields'] = $group_fields;
$form[$group_name]['#tree'] = TRUE;
// Multigroups cannot be vertical tabs, don't let Vertical Tabs module try to do that.
$form[$group_name]['#group'] = FALSE;
if (!isset($form['#multigroups'])) {
$form['#multigroups'] = array();
}
$form['#multigroups'][$group_name] = $group_fields;
// Add a visual indication to the fieldgroup title if the multigroup is required.
if (!empty($group['settings']['multigroup']['required'])) {
$form[$group_name]['#title'] .= '&nbsp;<span class="form-required" title="'. t('This group requires one collection of fields minimum.') .'">*</span>';
}
// Attach our own after build handler to the form, used to fix posting data
// and the form structure, moving fields back to their original positions.
// That is, move them from group->delta->field back to field->delta.
if (!isset($form['#after_build'])) {
$form['#after_build'] = array();
}
if (!in_array('content_multigroup_node_form_after_build', $form['#after_build'])) {
array_unshift($form['#after_build'], 'content_multigroup_node_form_after_build');
}
// Attach our own validation handler to the form, used to check for empty fields.
if (!isset($form['#validate'])) {
$form['#validate'] = array();
}
if (!in_array('content_multigroup_node_form_validate', $form['#validate'])) {
array_unshift($form['#validate'], 'content_multigroup_node_form_validate');
}
$elements[$group_name] = array();
foreach ($group_deltas as $delta) {
$element = content_multigroup_group_form($form, $form_state, $group, $delta);
$elements[$group_name] = array_merge($elements[$group_name], $element[$group_name]);
}
$form[$group_name] = $elements[$group_name];
// Unset the original group field values now that we've moved them.
foreach (array_keys($group_fields) as $field_name) {
unset($form[$group_name][$field_name]);
}
// Disable required flag during FormAPI validation, except when building the
// form for an 'Add more values' request, then replace it in pre_render.
// To avoid re-creating this array of values over and over, create it once
// and store it as a form attribute.
$form['#multigroup_required_fields'] = array();
if (empty($form_state['multigroup_add_more'])) {
foreach ($form['#multigroups'] as $group_name => $group_fields) {
$form['#multigroup_required_fields'][$group_name] = array();
foreach ($group_fields as $field_name => $field) {
if ($field['required']) {
$form['#multigroup_required_fields'][$group_name][$field_name] = TRUE;
$form['#field_info'][$field_name]['required'] = FALSE;
}
}
}
if (!isset($form['#pre_render'])) {
$form['#pre_render'] = array();
}
if (!in_array('content_multigroup_node_form_pre_render', $form['#pre_render'])) {
array_unshift($form['#pre_render'], 'content_multigroup_node_form_pre_render');
}
}
if (($add_more = content_multigroup_add_more($form, $form_state, $group)) !== FALSE) {
$form[$group_name] += $add_more;
}
}
/**
* Create a new delta value for the group.
*
* Called in form_alter and by AHAH add more.
*/
function content_multigroup_group_form(&$form, &$form_state, $group, $delta) {
module_load_include('inc', 'content', 'includes/content.node_form');
$element = array();
$type_name = $group['type_name'];
$content_type = content_types($type_name);
$group_name = $group['group_name'];
if (!isset($form[$group_name])) {//nested AHAH, not initial build
$element[$group_name] = array_shift(content_get_nested_elements($form, $group_name));
}
else {//initial build (via content_multigroup_fieldgroup_form) or non-nested AHAH
$element[$group_name] = $form[$group_name];
}
if (($group['group_type'] != 'multigroup')
|| (!(empty($element[$group['group_name']]['#access'])) && $element[$group['group_name']]['#access'] != TRUE)
|| empty($element[$group['group_name']])) {
return;
}
$group_fields = $form['#multigroups'][$group_name];
$element[$group_name]['#fields'] = array_keys($group_fields);
$node = $form['#node'];
$group_multiple = $group['settings']['multigroup']['multiple'];
foreach ($group_fields as $field_name => $field) {
if (empty($element[$group_name][$delta])) {
$element[$group_name] += array($delta => array($field_name => array()));
}
else {
$element[$group_name][$delta][$field_name] = array();
}
$item_count = (isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : $element[$group_name]['#item_count']);
$element[$group_name][$delta]['_weight'] = array(
'#type' => 'weight',
'#delta' => $item_count, // this 'delta' is the 'weight' element's property
'#default_value' => $delta,
'#weight' => 100,
);
// Add a checkbox to allow users remove a single delta subgroup.
// See content_set_empty() and theme_content_multigroup_node_form().
if ($group_multiple == 1) {
$element[$group_name][$delta]['_remove'] = array(
'#type' => 'checkbox',
'#attributes' => array('class' => 'content-multiple-remove-checkbox'),
'#default_value' => isset($form_state['multigroup_removed'][$group_name][$delta]) ? $form_state['multigroup_removed'][$group_name][$delta] : 0,
);
}
// Make each field into a pseudo single value field
// with the right delta value.
$field['multiple'] = 0;
$form['#field_info'][$field_name] = $field;
$node_copy = drupal_clone($node);
// Set the form '#node' to the delta value we want so the Content
// module will feed the right $items to the field module in
// content_field_form().
// There may be missing delta values for fields that were
// never created, so check first.
if (!empty($node->$field_name) && isset($node->{$field_name}[$delta])) {
$node_copy->$field_name = array($delta => $node->{$field_name}[$delta]);
}
else {
$value = NULL;
// Try to obtain default values only if the node is being created.
if (!isset($node->nid) && content_callback('widget', 'default value', $field) != CONTENT_CALLBACK_NONE) {
// If a module wants to insert custom default values here,
// it should provide a hook_default_value() function to call,
// otherwise the content module's content_default_value() function
// will be used.
$callback = content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] .'_default_value' : 'content_default_value';
if (function_exists($callback)) {
$items = $callback($form, $form_state, $field, 0);
$value = !empty($items) ? $items[0] : '';
}
}
$node_copy->$field_name = array($delta => $value);
}
$form['#node'] = $node_copy;
// Place the new element into the $delta position in the group form.
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
$field_form = content_field_form($form, $form_state, $field, $delta);
$value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0;
$element[$group_name][$delta][$field_name] = $field_form[$field_name][$value];
}
else {
// When the form is submitted, get the element data from the form values.
if (isset($form_state['values'][$field_name])) {
$form_state_copy = $form_state;
if (isset($form_state_copy['values'][$field_name][$delta])) {
$form_state_copy['values'][$field_name] = array($delta => $form_state_copy['values'][$field_name][$delta]);
}
else {
$form_state_copy['values'][$field_name] = array($delta => NULL);
}
$field_form = content_field_form($form, $form_state_copy, $field, $delta);
}
else {
$field_form = content_field_form($form, $form_state, $field, $delta);
}
// Multiple value fields have an additional level in the array form that
// needs to get fixed in $form_state['values'].
if (!isset($field_form[$field_name]['#element_validate'])) {
$field_form[$field_name]['#element_validate'] = array();
}
$field_form[$field_name]['#element_validate'][] = 'content_multigroup_fix_multivalue_fields';
$element[$group_name][$delta][$field_name] = $field_form[$field_name];
}
$element[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight'];
}
// Reset the form '#node' back to its original value.
$form['#node'] = $node;
return $element;
}
/**
* Fix required flag during form rendering stage.
*
* Required fields should display the required star in the rendered form.
*/
function content_multigroup_node_form_pre_render(&$form) {
foreach ($form['#multigroups'] as $group_name => $group_fields) {
if (!empty($form['#multigroup_required_fields'][$group_name])) {
$required_fields = array_keys($form['#multigroup_required_fields'][$group_name]);
content_multigroup_node_form_fix_required($form[$group_name], $required_fields, TRUE);
}
}
return $form;
}
/**
* Fix form and posting data when the form is submitted.
*
* FormAPI uses form_builder() during form processing to map incoming $_POST
* data to the proper elements in the form. It builds the '#parents' array,
* copies the $_POST array to the '#post' member of all form elements, and it
* also builds the $form_state['values'] array. Then the '#after_build' hook is
* invoked to allow custom processing of the form structure, and that happens
* just before validation and submit handlers are executed.
*
* During hook_form_alter(), the multigroup module altered the form structure
* moving elements from field->delta to multigroup->delta->field position,
* which is what has been processed by FormAPI to build the form structures,
* but field validation (and submit) handlers expect their data to be located
* in their original positions.
*
* We now need to move the fields back to their original positions in the form,
* and we need to do so without altering the form rendering process, which is
* now reflecting the structure the multigroup is interested in. We just need
* to fix the parts of the form that affect validation and submit processing.
*/
function _content_multigroup_node_form_after_build($form, &$form_state) {
// Disable required flag during FormAPI validation, except when building the
// form for an 'Add more values' request.
$required = !empty($form_state['multigroup_add_more']);
foreach ($form['#multigroups'] as $group_name => $group_fields) {
if (!empty($form['#multigroup_required_fields'][$group_name])) {
$required_fields = array_keys($form['#multigroup_required_fields'][$group_name]);
content_multigroup_node_form_fix_required($form[$group_name], $required_fields, $required);
}
}
if ($form_state['submitted'] && !$form_state['multigroup_add_more']) {
// Fix value positions in $form_state for the fields in multigroups.
foreach (array_keys($form['#multigroups']) as $group_name) {
content_multigroup_node_form_transpose_elements($form, $form_state, $form['#node']->type, $group_name);
}
// Fix form element parents for all fields in multigroups.
content_multigroup_node_form_fix_parents($form, $form['#multigroups']);
// Update posting data to reflect delta changes in the form structure.
if (!empty($_POST)) {
content_multigroup_node_form_fix_post($form);
}
}
return $form;
}
/**
* Fix required flag for required fields.
*
* We need to let the user enter an empty set of fields for a delta subgroup,
* even if it contains required fields, which is equivalent to say a subgroup
* should be ignored, not to be stored into the database.
* So, we need to check for required fields, but only for non-empty subgroups.
*
* When the form is processed for rendering, the required flag is enabled for
* all required fields, so the user can see what's required and what's not.
*
* When the form is processed for validation, the required flag is disabled,
* so that FormAPI does not report errors for empty fields.
*
* @see content_multigroup_node_form_validate().
*/
function content_multigroup_node_form_fix_required(&$elements, $required_fields, $required) {
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {
if (count($elements[$key]['#parents']) >= 3 && in_array($elements[$key]['#parents'][2], $required_fields) && isset($elements[$key]['#required'])) {
$elements[$key]['#required'] = $required;
// Required option lists do not have an empty option available. Add one to avoid 'An illegal choice has been detected' errors.
if (!$required && !empty($elements[$key]['#options']) && substr($elements[$key]['#id'], -7) != '_weight') {
$empty = array('' => '');
$elements[$key]['#options'] = $empty + $elements[$key]['#options'];
}
}
// Recurse through all children elements.
content_multigroup_node_form_fix_required($elements[$key], $required_fields, $required);
}
}
}
/**
* Node form validation handler.
*
* Perform validation for empty fields ignoring subgroups flagged for removal.
* Note that FormAPI validation for required fields is disabled because we need
* to accept empty fields that are flagged for removal.
*/
function content_multigroup_node_form_validate($form, &$form_state) {
$type_name = $form['#node']->type;
$groups = fieldgroup_groups($type_name);
foreach ($form['#multigroups'] as $group_name => $group_fields) {
$group = $groups[$group_name];
$group_required = isset($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 0;
$non_empty_subgroups = $non_removed_subgroups = $required_field_errors = array();
foreach ($group_fields as $field_name => $field) {
// Tell the content module that it is not needed to enforce requirement
// of fields in this multigroup because we are doing it here.
// See content_multiple_value_nodeapi_validate().
$form_state['values']['_content_ignore_required_fields'][$field_name] = TRUE;
foreach ($form_state['values'][$field_name] as $delta => $item) {
// Keep track of the highest delta value for this group.
$max_delta = $delta;
// Ignore subgroups flagged for removal.
if ($form_state['multigroup_removed'][$group_name][$delta]) {
continue;
}
// Keep track of non-removed subgroups.
$non_removed_subgroups[$delta] = TRUE;
$is_empty_function = $field['module'] .'_content_is_empty';
if ($is_empty_function($form_state['values'][$field_name][$delta], $field)) {
// Ignore fields that are not required.
if (!$field['required']) {
continue;
}
// Build an error message for this field in this subgroup, but do
// not flag it, yet.
if (!empty($item['_error_element'])) {
// Here we don't know the number of elements and subelements a
// widget could have added to the form, so we need to extract
// components from the top, where we have group/delta/field, and
// then push back field/delta on top of the list.
$error_element = explode('][', $item['_error_element']);
array_shift($error_element);
array_shift($error_element);
array_shift($error_element);
array_unshift($error_element, $field_name, $delta);
$error_element = implode('][', $error_element);
}
else {
// Imagefield does not use error_element, sets error on the field.
// Are there others that need different treatment?
$error_element = $field_name;
}
$required_field_errors[$delta][$field_name] = array(
'element' => $error_element,
'message' => t('!name field is required in group @group.', array(
'!name' => $form[$group_name][$delta][$field_name]['#title'],
'@group' => t($group['label']),
)),
);
}
else {
$non_empty_subgroups[$delta] = TRUE;
}
}
}
// Required multigroups require at least one non-empty subgroup of fields.
if ($group_required && empty($non_empty_subgroups)) {
form_set_error('', t('Group @name requires one collection of fields minimum.', array('@name' => t($group['label']))));
continue;
}
// Force remove any empty groups so they will collapse.
// Don't flag errors on empty groups.
for ($delta = 0; $delta <= $max_delta; $delta++) {
if (!isset($non_empty_subgroups[$delta]) && isset($non_removed_subgroups[$delta])) {
unset($non_removed_subgroups[$delta]);
$form_state['multigroup_removed'][$group_name][$delta] = TRUE;
foreach ($group_fields as $field_name => $item) {
$form_state['values'][$field_name][$delta]['_remove'] = TRUE;
}
if (isset($required_field_errors[$delta])) {
unset($required_field_errors[$delta]);
}
}
}
// Ok, now we can flag errors for all required fields that have not been
// filled in when they should.
foreach ($required_field_errors as $delta => $error_list) {
foreach ($error_list as $field_name => $error_info) {
form_set_error($error_info['element'], $error_info['message']);
}
}
}
}
/**
* Transpose element positions in $form_state for the fields in a multigroup.
*/
function content_multigroup_node_form_transpose_elements(&$form, &$form_state, $type_name, $group_name) {
$groups = fieldgroup_groups($type_name);
$group = $groups[$group_name];
$group_fields = $form['#multigroups'][$group_name];
// Save the remove state of multigroup items in the $form_state array.
if (!isset($form_state['multigroup_removed'])) {
$form_state['multigroup_removed'] = array();
}
if (!isset($form_state['multigroup_removed'][$group_name])) {
$form_state['multigroup_removed'][$group_name] = array();
}
// Move group data from group->delta->field to field->delta.
$group_data = array();
foreach ($form_state['values'][$group_name] as $delta => $items) {
// Skip 'add more' button.
if (!is_array($items) || !isset($items['_weight'])) {
continue;
}
foreach ($group_fields as $field_name => $field) {
if (!isset($group_data[$field_name])) {
$group_data[$field_name] = array();
}
// Get field weight and remove state from the group and keep track of the
// current delta for each field item.
$item_defaults = array(
'_weight' => $items['_weight'],
'_remove' => $items['_remove'],
'_old_delta' => $delta,
);
$group_data[$field_name][$delta] = (is_array($items[$field_name]) ? array_merge($items[$field_name], $item_defaults) : $item_defaults);
// Store the remove state and the element weight in the form element as
// well, so we can restore them later.
// See content_multigroup_fix_multivalue_fields().
// See content_multigroup_fix_element_values().
$form[$group_name][$delta][$field_name]['#_weight'] = $items['_weight'];
$form[$group_name][$delta][$field_name]['#removed'] = $items['_remove'];
// Insert an element valitation callback of our own at the end of the
// list to ensure the drag'n'drop weight of the element is not lost by
// a form_set_value() operation made by the validation callback of the
// widget element.
if (!isset($form[$group_name][$delta][$field_name]['#element_validate'])) {
$form[$group_name][$delta][$field_name]['#element_validate'] = array();
}
$form[$group_name][$delta][$field_name]['#element_validate'][] = 'content_multigroup_fix_element_values';
}
$form_state['multigroup_removed'][$group_name][$delta] = $items['_remove'];
}
$form_group_sorted = FALSE;
foreach ($group_data as $field_name => $items) {
// Sort field items according to drag-n-drop reordering. Deltas are also
// rebuilt to start counting from 0 to n. Note that since all fields in the
// group share the same weight, their deltas remain in sync.
usort($items, '_content_sort_items_helper');
// Now we need to apply the same ordering to the form elements. Also,
// note that deltas have changed during the sort operation, so we need
// to reflect this delta conversion in the form.
if (!$form_group_sorted) {
$form_group_items = array();
$form_deltas = array();
foreach ($items as $new_delta => $item) {
$form_deltas[$item['_old_delta']] = $new_delta;
$form_group_items[$new_delta] = $form[$group_name][$item['_old_delta']];
unset($form[$group_name][$item['_old_delta']]);
}
foreach ($form_group_items as $new_delta => $form_group_item) {
$form[$group_name][$new_delta] = $form_group_item;
}
content_multigroup_node_form_fix_deltas($form[$group_name], $form_deltas);
$form_group_sorted = TRUE;
}
// Get rid of the old delta value.
foreach (array_keys($items) as $delta) {
unset($items[$delta]['_old_delta']);
}
// Fix field and delta positions in the $_POST array.
if (!empty($_POST)) {
$_POST[$field_name] = array();
foreach ($items as $new_delta => $item) {
$_POST[$field_name][$new_delta] = $item;
}
if (isset($_POST[$group_name])) {
unset($_POST[$group_name]);
}
}
// Move field items back to their original positions.
$form_state['values'][$field_name] = $items;
}
// Finally, get rid of the group data in form values.
unset($form_state['values'][$group_name]);
}
/**
* Fix deltas for all affected form elements.
*/
function content_multigroup_node_form_fix_deltas(&$elements, $form_deltas) {
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key] && substr($key, -9) != '_add_more') {
// Fix the second item, the delta value, of the element's '#parents' array.
$elements[$key]['#parents'][1] = $form_deltas[$elements[$key]['#parents'][1]];
// If present, fix delta value in '#delta' attribute of the element.
if (isset($elements[$key]['#delta']) && isset($form_deltas[$elements[$key]['#delta']])) {
$elements[$key]['#delta'] = $form_deltas[$elements[$key]['#delta']];
}
// Recurse through all children elements.
content_multigroup_node_form_fix_deltas($elements[$key], $form_deltas);
}
}
}
/**
* Fix form element parents for all fields in multigroups.
*
* The $element['#parents'] array needs to reflect the position of the fields
* in the $form_state['values'] array so that form_set_value() can be safely
* used by field validation handlers.
*/
function content_multigroup_node_form_fix_parents(&$elements, $multigroups) {
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {
// Check if the current element is child of a multigroup. The #parents
// array for field values has, at least, 3 parent elements, being the
// first one the name of a multigroup.
if (count($elements[$key]['#parents']) >= 3 && isset($multigroups[$elements[$key]['#parents'][0]])) {
// Extract group name, delta and field name from the #parents array.
array_shift($elements[$key]['#parents']);
$delta = array_shift($elements[$key]['#parents']);
$field_name = array_shift($elements[$key]['#parents']);
// Now, insert field name and delta to the #parents array.
array_unshift($elements[$key]['#parents'], $field_name, $delta);
}
// Recurse through all children elements.
content_multigroup_node_form_fix_parents($elements[$key], $multigroups);
}
}
}
/**
* Update posting data to reflect delta changes in the form structure.
*
* The $_POST array is fixed in content_multigroup_node_form_transpose_elements().
*/
function content_multigroup_node_form_fix_post(&$elements) {
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {
// Update the element copy of the $_POST array.
$elements[$key]['#post'] = $_POST;
// Recurse through all children elements.
content_multigroup_node_form_fix_post($elements[$key]);
}
}
// Update the form copy of the $_POST array.
$elements['#post'] = $_POST;
}
/**
* Make sure the '_weight' and '_remove' attributes of the element exist.
*
* @see content_multigroup_node_form_transpose_elements()
*/
function content_multigroup_fix_element_values($element, &$form_state) {
$field_name = $element['#field_name'];
$delta = $element['#delta'];
if (!isset($form_state['values'][$field_name][$delta]['_weight']) || !isset($form_state['values'][$field_name][$delta]['_remove'])) {
$value = array('_weight' => $element['#_weight'], '_remove' => $element['#removed']);
if (isset($form_state['values'][$field_name][$delta]) && is_array($form_state['values'][$field_name][$delta])) {
$value = array_merge($form_state['values'][$field_name][$delta], $value);
}
form_set_value($element, $value, $form_state);
}
}
/**
* Fix the value for fields that deal with multiple values themselves.
*/
function content_multigroup_fix_multivalue_fields($element, &$form_state) {
$field_name = $element['#field_name'];
$delta = $element['#delta'];
if (isset($form_state['values'][$field_name][$delta][0]) && is_array($form_state['values'][$field_name][$delta][0])) {
$value = array_merge($form_state['values'][$field_name][$delta][0], array('_remove' => $element['#removed']));
}
else {
$value = array('_remove' => $element['#removed']);
}
form_set_value($element, $value, $form_state);
}
/**
* Add AHAH add more button, if not working with a programmed form.
*/
function content_multigroup_add_more(&$form, &$form_state, $group) {
$group_multiple = $group['settings']['multigroup']['multiple'];
if ($group_multiple != 1 || !empty($form['#programmed'])) {
return FALSE;
}
// Make sure the form is cached so ahah can work.
$form['#cache'] = TRUE;
$content_type = content_types($group['type_name']);
$group_name = $group['group_name'];
$group_name_css = str_replace('_', '-', $group_name);
$form_element = array();
$form_element[$group_name .'_add_more'] = array(
'#type' => 'submit',
'#name' => $group_name .'_add_more',
'#value' => theme('content_multigroup_add_more_label', $group_name),
'#weight' => $group_multiple + 1,
'#submit' => array('content_multigroup_add_more_submit'),
'#ahah' => array(
'path' => 'content_multigroup/js_add_more/'. $content_type['url_str'] .'/'. $group_name,
'wrapper' => $group_name_css .'-items',
'method' => 'replace',
'effect' => 'fade',
),
// When JS is disabled, the content_multigroup_add_more_submit handler will
// find the relevant group information using these entries.
'#group_name' => $group_name,
'#type_name' => $group['type_name'],
'#item_count' => $form[$group_name]['#item_count'],
);
// Add wrappers for the group and 'more' button.
$form_element['#prefix'] = '<div id="'. $group_name_css .'-items">';
$form_element['#suffix'] = '</div>';
$form_element[$group_name .'_add_more']['#prefix'] = '<div class="content-add-more clear-block">';
$form_element[$group_name .'_add_more']['#suffix'] = '</div>';
return $form_element;
}
/**
* Submit handler to add more choices to a content form. This handler is used when
* JavaScript is not available. It makes changes to the form state and the
* entire form is rebuilt during the page reload.
*/
function content_multigroup_add_more_submit($form, &$form_state) {
// Set the form to rebuild and run submit handlers.
node_form_submit_build_node($form, $form_state);
$group_name = $form_state['clicked_button']['#group_name'];
$type_name = $form_state['clicked_button']['#type_name'];
// Make the changes we want to the form state.
if (isset($form_state['clicked_button']['#item_count'])) {
$form_state['item_count'][$group_name] = $form_state['clicked_button']['#item_count'] + 1;
}
}
/**
* Menu callback for AHAH addition of new empty widgets.
*
* Adapted from content_add_more_js to work with groups instead of fields.
*/
function content_multigroup_add_more_js($type_name_url, $group_name) {
$content_type = content_types($type_name_url);
$groups = fieldgroup_groups($content_type['type']);
$group = $groups[$group_name];
if (($group['settings']['multigroup']['multiple'] != 1) || empty($_POST['form_build_id'])) {
// Invalid request.
drupal_json(array('data' => ''));
exit;
}
// Retrieve the cached form.
$form_state = array('submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);
if (!$form) {
// Invalid form_build_id.
drupal_json(array('data' => ''));
exit;
}
// We don't simply return a new empty widget to append to existing ones, because
// - ahah.js won't simply let us add a new row to a table
// - attaching the 'draggable' behavior won't be easy
// So we resort to rebuilding the whole table of widgets including the existing ones,
// which makes us jump through a few hoops.
// The form that we get from the cache is unbuilt. We need to build it so that
// _value callbacks can be executed and $form_state['values'] populated.
// We only want to affect $form_state['values'], not the $form itself
// (built forms aren't supposed to enter the cache) nor the rest of $form_data,
// so we use copies of $form and $form_data.
$form_copy = $form;
$form_state_copy = $form_state;
$form_copy['#post'] = array();
form_builder($_POST['form_id'], $form_copy, $form_state_copy);
// Just grab the data we need.
$form_state['values'] = $form_state_copy['values'];
// Reset cached ids, so that they don't affect the actual form we output.
form_clean_id(NULL, TRUE);
// Sort the $form_state['values'] we just built *and* the incoming $_POST data
// according to d-n-d reordering.
unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']);
foreach ($_POST[$group_name] as $delta => $item) {
$form_state['values'][$group_name][$delta]['_weight'] = $item['_weight'];
$form_state['values'][$group_name][$delta]['_remove'] = isset($item['_remove']) ? $item['_remove'] : 0;
}
$group['multiple'] = $group['settings']['multigroup']['multiple'];
$form_state['values'][$group_name] = _content_sort_items($group, $form_state['values'][$group_name]);
$_POST[$group_name] = _content_sort_items($group, $_POST[$group_name]);
// Build our new form element for the whole group, asking for one more element.
$delta = max(array_keys($_POST[$group_name])) + 1;
$form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1);
$form_element = content_multigroup_group_form($form, $form_state, $group, $delta);
// Rebuild weight deltas to make sure they all are equally dimensioned.
foreach ($form_element[$group_name] as $key => $item) {
if (is_numeric($key) && isset($item['_weight']) && is_array($item['_weight'])) {
$form_element[$group_name][$key]['_weight']['#delta'] = $delta;
}
}
// Add the new element at the right place in the (original, unbuilt) form.
$success = content_set_nested_elements($form, $group_name, $form_element[$group_name]);
// Save the new definition of the form.
$form_state['values'] = array();
form_set_cache($form_build_id, $form, $form_state);
// Build the new form against the incoming $_POST values so that we can
// render the new element.
$_POST[$group_name][$delta]['_weight'] = $delta;
$form_state = array('submitted' => FALSE, 'multigroup_add_more' => TRUE);
$form += array(
'#post' => $_POST,
'#programmed' => FALSE,
);
$form = form_builder($_POST['form_id'], $form, $form_state);
// Render the new output.
$group_form = array_shift(content_get_nested_elements($form, $group_name));
// We add a div around the new content to receive the ahah effect.
$group_form[$delta]['#prefix'] = '<div class="ahah-new-content">'. (isset($group_form[$delta]['#prefix']) ? $group_form[$delta]['#prefix'] : '');
$group_form[$delta]['#suffix'] = (isset($group_form[$delta]['#suffix']) ? $group_form[$delta]['#suffix'] : '') .'</div>';
// Prevent duplicate wrapper.
unset($group_form['#prefix'], $group_form['#suffix']);
// We're in the AHAH handler, so the fieldset was expanded.
$group_form['#collapsed'] = FALSE;
// If a newly inserted widget contains AHAH behaviors, they normally won't
// work because AHAH doesn't know about those - it just attaches to the exact
// form elements that were initially specified in the Drupal.settings object.
// The new ones didn't exist then, so we need to update Drupal.settings
// by ourselves in order to let AHAH know about those new form elements.
$javascript = drupal_add_js(NULL, NULL);
$output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');</script>' : '';
$output = theme('status_messages') . drupal_render($group_form) . $output_js;
// Using drupal_json() breaks filefield's file upload, because the jQuery
// Form plugin handles file uploads in a way that is not compatible with
// 'text/javascript' response type.
$GLOBALS['devel_shutdown'] = FALSE;
print drupal_to_js(array('status' => TRUE, 'data' => $output));
exit;
}
/**
* Theme an individual form element.
*
* Combine multiple values into a table with drag-n-drop reordering.
*/
function theme_content_multigroup_node_form($element) {
$group_name = $element['#group_name'];
$groups = fieldgroup_groups($element['#type_name']);
$group = $groups[$group_name];
$group_multiple = $group['settings']['multigroup']['multiple'];
$group_fields = $element['#group_fields'];
$table_id = $element['#group_name'] .'_values';
$table_class = 'content-multiple-table';
$order_class = $element['#group_name'] .'-delta-order';
$subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
$show_label = isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above';
$subgroup_labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
$multiple_columns = isset($group['settings']['multigroup']['multiple-columns']) ? $group['settings']['multigroup']['multiple-columns'] : 0;
$headers = array();
if ($group_multiple >= 1) {
$headers[] = array('data' => '');
}
if ($multiple_columns) {
foreach ($group_fields as $field_name => $field) {
$required = !empty($field['required']) ? '&nbsp;<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';
$headers[] = array(
'data' => check_plain(t($field['widget']['label'])) . $required,
'class' => 'content-multigroup-cell-'. str_replace('_', '-', $field_name),
);
}
$table_class .= ' content-multigroup-edit-table-multiple-columns';
}
else {
if ($group_multiple >= 1) {
$headers[0]['colspan'] = 2;
}
$table_class .= ' content-multigroup-edit-table-single-column';
}
if ($group_multiple >= 1) {
$headers[] = array('data' => t('Order'), 'class' => 'content-multiple-weight-header');
if ($group_multiple == 1) {
$headers[] = array('data' => '<span>'. t('Remove') .'</span>', 'class' => 'content-multiple-remove-header');
}
}
$rows = array();
$i = 0;
foreach (element_children($element) as $delta => $key) {
if (is_numeric($key)) {
$cells = array();
$label = ($show_label == 'above' && !empty($subgroup_labels[$i]) ? theme('content_multigroup_node_label', check_plain(t($subgroup_labels[$i]))) : '');
$element[$key]['_weight']['#attributes']['class'] = $order_class;
if ($group_multiple >= 1) {
$cells[] = array('data' => '', 'class' => 'content-multiple-drag');
$delta_element = drupal_render($element[$key]['_weight']);
if ($group_multiple == 1) {
$remove_element = drupal_render($element[$key]['_remove']);
}
}
else {
$element[$key]['_weight']['#type'] = 'hidden';
}
if ($multiple_columns) {
foreach ($group_fields as $field_name => $field) {
$cell = array(
'data' => (isset($element[$key][$field_name]) ? drupal_render($element[$key][$field_name]) : ''),
'class' => 'content-multigroup-cell-'. str_replace('_', '-', $field_name),
);
if (!empty($cell['data']) && !empty($element[$key][$field_name]['#description'])) {
$cell['title'] = $element[$key][$field_name]['#description'];
}
$cells[] = $cell;
}
}
else {
$cells[] = $label . drupal_render($element[$key]);
}
if ($group_multiple >= 1) {
$row_class = 'draggable';
$cells[] = array('data' => $delta_element, 'class' => 'delta-order');
if ($group_multiple == 1) {
if (!empty($element[$key]['_remove']['#value'])) {
$row_class .= ' content-multiple-removed-row';
}
$cells[] = array('data' => $remove_element, 'class' => 'content-multiple-remove-cell');
}
$rows[] = array('data' => $cells, 'class' => $row_class);
}
else {
$rows[] = array('data' => $cells);
}
}
$i++;
}
drupal_add_css(drupal_get_path('module', 'content_multigroup') .'/content_multigroup.css');
$output = theme('table', $headers, $rows, array('id' => $table_id, 'class' => $table_class));
$output .= drupal_render($element[$group_name .'_add_more']);
// Enable drag-n-drop only if the group really allows multiple values.
if ($group_multiple >= 1) {
drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
drupal_add_js(drupal_get_path('module', 'content') .'/js/content.node_form.js');
}
return $output;
}
/**
* Theme the sub group label in the node form.
*/
function theme_content_multigroup_node_label($text) {
return !empty($text) ? '<h3>'. $text .'</h3>' : '';
}
/**
* Theme the label for the "Add more values" button
*/
function theme_content_multigroup_add_more_label($group_name) {
return t('Add more values');
}

View file

@ -0,0 +1,262 @@
<?php
/**
* @file
* Implementation of node view functions for content multigroup.
*/
/**
* Implementation of hook_fieldgroup_view().
*/
function _content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) {
drupal_add_css(drupal_get_path('module', 'content_multigroup') .'/content_multigroup.css');
$node_copy = drupal_clone($node);
$group_name = $group['group_name'];
$group_class = 'content-multigroup-'. str_replace('_', '-', $group_name);
$group_multiple = (int)$group['settings']['multigroup']['multiple'];
$subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
$show_label = isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above';
$subgroup_labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
$subgroup_format = isset($subgroup_settings[$context]['format']) ? $subgroup_settings[$context]['format'] : 'fieldset';
$content_type = content_types($group['type_name']);
// Build list of accessible fields in this group.
$group_fields = array();
foreach ($content_type['fields'] as $field_name => $field) {
if (isset($group['fields'][$field_name]) && isset($element[$field_name])) {
if (!isset($element[$field_name]['#access']) || $element[$field_name]['#access']) {
$group_fields[$field_name] = $field;
}
}
}
switch ($group_multiple) {
case 0:
$group_deltas = array(0);
break;
case 1:
// Compute unique deltas from all deltas used by fields in this multigroup.
$group_deltas = array();
foreach (array_keys($group_fields) as $field_name) {
if (is_array($node->content[$field_name]['field']['items'])) {
foreach (array_keys($node->content[$field_name]['field']['items']) as $delta) {
$group_deltas[$delta] = $delta;
}
}
}
sort($group_deltas);
break;
default:
$group_deltas = range(0, $group_multiple - 1);
break;
}
foreach ($group_deltas as $index => $delta) {
$element[$delta] = array(
'#title' => ($show_label == 'above' && !empty($subgroup_labels[$index]) ? check_plain(t($subgroup_labels[$index])) : ''),
'#attributes' => array('class' => 'content-multigroup-wrapper content-multigroup-'. $index),
'#weight' => $delta,
);
// Create a pseudo node that only has the value we want in this group and
// pass it to the formatter.
// @TODO Watch this change and be sure it doesn't break anything else.
// The delta was previously always set to '1' to keep CCK's inline label
// from assigning a class of 'first'. But changing it back to the right
// delta for the individual item makes the formatters easier to manage
// since the delta is always the correct delta and not occasionally
// a bogus value. It aslo fixes issues like http://drupal.org/node/766734
// by keeping the layout of multigroups the same as other fieldgroups.
foreach (array_keys($group_fields) as $field_name) {
if (isset($node->content[$field_name])) {
$node_copy->content[$field_name]['field']['items'] = array(
$delta => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL,
);
$element[$delta][$field_name] = $node_copy->content[$field_name];
$element[$delta][$field_name]['#delta'] = $delta;
}
}
switch ($subgroup_format) {
case 'simple':
$element['#attributes']['class'] = $group_class;
$element[$delta]['#theme'] = 'content_multigroup_display_simple';
$element[$delta]['#fields'] = $group_fields;
break;
case 'fieldset_collapsed':
$element[$delta]['#collapsed'] = TRUE;
case 'fieldset_collapsible':
$element[$delta]['#collapsible'] = TRUE;
case 'fieldset':
$element['#attributes']['class'] = $group_class;
$element[$delta]['#type'] = 'content_multigroup_display_fieldset';
$element[$delta]['#fields'] = $group_fields;
break;
case 'hr':
$element['#attributes']['class'] = $group_class;
$element[$delta]['#theme'] = 'content_multigroup_display_hr';
$element[$delta]['#fields'] = $group_fields;
break;
case 'table-single':
$element['#theme'] = 'content_multigroup_display_table_single';
$element['#attributes']['class'] = $group_class .' content-multigroup-display-table-single-column';
$element['#fields'] = $group_fields;
break;
case 'table-multiple':
$element['#theme'] = 'content_multigroup_display_table_multiple';
$element['#attributes']['class'] = $group_class .' content-multigroup-display-table-multiple-columns';
$element['#fields'] = $group_fields;
break;
case 'ul':
$element['#theme'] = 'content_multigroup_display_ul';
$element['#attributes']['class'] = $group_class;
$element['#fields'] = $group_fields;
break;
}
}
// Unset the original group field values now that we've moved them.
foreach (array_keys($group_fields) as $field_name) {
if (isset($element[$field_name])) {
unset($element[$field_name]);
}
}
}
/**
* Theme a subgroup of fields in 'simple' format.
*
* No output is generated if all fields are empty.
*/
function theme_content_multigroup_display_simple($element) {
$children = $output = '';
foreach (element_children($element) as $key) {
$children .= drupal_render($element[$key]);
}
if (!empty($children)) {
$output .= '<div'. drupal_attributes($element['#attributes']) .'>';
if (!empty($element['#title'])) {
$output .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
}
$output .= $children .'</div>';
}
return $output;
}
/**
* Theme a subgroup of fields in 'fieldset' format.
*
* No output is generated if all fields are empty.
*/
function theme_content_multigroup_display_fieldset($element) {
if (empty($element['#children']) && empty($element['#value'])) {
return '';
}
return theme('fieldset', $element);
}
/**
* Theme a subgroup of fields in 'hr' format.
*
* No output is generated if all fields are empty.
*/
function theme_content_multigroup_display_hr($element) {
$children = $output = '';
foreach (element_children($element) as $key) {
$children .= drupal_render($element[$key]);
}
if (!empty($children)) {
$output .= '<div'. drupal_attributes($element['#attributes']) .'><hr class="content-multigroup" />';
if (!empty($element['#title'])) {
$output .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
}
$output .= $children .'</div>';
}
return $output;
}
/**
* Theme a multigroup in 'table-single' format.
*
* Each subgroup has its own table row with a single cell for all fields.
* No output is generated if all fields are empty.
*/
function theme_content_multigroup_display_table_single($element) {
$headers = array();
$rows = array();
foreach (element_children($element) as $delta) {
$items = array();
foreach ($element['#fields'] as $field_name => $field) {
$item = drupal_render($element[$delta][$field_name]);
if (!empty($item)) {
$items[] = $item;
}
}
if (!empty($items)) {
if (!empty($element[$delta]['#title'])) {
array_unshift($items, '<label class="content-multigroup">'. $element[$delta]['#title'] .':</label>');
}
$rows[] = array('data' => array(implode("\n", $items)), 'class' => $element[$delta]['#attributes']['class']);
}
}
return count($rows) ? theme('table', $headers, $rows, $element['#attributes']) : '';
}
/**
* Theme a multigroup in 'table-multiple' format.
*
* Each subgroup has its own table row with a separate cell for each field.
* No output is generated if all fields are empty.
*/
function theme_content_multigroup_display_table_multiple($element) {
$headers = array();
foreach ($element['#fields'] as $field_name => $field) {
$label_display = isset($field['display_settings']['label']['format']) ? $field['display_settings']['label']['format'] : 'above';
$headers[] = array(
'data' => ($label_display != 'hidden' ? check_plain(t($field['widget']['label'])) : ''),
'class' => 'content-multigroup-cell-'. str_replace('_', '-', $field_name),
);
}
$rows = array();
foreach (element_children($element) as $delta) {
$cells = array();
$empty = TRUE;
foreach ($element['#fields'] as $field_name => $field) {
$item = drupal_render($element[$delta][$field_name]);
$cells[] = array(
'data' => $item,
'class' => $element[$delta]['#attributes']['class'] .' content-multigroup-cell-'. str_replace('_', '-', $field_name),
);
if (!empty($item)) {
$empty = FALSE;
}
}
// Get the row only if there is at least one non-empty field.
if (!$empty) {
$rows[] = $cells;
}
}
return count($rows) ? theme('table', $headers, $rows, $element['#attributes']) : '';
}
/**
* Theme a subgroup of fields in an unordered list.
*
* No output is generated if all fields are empty.
*/
function theme_content_multigroup_display_ul($element) {
$items = array();
foreach (element_children($element) as $delta) {
foreach ($element['#fields'] as $field_name => $field) {
$item = drupal_render($element[$delta][$field_name]);
if (!empty($item)) {
$items[] = $item;
}
}
}
$element['#attributes']['class'] .= ' content-multigroup';
return count($items) ? theme('item_list', $items, $element['#title'], 'ul', $element['#attributes']) : '';
}

View file

@ -0,0 +1,174 @@
<?php
/**
* @file
* This file provides a CTools content type for multigroups.
*/
/**
* Callback function to supply a list of content types.
*/
function content_multigroup_content_multigroup_ctools_content_types() {
return array(
'title' => t('Content multigroup'),
'defaults' => array('label' => 'hidden', 'format' => 'simple', 'subgroup' => 'fieldset', 'empty' => ''),
);
}
/**
* Return all multigroup content types available.
*/
function content_multigroup_content_multigroup_content_type_content_types() {
// This will hold all the individual multigroup content types.
$types = array();
// The outer loop goes through each node type with groups.
foreach (fieldgroup_groups() as $node_type_groups) {
// The inner loop gives us each fieldgroup on each node type with groups.
foreach ($node_type_groups as $group) {
// Skip field groups that are not of multigroup type.
if ($group['group_type'] != 'multigroup') {
continue;
}
// Name the content type a combination of fieldgroup and node type names.
$content_type_name = $group['type_name'] . ':' . $group['group_name'];
// Assemble the information about the content type.
$info = array(
'category' => t('Node'),
'icon' => 'icon_cck_field_group.png',
'title' => t('Multigroup: @group in @type', array(
'@group' => t($group['label']),
'@type' => node_get_types('name', $group['type_name']),
)),
'description' => t('All fields from this field group on the referenced node.'),
'required context' => new ctools_context_required(t('Node'), 'node', array('type' => array($group['type_name']))),
);
$types[$content_type_name] = $info;
}
}
return $types;
}
/**
* Output function for the 'multigroup' content type.
*/
function content_multigroup_content_multigroup_content_type_render($subtype, $conf, $panel_args, $context) {
if (!isset($context->data)) {
return;
}
$node = drupal_clone($context->data);
// Extract the node type and fieldgroup name from the subtype.
list($node_type, $group_name) = explode(':', $subtype, 2);
// Get a list of all fieldgroups for this node type.
$groups = fieldgroup_groups($node_type);
if (!isset($groups[$group_name])) {
return;
}
$group = $groups[$group_name];
// Render the field group.
$node->build_mode = NODE_BUILD_NORMAL;
$group['settings']['display']['label'] = $conf['label'] == 'normal' || !empty($conf['override_title']) ? 'hidden' : $conf['label'];
$group['settings']['display']['full']['format'] = $conf['format'];
$group['settings']['display']['full']['exclude'] = 0;
$group['settings']['multigroup']['subgroup']['full']['format'] = $conf['subgroup'];
$group['settings']['multigroup']['subgroup']['full']['exclude'] = 0;
$output = fieldgroup_view_group($group, $node);
$block = new stdClass();
if ($conf['label'] == 'normal') {
$block->title = t($group['label']);
}
$block->content = !empty($output) ? $output : $conf['empty'];
return $block;
}
/**
* Returns a settings form for the custom type.
*/
function content_multigroup_content_multigroup_content_type_edit_form(&$form, &$form_state) {
$conf = $form_state['conf'];
$label_options = array(
'normal' => t('Block title'),
'above' => t('Above'),
);
$form['label'] = array(
'#type' => 'select',
'#title' => t('Multigroup label'),
'#default_value' => !empty($conf['label']) && isset($label_options[$conf['label']]) ? $conf['label'] : 'hidden',
'#options' => $label_options,
'#description' => t('Configure how the field group label is going to be displayed. This option takes no effect when "Override title" option is enabled, the specified block title is displayed instead.'),
);
$format_options = array(
'simple' => t('Simple'),
'fieldset' => t('Fieldset'),
'fieldset_collapsible' => t('Fieldset - Collapsible'),
'fieldset_collapsed' => t('Fieldset - Collapsed'),
);
$form['format'] = array(
'#type' => 'select',
'#title' => t('Multigroup format'),
'#default_value' => !empty($conf['format']) && isset($format_options[$conf['format']]) ? $conf['format'] : 'simple',
'#options' => $format_options,
'#description' => t('This option allows you to configure the field group format.'),
);
$subgroup_options = array(
'simple' => t('Simple'),
'fieldset' => t('Fieldset'),
'fieldset_collapsible' => t('Fieldset - collapsible'),
'fieldset_collapsed' => t('Fieldset - collapsed'),
'hr' => t('Horizontal line'),
'table-single' => t('Table - Single column'),
'table-multiple' => t('Table - Multiple columns'),
);
$form['subgroup'] = array(
'#type' => 'select',
'#title' => t('Subgroup format'),
'#default_value' => !empty($conf['subgroup']) && isset($subgroup_options[$conf['subgroup']]) ? $conf['subgroup'] : 'fieldset',
'#options' => $subgroup_options,
'#description' => t('This option allows you to configure the format of the subgroups in the multigroup.'),
);
$form['empty'] = array(
'#type' => 'textarea',
'#title' => t('Empty text'),
'#description' => t('Text to display if group has no data. Note that title will not display unless overridden.'),
'#rows' => 5,
'#default_value' => $conf['empty'],
);
}
function content_multigroup_content_multigroup_content_type_edit_form_submit(&$form, &$form_state) {
// Copy everything from our defaults.
foreach (array_keys($form_state['plugin']['defaults']) as $key) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
/**
* Admin title for multigroup content type.
*/
function content_multigroup_content_multigroup_content_type_admin_title($subtype, $conf, $context) {
// Extract the node type and fieldgroup name from the subtype.
list($node_type, $group_name) = explode(':', $subtype, 2);
// Get information about this field group for this node type.
$groups = fieldgroup_groups($node_type);
$group = $groups[$group_name];
return t('"@s" multigroup: @group in @type', array(
'@s' => $context->identifier,
'@group' => t($group['label']),
'@type' => node_get_types('name', $node_type),
));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

View file

@ -0,0 +1,94 @@
<?php
/**
* @file
* Views integration for Content Multigroups.
*/
/**
* Implementation of hook_views_data_alter().
*/
function content_multigroup_views_data_alter(&$data) {
// Scan all field groups in the system.
foreach (fieldgroup_groups() as $type_name => $groups) {
$type_label = node_get_types('name', $type_name);
foreach ($groups as $group_name => $group) {
// Let's focus on multigroups that really accept multiple values.
if ($group['group_type'] == 'multigroup' && !empty($group['settings']['multigroup']['multiple'])) {
// Scan all fields in this multigroup.
$field_labels = array();
foreach (array_keys($group['fields']) as $field_name) {
// Load information about the field for this particular content type.
$field = content_fields($field_name, $type_name);
// Get the Views table alias.
$table_alias = content_views_tablename($field);
// Discard this field if not already exposed to Views.
if (isset($data[$table_alias])) {
$field_labels[$field_name] = t($field['widget']['label']);
}
}
if (!empty($field_labels)) {
// Build the name for this filter.
// The scope of field groups is the content type itself. You can
// have more than one group with the same name but different fields
// in different content types. Therefore, we need the type name and
// multigroup name.
$db_field = 'multigroup_'. $type_name .'_'. $group_name;
// Build the labels for the filter.
$label_truncated = truncate_utf8(t($group['label']), 10, TRUE);
$title = t('@group_label multigroup in @type_label', array(
'@group_label' => t($group['label']),
'@type_label' => $type_label,
));
$title_short = t('@label-truncated in @type_label', array(
'@label-truncated' => $label_truncated,
'@type_label' => $type_label,
));
$help_text = t('Synchronize multiple values for fields in @group_label multigroup, @type_label type. Fields in this group: @fields.', array(
'@group_label' => t($group['label']),
'@type_label' => $type_label,
'@fields' => implode(', ', $field_labels),
));
// Attach the new filter to the node table.
$data['node'][$db_field] = array(
'group' => t('Content multigroup'),
'title' => $title,
'title short' => $title_short,
'help' => $help_text,
'filter' => array(
'field' => $db_field,
'table' => 'node',
'handler' => 'content_multigroup_handler_filter',
'content_type_name' => $type_name,
'content_group_name' => $group_name,
'allow empty' => TRUE,
),
);
}
}
}
}
}
/**
* Implementation of hook_views_handlers().
*/
function content_multigroup_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'content_multigroup') . '/views/handlers',
),
'handlers' => array(
'content_multigroup_handler_filter' => array(
'parent' => 'views_handler_filter',
),
),
);
}

View file

@ -0,0 +1,99 @@
<?php
/**
* @file
* Defines a content_multigroup filter object in views.
*
* This filter allows you to associate the deltas in a multigroup so that
* views doesn't do a cross-join across all values in multivalue fields.
*/
class content_multigroup_handler_filter extends views_handler_filter {
var $content_multigroup_fields;
function can_expose() {
return FALSE;
}
function admin_summary() {
return t('+ delta');
}
/**
* Get information about the multigroup.
*/
function _get_multigroup() {
$groups = fieldgroup_groups($this->definition['content_type_name']);
return $groups[$this->definition['content_group_name']];
}
/**
* Get information about the fields in this multigroup.
*/
function _get_multigroup_fields() {
if (!isset($this->content_multigroup_fields)) {
$group = $this->_get_multigroup();
$this->content_multigroup_fields = array();
foreach (array_keys($group['fields']) as $field_name) {
$field = content_fields($field_name, $this->definition['content_type_name']);
$table_alias = content_views_tablename($field);
$this->content_multigroup_fields[$table_alias] = $field;
}
}
return $this->content_multigroup_fields;
}
/**
* Define default value for master field.
*/
function options(&$options) {
$multigroup_fields = $this->_get_multigroup_fields();
// Find the first required field.
foreach ($multigroup_fields as $table_alias => $field) {
if ($field['required']) {
$options['content_multigroup_master_field'] = $table_alias;
break;
}
}
// Default to first field in the multigroup.
if (empty($options['content_multigroup_master_field'])) {
$options['content_multigroup_master_field'] = current(array_keys($multigroup_fields));
}
}
/**
* Options from to ask the user for a master field.
*/
function options_form(&$form, &$form_state) {
$group = $this->_get_multigroup();
$options = array();
foreach ($this->_get_multigroup_fields() as $table_alias => $field) {
$options[$table_alias] = t($field['widget']['label']);
}
$form['content_multigroup_master_field'] = array(
'#title' => t('Available fields in @group_label multigroup', array('@group_label' => t($group['label']))),
'#type' => 'radios',
'#options' => $options,
'#default_value' => $this->options['content_multigroup_master_field'],
'#description' => t('Select the field in this multigroup that will be used to build the primary join with the content table.'),
);
}
/**
* Add joins to the query to synchronize the fields in this multigroup.
*/
function query() {
// Create the join between the master field table and the node table.
$base_alias = $this->query->ensure_table($this->options['content_multigroup_master_field'], $this->relationship);
// Now we want to join the master field table with all other tables
// related to fields in the same multigroup, but adding the delta
// key to the join condition. This is what allows us to keep delta
// values in sync for all fields in the same multigroup.
foreach ($this->_get_multigroup_fields() as $table_alias => $field) {
if ($table_alias != $this->options['content_multigroup_master_field']) {
$alias = $this->query->ensure_table($table_alias, $this->relationship);
$this->query->table_queue[$alias]['join']->extra = $base_alias .'.delta = '. $alias .'.delta';
}
}
}
}

View file

@ -0,0 +1,11 @@
name = Content Permissions
description = Set field-level permissions for CCK fields.
package = CCK
core = 6.x
dependencies[] = content
; Information added by Drupal.org packaging script on 2015-06-25
version = "6.x-3.0-alpha4"
core = "6.x"
project = "cck"
datestamp = "1435195385"

View file

@ -0,0 +1,9 @@
<?php
/**
* @file
* Implementation of hook_install().
*/
function content_permissions_install() {
drupal_set_message(t('Please <a href="!url">configure your field permissions</a> immediately. All fields are inaccessible by default.', array('!url' => url('admin/user/permissions', array('fragment' => 'content_permissions')))));
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Implementation of hook_perm().
*/
function content_permissions_perm() {
$perms = array();
foreach (content_fields() as $field) {
$perms[] = 'edit '. $field['field_name'];
$perms[] = 'view '. $field['field_name'];
}
return $perms;
}
/**
* Implementation of hook_field_access().
*
* @see content_access().
*/
function content_permissions_field_access($op, $field, $account, $node = NULL) {
switch ($op) {
case 'view':
case 'edit':
return user_access($op .' '. $field['field_name'], $account);
}
return TRUE;
}

View file

@ -0,0 +1,5 @@
div.fieldgroup .content {
padding-left:0;
padding-right:1em;
}

View file

@ -0,0 +1,32 @@
<?php
/**
* @file fieldgroup-simple.tpl.php
* Default theme implementation to display the a 'simple-styled' fieldgroup.
*
* Available variables:
* - $group_name - The group name
* - $group_name_css - The css-compatible group name.
* - $label - The group label
* - $description - The group description
* - $content - The group content
*
* @see template_preprocess_fieldgroup_simple()
*/
?>
<?php if ($content) : ?>
<div class="fieldgroup <?php print $group_name_css; ?>">
<?php if ($label): ?>
<h2><?php print $label; ?></h2>
<?php if ($description): ?>
<div class="description"><?php print $description; ?></div>
<?php endif; ?>
<?php endif; ?>
<div class="content"><?php print $content; ?></div>
</div>
<?php endif; ?>

View file

@ -0,0 +1,7 @@
div.fieldgroup {
margin:.5em 0 1em 0;
}
div.fieldgroup .content {
padding-left:1em;/*LTR*/
}

View file

@ -0,0 +1,11 @@
name = Fieldgroup
description = Create display groups for CCK fields.
dependencies[] = content
package = CCK
core = 6.x
; Information added by Drupal.org packaging script on 2015-06-25
version = "6.x-3.0-alpha4"
core = "6.x"
project = "cck"
datestamp = "1435195385"

View file

@ -0,0 +1,328 @@
<?php
/**
* @file
* Implementation of hook_install().
*/
function fieldgroup_install() {
drupal_load('module', 'content');
db_query("UPDATE {system} SET weight = 9 WHERE name = 'fieldgroup'");
drupal_install_schema('fieldgroup');
content_notify('install', 'fieldgroup');
variable_set('fieldgroup_schema_version', 6000);
}
/**
* Implementation of hook_uninstall().
*/
function fieldgroup_uninstall() {
drupal_load('module', 'content');
drupal_uninstall_schema('fieldgroup');
content_notify('uninstall', 'fieldgroup');
}
/**
* Implementation of hook_enable().
*
* Notify content module when this module is enabled.
*/
function fieldgroup_enable() {
drupal_load('module', 'content');
content_notify('enable', 'fieldgroup');
}
/**
* Implementation of hook_disable().
*
* Notify content module when this module is disabled.
*/
function fieldgroup_disable() {
drupal_load('module', 'content');
content_notify('disable', 'fieldgroup');
}
/**
* Implementation of hook_schema.
*/
function fieldgroup_schema() {
$schema['content_group'] = array(
'fields' => array(
'group_type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => 'standard'),
'type_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'parent' => array('type' => 'varchar', 'length' => 32, 'not null' => FALSE, 'default' => ''),
'label' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
'settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE),
'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
),
'primary key' => array('type_name', 'group_name'),
);
$schema['content_group_fields'] = array(
'fields' => array(
'type_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
),
'primary key' => array('type_name', 'group_name', 'field_name'),
);
return $schema;
}
/**
* rename groups form "group-*" to "group_*"
*/
function fieldgroup_update_1() {
$ret = array();
if (!db_table_exists('node_group')) {
return $ret;
}
switch ($GLOBALS['db_type']) {
case 'pgsql':
$ret[] = update_sql("UPDATE {node_group} SET group_name = 'group_'||SUBSTRING(group_name FROM 7)");
$ret[] = update_sql("UPDATE {node_group_fields} SET group_name = 'group_'||SUBSTRING(group_name FROM 7)");
break;
case 'mysql':
case 'mysqli':
$ret[] = update_sql("UPDATE {node_group} SET group_name = CONCAT('group_', SUBSTRING(group_name FROM 7))");
$ret[] = update_sql("UPDATE {node_group_fields} SET group_name = CONCAT('group_', SUBSTRING(group_name FROM 7))");
break;
}
return $ret;
}
/**
* add display settings for the group
*/
function fieldgroup_update_2() {
$ret = array();
if (!db_table_exists('node_group')) {
return $ret;
}
// set settings column to accept larger values
switch ($GLOBALS['db_type']) {
case 'mysql':
case 'mysqli':
$ret[] = update_sql('ALTER TABLE {node_group} CHANGE settings settings mediumtext NOT NULL');
break;
case 'pgsql':
db_change_column($ret, 'node_group', 'settings', 'settings', 'text', array('not null' => TRUE));
break;
}
// move description into the settings array, and add new settings
$result = db_query("SELECT * FROM {node_group}");
while ($group = db_fetch_array($result)) {
$settings = array();
$settings['form'] = unserialize($group['settings']);
$settings['form']['description'] = $group['description'];
$settings['display'] = array('collapsible' => 0, 'collapsed' => 0, 'description' => '');
$ret[] = update_sql("UPDATE {node_group} SET settings = '". db_escape_string(serialize($settings)) ."', description = '' WHERE group_name = '". $group['group_name'] ."'");
}
// drop description column
switch ($GLOBALS['db_type']) {
case 'mysql':
case 'mysqli':
$ret[] = update_sql('ALTER TABLE {node_group} DROP description');
break;
case 'pgsql':
// Postgres only supports dropping of columns since 7.4
break;
}
return $ret;
}
/**
* converts group settings collapsible/collapsed => style
*/
function fieldgroup_update_3() {
$ret = array();
if (!db_table_exists('node_group')) {
return $ret;
}
$result = db_query("SELECT * FROM {node_group}");
while ($group = db_fetch_array($result)) {
$group['settings'] = unserialize($group['settings']);
if (!isset($group['settings']['form']['style'])) {
foreach (array('form', 'display') as $context) {
if (isset($group['settings'][$context]['collapsible']) && $group['settings'][$context]['collapsible']) {
if (isset($group['settings'][$context]['collapsed']) && $group['settings'][$context]['collapsed']) {
$group['settings'][$context]['style'] = 'fieldset_collapsed';
}
else {
$group['settings'][$context]['style'] = 'fieldset_collapsible';
}
}
else {
$group['settings'][$context]['style'] = 'fieldset';
}
}
$ret[] = update_sql("UPDATE {node_group} SET settings = '". db_escape_string(serialize($group['settings'])) ."' WHERE group_name = '". $group['group_name'] ."'");
}
}
return $ret;
}
/*
* Increases module weight, so that other modules can form_alter() cck forms before the fields
* are moved in groups
*/
function fieldgroup_update_4() {
$ret = array();
$ret[] = update_sql("UPDATE {system} SET weight = 9 WHERE name = 'fieldgroup'");
return $ret;
}
/**
* Start D6 upgrades
*/
/**
* Move fieldgroup tables to the content_* namespace.
*/
function fieldgroup_update_6000() {
if ($abort = content_check_update('fieldgroup')) {
return $abort;
}
$ret = array();
db_rename_table($ret, 'node_group', 'content_group');
db_rename_table($ret, 'node_group_fields', 'content_group_fields');
variable_set('fieldgroup_schema_version', 6000);
return $ret;
}
/*
* Increases module weight, so that other modules can form_alter() cck forms before the fields
* are moved in groups.
*
* Sites upgraded from D5 should have this already set.
* New D6 installs earlier than RC5 need this, as it was missing in fieldgroup_install.
*/
function fieldgroup_update_6001() {
if ($abort = content_check_update('fieldgroup')) {
return $abort;
}
$ret = array();
$ret[] = update_sql("UPDATE {system} SET weight = 9 WHERE name = 'fieldgroup'");
return $ret;
}
/**
* Same as 6000 : Move fieldgroup tables to the content_* namespace.
* This was missing in D6 releases earlier than RC5. Ensure we don't run this twice.
*/
function fieldgroup_update_6002() {
if ($abort = content_check_update('fieldgroup')) {
return $abort;
}
$ret = array();
if (db_table_exists('node_group')) {
db_rename_table($ret, 'node_group', 'content_group');
db_rename_table($ret, 'node_group_fields', 'content_group_fields');
variable_set('fieldgroup_schema_version', 6000);
}
return $ret;
}
/**
* Remove tinyint (127) limitation on group weights.
*/
function fieldgroup_update_6003() {
if ($abort = content_check_update('fieldgroup')) {
return $abort;
}
$ret = array();
db_change_field($ret, 'content_group', 'weight', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
return $ret;
}
/**
* Add 'type' property for fieldgroups.
*/
function fieldgroup_update_6004() {
if ($abort = content_check_update('fieldgroup')) {
return $abort;
}
$ret = array();
db_add_field($ret, 'content_group', 'group_type', array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => 'standard'));
$ret[] = update_sql("DELETE FROM {cache_content} WHERE cid='fieldgroup_data'");
return $ret;
}
/**
* Add the 'exclude from $content' display setting to all existing groups.
*/
function fieldgroup_update_6005() {
$ret = array();
$result = db_query("SELECT * FROM {content_group}");
while ($type = db_fetch_array($result)) {
$new_settings = array();
$settings = unserialize($type['settings']);
$new_settings = $settings;
$display_settings = !empty($settings['display']) ? $settings['display'] : array();
if (!empty($display_settings)) {
foreach ($display_settings as $key => $val) {
$new_settings['display'][$key] = $val;
if ($key !== 'label' && is_array($val)) {
$new_settings['display'][$key]['exclude'] = 0;
}
}
}
else {
$new_settings['display'] = array(
'label' => array('format' => 'above'),
'full' => array('format' => 'default', 'exclude' => 0),
'teaser' => array('format' => 'default', 'exclude' => 0),
);
}
db_query("UPDATE {content_group} SET settings='%s' WHERE group_name='%s' AND type_name='%s'", serialize($new_settings), $type['group_name'], $type['type_name']);
}
return $ret;
}
/**
* Removed a previous version of "Remove orphaned fields" (6007), broken for db prefixes.
*/
function fieldgroup_update_6006() {
return array();
}
/**
* Remove orphaned fields (see http://drupal.org/node/339537).
*/
function fieldgroup_update_6007() {
$ret = array();
$ret[] = update_sql("DELETE FROM {content_group_fields} WHERE (field_name, type_name) NOT IN (SELECT field_name, type_name FROM {content_node_field_instance})");
return $ret;
}
/**
* allow for nesting of fieldgroups
*/
function fieldgroup_update_6008() {
if ($abort = content_check_update('fieldgroup')) {
return $abort;
}
$ret = array();
db_add_field($ret, 'content_group', 'parent', array('type' => 'varchar', 'length' => 32, 'not null' => FALSE, 'default' => ''));
return $ret;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,163 @@
<?php
/**
* @file
* This file provides a CTools content type for fieldgroups.
*/
/**
* Callback function to supply a list of content types.
*/
function fieldgroup_content_fieldgroup_ctools_content_types() {
return array(
'title' => t('Content fieldgroup'),
'defaults' => array('label' => 'hidden', 'format' => 'simple', 'empty' => ''),
);
}
/**
* Return all fieldgroup content types available.
*/
function fieldgroup_content_fieldgroup_content_type_content_types() {
// This will hold all the individual fieldgroup content types.
$types = array();
// The outer loop goes through each node type with groups.
foreach (fieldgroup_groups() as $node_type_groups) {
// The inner loop gives us each fieldgroup on each node type with groups.
foreach ($node_type_groups as $group) {
// Skip field groups that are not of standard type.
if ($group['group_type'] != 'standard') {
continue;
}
// Name the content type a combination of fieldgroup and node type names.
$content_type_name = $group['type_name'] . ':' . $group['group_name'];
// Assemble the information about the content type.
$info = array(
'category' => t('Node'),
'icon' => 'icon_cck_field_group.png',
'title' => t('Field group: @group in @type', array(
'@group' => t($group['label']),
'@type' => node_get_types('name', $group['type_name']),
)),
'description' => t('All fields from this field group on the referenced node.'),
'required context' => new ctools_context_required(t('Node'), 'node', array('type' => array($group['type_name']))),
);
$types[$content_type_name] = $info;
}
}
return $types;
}
/**
* Output function for the 'fieldgroup' content type.
*/
function fieldgroup_content_fieldgroup_content_type_render($subtype, $conf, $panel_args, $context) {
if (!isset($context->data)) {
return;
}
$node = drupal_clone($context->data);
// Make sure old data doesn't cause problems:
if (empty($conf['label'])) {
$conf['label'] = 'hidden';
}
if (empty($conf['format'])) {
$conf['format'] = 'simple';
}
// Extract the node type and fieldgroup name from the subtype.
list($node_type, $group_name) = explode(':', $subtype, 2);
// Get a list of all fieldgroups for this node type.
$groups = fieldgroup_groups($node_type);
if (!isset($groups[$group_name])) {
return;
}
$group = $groups[$group_name];
// Render the field group.
$node->build_mode = NODE_BUILD_NORMAL;
$group['settings']['display']['label'] = $conf['label'] == 'normal' || !empty($conf['override_title']) ? 'hidden' : $conf['label'];
$group['settings']['display']['full']['format'] = $conf['format'];
$group['settings']['display']['full']['exclude'] = 0;
$output = fieldgroup_view_group($group, $node);
$block = new stdClass();
if ($conf['label'] == 'normal') {
$block->title = t($group['label']);
}
$block->content = !empty($output) ? $output : $conf['empty'];
return $block;
}
/**
* Returns a settings form for the custom type.
*/
function fieldgroup_content_fieldgroup_content_type_edit_form(&$form, &$form_state) {
$conf = $form_state['conf'];
$label_options = array(
'normal' => t('Block title'),
'above' => t('Above'),
);
$form['label'] = array(
'#type' => 'select',
'#title' => t('Field group label'),
'#default_value' => !empty($conf['label']) && isset($label_options[$conf['label']]) ? $conf['label'] : 'hidden',
'#options' => $label_options,
'#description' => t('Configure how the field group label is going to be displayed. This option takes no effect when "Override title" option is enabled, the specified block title is displayed instead.'),
);
$format_options = array(
'simple' => t('Simple'),
'fieldset' => t('Fieldset'),
'fieldset_collapsible' => t('Fieldset - Collapsible'),
'fieldset_collapsed' => t('Fieldset - Collapsed'),
);
$form['format'] = array(
'#type' => 'select',
'#title' => t('Field group format'),
'#default_value' => !empty($conf['format']) && isset($format_options[$conf['format']]) ? $conf['format'] : 'simple',
'#options' => $format_options,
'#description' => t('This option allows you to configure the field group format.'),
);
$form['empty'] = array(
'#type' => 'textarea',
'#title' => t('Empty text'),
'#description' => t('Text to display if group has no data. Note that title will not display unless overridden.'),
'#rows' => 5,
'#default_value' => $conf['empty'],
);
}
function fieldgroup_content_fieldgroup_content_type_edit_form_submit(&$form, &$form_state) {
// Copy everything from our defaults.
foreach (array_keys($form_state['plugin']['defaults']) as $key) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
/**
* Admin title for fieldgroup content type.
*/
function fieldgroup_content_fieldgroup_content_type_admin_title($subtype, $conf, $context) {
// Extract the node type and fieldgroup name from the subtype.
list($node_type, $group_name) = explode(':', $subtype, 2);
// Get information about this field group for this node type.
$groups = fieldgroup_groups($node_type);
$group = $groups[$group_name];
return t('"@s" field group: @group in @type', array(
'@s' => $context->identifier,
'@group' => t($group['label']),
'@type' => node_get_types('name', $node_type),
));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

View file

@ -0,0 +1,7 @@
[advanced help settings]
hide = TRUE
[nodereference]
title = Nodereference field
parent = content%fields

View file

@ -0,0 +1,3 @@
<p>The Nodereference field stores the nid of a related node. The title of the related node is usually displayed as the value of this field.</p>
<p>The Nodereference field can use an autocomplete widget, or, when used with <a href="&topic:optionwidgets/optionwidgets&">Optionwidgets</a>, the available values can be presented to the end user in a drop-down select list, checkboxes, or radios.</p>
<p>A Nodereference field can be used in Views to create a <a href="&topic:views/relationship&">Relationship</a> to another node, to allow you to use any field, argument, or filter from the related node in your view. </p>

View file

@ -0,0 +1,13 @@
name = Node Reference
description = Defines a field type for referencing one node from another.
dependencies[] = content
dependencies[] = text
dependencies[] = optionwidgets
package = CCK
core = 6.x
; Information added by Drupal.org packaging script on 2015-06-25
version = "6.x-3.0-alpha4"
core = "6.x"
project = "cck"
datestamp = "1435195385"

View file

@ -0,0 +1,164 @@
<?php
/**
* @file
* Implementation of hook_install().
*/
function nodereference_install() {
drupal_load('module', 'content');
content_notify('install', 'nodereference');
}
/**
* Implementation of hook_uninstall().
*/
function nodereference_uninstall() {
drupal_load('module', 'content');
content_notify('uninstall', 'nodereference');
}
/**
* Implementation of hook_enable().
*
* Notify content module when this module is enabled.
*/
function nodereference_enable() {
drupal_load('module', 'content');
content_notify('enable', 'nodereference');
}
/**
* Implementation of hook_disable().
*
* Notify content module when this module is disabled.
*/
function nodereference_disable() {
drupal_load('module', 'content');
content_notify('disable', 'nodereference');
}
function nodereference_update_last_removed() {
return 3;
}
/**
* All fields must allow NULL values to indicate empty fields.
*/
function nodereference_update_6000(&$sandbox) {
include_once('./'. drupal_get_path('module', 'content') .'/content.install');
drupal_load('module', 'content');
$ret = array();
if (!isset($sandbox['progress'])) {
if ($abort = content_check_update('nodereference')) {
return $abort;
}
// Get the latest cache values and schema.
content_clear_type_cache(TRUE, TRUE);
$types = content_types_install();
if (empty($types)) {
return $ret;
}
$sandbox['fields'] = array();
foreach ($types as $type_name => $fields) {
foreach ($fields as $field) {
if ($field['type'] == 'nodereference') {
$sandbox['fields'][] = $field;
}
}
}
if (empty($sandbox['fields'])) {
return $ret;
}
$sandbox['progress'] = 0;
$sandbox['visited'] = array();
}
$field = $sandbox['fields'][$sandbox['progress']];
// We only want to process a field once -- if we hit it a second time,
// that means it's its own table and it should have already been updated.
if (!in_array($field['field_name'], $sandbox['visited'])) {
$db_info = content_database_info($field);
$table = $db_info['table'];
$attributes = $db_info['columns']['nid'];
$column = $attributes['column'];
$attributes['not null'] = FALSE;
db_change_field($ret, $table, $column, $column, array('type' => 'int', 'not null' => FALSE));
db_field_set_no_default($ret, $db_info['table'], $column);
$ret[] = update_sql("UPDATE {". $db_info['table'] ."} SET ". $column ." = NULL WHERE ". $column ." = 0");
$sandbox['visited'][] = $field['field_name'];
}
$sandbox['progress']++;
$ret['#finished'] = $sandbox['progress'] / count($sandbox['fields']);
return $ret;
}
/**
* Create an index by node reference column for all fields.
*/
function nodereference_update_6001(&$sandbox) {
include_once('./'. drupal_get_path('module', 'content') .'/content.install');
drupal_load('module', 'content');
$ret = array();
if (!isset($sandbox['progress'])) {
if ($abort = content_check_update('nodereference')) {
return $abort;
}
// Get the latest cache values and schema.
content_clear_type_cache(TRUE, TRUE);
$types = content_types_install();
if (empty($types)) {
return $ret;
}
$sandbox['fields'] = array();
foreach ($types as $type_name => $fields) {
foreach ($fields as $field) {
if ($field['type'] == 'nodereference') {
$sandbox['fields'][] = $field;
}
}
}
if (empty($sandbox['fields'])) {
return $ret;
}
$sandbox['progress'] = 0;
$sandbox['visited'] = array();
}
$field = $sandbox['fields'][$sandbox['progress']];
// We only want to process a field once -- if we hit it a second time,
// that means it's its own table and it should have already been updated.
if (!in_array($field['field_name'], $sandbox['visited'])) {
$db_info = content_database_info($field);
$table = $db_info['table'];
$attributes = $db_info['columns']['nid'];
$column = $attributes['column'];
if (!content_db_index_exists($table, $column)) {
db_add_index($ret, $table, $column, array($column));
}
$sandbox['visited'][] = $field['field_name'];
}
$sandbox['progress']++;
$ret['#finished'] = $sandbox['progress'] / count($sandbox['fields']);
return $ret;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Provides additional rules support for nodereference fields.
*/
/**
* Implementation of hook_rules_action_info().
*/
function nodereference_rules_action_info() {
$info = array();
$info['nodereference_rules_action_load'] = array(
'label' => t('Load a referenced node'),
'arguments' => array(
'node' => array(
'type' => 'node',
'label' => t('Content containing the node reference field'),
),
),
'new variables' => array(
'referenced_node' => array(
'type' => 'node',
'label' => t('Referenced content'),
),
),
'module' => 'CCK',
'help' => t('Note that if the field has multiple values, only the first content node will be loaded.'),
);
return $info;
}
function nodereference_rules_action_load($node, $settings) {
if ($nid = $node->{$settings['field']}[0]['nid']) {
return array('referenced_node' => node_load(array('nid' => $nid)));
}
}
function nodereference_rules_action_load_form($settings, &$form) {
$settings += array('field' => '');
$options = content_rules_get_field_names_by_type('nodereference');
$form['settings']['field'] = array(
'#type' => 'select',
'#title' => t('Field'),
'#default_value' => $settings['field'],
'#options' => $options,
'#required' => TRUE,
'#disabled' => empty($options),
'#description' => empty($options) ? t('There are no nodereference fields defined.') : '',
);
}
/**
* Helps upgrading from the workflow-ng action
* "workflow_ng_action_load_referenced_node" to the equivalent rules action.
*/
function workflow_ng_action_load_referenced_node_upgrade(&$element) {
$element['#name'] = 'nodereference_rules_action_load';
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Provides tokens for nodereference fields.
*/
/**
* Implementation of hook_token_list().
*/
function nodereference_token_list($type = 'all') {
if ($type == 'field' || $type == 'all') {
$tokens = array();
$tokens['node reference']['nid'] = t('Referenced node ID');
$tokens['node reference']['title'] = t('Referenced node title');
$tokens['node reference']['title-raw'] = t('Referenced node unfiltered title. WARNING - raw user input.');
$tokens['node reference']['link'] = t("Formatted html link to the referenced node.");
$tokens['node reference']['path'] = t("Relative path alias to the referenced node.");
$tokens['node reference']['url'] = t("Absolute path alias to the referenced node.");
return $tokens;
}
}
/**
* Implementation of hook_token_values().
*/
function nodereference_token_values($type, $object = NULL, $options = array()) {
if ($type == 'field') {
$item = $object[0];
$title = is_numeric($item['nid']) ? _nodereference_titles($item['nid']) : '';
$tokens['nid'] = $item['nid'];
$tokens['title'] = $title ? check_plain($title) : '';
$tokens['title-raw'] = $title;
$tokens['link'] = isset($item['view']) ? $item['view'] : '';
$tokens['path'] = is_numeric($item['nid']) ? url('node/' . $item['nid']) : '';
$tokens['url'] = is_numeric($item['nid']) ? url('node/' . $item['nid'], array('absolute' => TRUE)) : '';
return $tokens;
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Implements the node reference relationship for Panels.
*/
/**
* Implementation of hook_ctools_relationships().
*/
function nodereference_node_from_noderef_ctools_relationships() {
return array(
'title' => t('Node from reference'),
'keyword' => 'nodereference',
'description' => t('Adds a node from a node reference in a node context; if multiple nodes are referenced, this will get the first referenced node only.'),
'required context' => new ctools_context_required(t('Node'), 'node'),
'context' => 'nodereference_node_from_noderef_context',
'settings form' => 'nodereference_node_from_noderef_settings_form',
'settings form validate' => 'nodereference_node_from_noderef_settings_form_validate',
);
}
/**
* Return a new ctools context based on an existing context.
*/
function nodereference_node_from_noderef_context($context, $conf) {
$field = content_fields($conf['field_name']);
// If unset it wants a generic, unfilled context, which is just NULL.
if (empty($context->data)) {
$new_context = ctools_context_create_empty('node', NULL);
}
else if (isset($context->data->{$conf['field_name']}[0]['nid']) && ($nid = $context->data->{$conf['field_name']}[0]['nid'])) {
if ($node = node_load($nid)) {
$new_context = ctools_context_create('node', $node);
}
}
if (!empty($new_context)) {
// Have nodereference relationships limit CCK field availability as well.
$restrictions = array_keys(array_filter($field['referenceable_types']));
if ($restrictions) {
if (isset($new_context->restrictions['type'])) {
$new_context->restrictions['type'] = array_unique(array_merge($new_context->restrictions['type'], $restrictions));
}
else {
$new_context->restrictions['type'] = $restrictions;
}
}
return $new_context;
}
}
/**
* Settings form for the ctools relationship.
*/
function nodereference_node_from_noderef_settings_form($conf) {
$options = array();
foreach (content_fields() as $field) {
if ($field['type'] == 'nodereference') {
$options[$field['field_name']] = t($field['widget']['label']);
}
}
$form['field_name'] = array(
'#title' => t('Node reference field'),
'#type' => 'select',
'#options' => $options,
'#default_value' => isset($conf['field_name']) ? $conf['field_name'] : '',
'#prefix' => '<div class="clear-block">',
'#suffix' => '</div>',
);
return $form;
}

View file

@ -0,0 +1,7 @@
[advanced help settings]
hide = TRUE
[number]
title = Number field
parent = content%fields

View file

@ -0,0 +1,2 @@
<p>The Number field stores numeric data in the database. It can either be an integer value, a decimal value, or a float value.</p>
<p>The Number field provides a place for the administrator to create a list of 'Allowed values' for the field. When used with <a href="&topic:optionwidgets/optionwidgets&">Optionwidgets</a>, the allowed values are presented to the end user in a drop-down select list, checkboxes, or radios.</p>

View file

@ -0,0 +1,11 @@
name = Number
description = Defines numeric field types.
dependencies[] = content
package = CCK
core = 6.x
; Information added by Drupal.org packaging script on 2015-06-25
version = "6.x-3.0-alpha4"
core = "6.x"
project = "cck"
datestamp = "1435195385"

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Implementation of hook_install().
*/
function number_install() {
drupal_load('module', 'content');
content_notify('install', 'number');
}
/**
* Implementation of hook_uninstall().
*/
function number_uninstall() {
drupal_load('module', 'content');
content_notify('uninstall', 'number');
}
/**
* Implementation of hook_enable().
*
* Notify content module when this module is enabled.
*/
function number_enable() {
drupal_load('module', 'content');
content_notify('enable', 'number');
}
/**
* Implementation of hook_disable().
*
* Notify content module when this module is disabled.
*/
function number_disable() {
drupal_load('module', 'content');
content_notify('disable', 'number');
}
function number_update_last_removed() {
return 5;
}
/**
* Rename old decimal fields, which were really floats, to float
*/
function number_update_6000() {
if ($abort = content_check_update('number')) {
return $abort;
}
$ret = array();
drupal_load('module', 'content');
$ret[] = update_sql("UPDATE {". content_field_tablename() ."} SET type='number_float' WHERE type = 'number_decimal'");
content_clear_type_cache();
return $ret;
}

View file

@ -0,0 +1,589 @@
<?php
/**
* @file
* Defines numeric field types.
*/
// We do not use hook_init(), since that hook is not fired in update.php, and we
// need token generation to be active within hook_update_N() (e.g. for
// node_save() calls.)
require_once dirname(__FILE__) . '/number.token.inc';
/**
* Implementation of hook_theme().
*/
function number_theme() {
return array(
'number' => array('arguments' => array('element' => NULL)),
'number_formatter_default' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_us_0' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_us_1' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_us_2' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_be_0' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_be_1' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_be_2' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_fr_0' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_fr_1' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_fr_2' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'),
'number_formatter_unformatted' => array('arguments' => array('element' => NULL)),
);
}
/**
* Implementation of hook_field_info().
*/
function number_field_info() {
return array(
'number_integer' => array(
'label' => t('Integer'),
'description' => t('Store a number in the database as an integer.'),
// 'content_icon' => 'icon_content_number.png',
),
'number_decimal' => array(
'label' => t('Decimal'),
'description' => t('Store a number in the database in a fixed decimal format.'),
// 'content_icon' => 'icon_content_number.png',
),
'number_float' => array(
'label' => t('Float'),
'description' => t('Store a number in the database in a floating point format.'),
// 'content_icon' => 'icon_content_number.png',
),
);
}
/**
* Implementation of hook_field_settings().
*/
function number_field_settings($op, $field) {
switch ($op) {
case 'form':
$form = array();
$form['min'] = array(
'#type' => 'textfield',
'#title' => t('Minimum'),
'#element_validate' => array('_element_validate_number'),
'#default_value' => is_numeric($field['min']) ? $field['min'] : '',
);
$form['max'] = array(
'#type' => 'textfield',
'#title' => t('Maximum'),
'#element_validate' => array('_element_validate_number'),
'#default_value' => is_numeric($field['max']) ? $field['max'] : '',
);
if ($field['type'] == 'number_decimal') {
$form['precision'] = array(
'#type' => 'select',
'#options' => drupal_map_assoc(range(10, 32)),
'#title' => t('Precision'),
'#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'),
'#default_value' => is_numeric($field['precision']) ? $field['precision'] : 10,
);
$form['scale'] = array(
'#type' => 'select',
'#options' => drupal_map_assoc(range(0, 10)),
'#title' => t('Scale'),
'#description' => t('The number of digits to the right of the decimal.'),
'#default_value' => is_numeric($field['scale']) ? $field['scale'] : 2,
);
$form['decimal'] = array(
'#type' => 'select',
'#options' => array('.' => 'decimal point', ',' => 'comma', ' ' => 'space'),
'#title' => t('Decimal marker'),
'#description' => t('The character users will input to mark the decimal point in forms.'),
'#default_value' => !empty($field['decimal']) ? $field['decimal'] : '.',
);
}
$form['append']['prefix'] = array(
'#type' => 'textfield',
'#title' => t('Prefix'),
'#size' => 60,
'#default_value' => !empty($field['prefix']) ? $field['prefix'] : '',
'#description' => t('Define a string that should be prefixed to the value, like $ or €. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds).'),
);
$form['append']['suffix'] = array(
'#type' => 'textfield',
'#title' => t('Suffix'),
'#size' => 60,
'#default_value' => !empty($field['suffix']) ? $field['suffix'] : '',
'#description' => t('Define a string that should suffixed to the value, like m², m/s², kb/s. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds).'),
);
$form['allowed_values_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('Allowed values'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['allowed_values_fieldset']['allowed_values'] = array(
'#type' => 'textarea',
'#title' => t('Allowed values list'),
'#default_value' => !empty($field['allowed_values']) ? $field['allowed_values'] : '',
'#required' => FALSE,
'#rows' => 10,
'#description' => t('The possible values this field can contain. Enter one value per line, in the format key|label. The key is the value that will be stored in the database, and it must match the field storage type (%type). The label is optional, and the key will be used as the label if no label is specified.<br />Allowed HTML tags: @tags', array('%type' => $field['type'], '@tags' => _content_filter_xss_display_allowed_tags())),
);
$form['allowed_values_fieldset']['advanced_options'] = array(
'#type' => 'fieldset',
'#title' => t('PHP code'),
'#collapsible' => TRUE,
'#collapsed' => empty($field['allowed_values_php']),
);
if (user_access('Use PHP input for field settings (dangerous - grant with care)')) {
$form['allowed_values_fieldset']['advanced_options']['allowed_values_php'] = array(
'#type' => 'textarea',
'#title' => t('Code'),
'#default_value' => !empty($field['allowed_values_php']) ? $field['allowed_values_php'] : '',
'#rows' => 6,
'#description' => t('Advanced usage only: PHP code that returns a keyed array of allowed values. Should not include &lt;?php ?&gt; delimiters. If this field is filled out, the array returned by this code will override the allowed values list above.'),
);
}
else {
$form['allowed_values_fieldset']['advanced_options']['markup_allowed_values_php'] = array(
'#type' => 'item',
'#title' => t('Code'),
'#value' => !empty($field['allowed_values_php']) ? '<code>'. check_plain($field['allowed_values_php']) .'</code>' : t('&lt;none&gt;'),
'#description' => empty($field['allowed_values_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override the allowed values list above.'),
);
}
return $form;
case 'save':
$values = array('prefix', 'suffix', 'min', 'max', 'allowed_values', 'allowed_values_php');
if ($field['type'] == 'number_decimal') {
$values = array_merge($values, array('precision', 'scale', 'decimal'));
}
return $values;
case 'database columns':
if ($field['type'] == 'number_integer') {
return array(
'value' => array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE),
);
}
if ($field['type'] == 'number_float') {
return array(
'value' => array('type' => 'float', 'not null' => FALSE, 'sortable' => TRUE),
);
}
if ($field['type'] == 'number_decimal') {
$precision = isset($field['precision']) ? $field['precision'] : 10;
$scale = isset($field['scale']) ? $field['scale'] : 2;
return array(
'value' => array('type' => 'numeric', 'precision' => $precision, 'scale' => $scale, 'not null' => FALSE, 'sortable' => TRUE),
);
}
case 'views data':
$allowed_values = content_allowed_values($field);
if (count($allowed_values)) {
$data = content_views_field_views_data($field);
$db_info = content_database_info($field);
$table_alias = content_views_tablename($field);
// Filter: Add a 'many to one' filter.
$copy = $data[$table_alias][$field['field_name'] .'_value'];
$copy['title'] = t('@label (!name) - Allowed values', array('@label' => t($field['widget']['label']), '!name' => $field['field_name']));
$copy['filter']['handler'] = 'content_handler_filter_many_to_one';
$copy['filter']['numeric'] = TRUE;
unset($copy['field'], $copy['argument'], $copy['sort']);
$data[$table_alias][$field['field_name'] .'_value_many_to_one'] = $copy;
// Argument: swap the handler to the 'many to one' operator
$data[$table_alias][$field['field_name'] .'_value']['argument']['handler'] = 'content_handler_argument_many_to_one';
$data[$table_alias][$field['field_name'] .'_value']['argument']['numeric'] = TRUE;
return $data;
}
break;
}
}
function _number_widget_settings_min_validate($element, &$form_state) {
$value = $form_state['values']['min'];
if ($value && !is_numeric($value)) {
form_set_error('min', t('"Minimum" must be a number.'));
}
}
function _number_widget_settings_max_validate($element, &$form_state) {
$value = $form_state['values']['max'];
if ($value && !is_numeric($value)) {
form_set_error('max', t('"Maximum" must be a number.'));
}
}
/**
* Implementation of hook_field().
*/
function number_field($op, &$node, $field, &$items, $teaser, $page) {
switch ($op) {
case 'validate':
$allowed_values = content_allowed_values($field);
if (is_array($items)) {
foreach ($items as $delta => $item) {
$error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
if ($item['value'] != '') {
if (is_numeric($field['min']) && $item['value'] < $field['min']) {
form_set_error($error_element, t('%name: the value may be no smaller than %min.', array('%name' => t($field['widget']['label']), '%min' => $field['min'])));
}
if (is_numeric($field['max']) && $item['value'] > $field['max']) {
form_set_error($error_element, t('%name: the value may be no larger than %max.', array('%name' => t($field['widget']['label']), '%max' => $field['max'])));
}
if (count($allowed_values)) {
// We cannot use array_key_exists() because allowed values are
// stored as strings, and we need to compare numeric equality.
$valid = FALSE;
foreach ($allowed_values as $kay => $value) {
if ((float) $item['value'] == (float) $kay) {
$valid = TRUE;
break;
}
}
if (!$valid) {
form_set_error($error_element, t('%name: illegal value.', array('%name' => t($field['widget']['label']))));
}
}
}
}
}
return $items;
}
}
/**
* Implementation of hook_content_is_empty().
*/
function number_content_is_empty($item, $field) {
if (empty($item['value']) && (string)$item['value'] !== '0') {
return TRUE;
}
return FALSE;
}
/**
* Implementation of hook_field_formatter_info().
*/
function number_field_formatter_info() {
return array(
'default' => array('label' => '9999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')),
'us_0' => array('label' => '9,999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')),
'us_1' => array('label' => '9,999.9', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')),
'us_2' => array('label' => '9,999.99', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')),
'be_0' => array('label' => '9.999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')),
'be_1' => array('label' => '9.999,9', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')),
'be_2' => array('label' => '9.999,99', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')),
'fr_0' => array('label' => '9 999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')),
'fr_1' => array('label' => '9 999, 9', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')),
'fr_2' => array('label' => '9 999, 99', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')),
'unformatted' => array('label' => t('unformatted'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')),
);
}
/**
* Proxy theme function for 'unformatted' number field formatter.
*/
function theme_number_formatter_unformatted($element) {
return $element['#item']['value'];
}
/**
* Proxy theme function for number field formatters.
*/
function theme_number_formatter_generic($element) {
$field = content_fields($element['#field_name'], $element['#type_name']);
$value = $element['#item']['value'];
if (($allowed_values = content_allowed_values($field))) {
if (isset($allowed_values[$value]) && $allowed_values[$value] != $value) {
return $allowed_values[$value];
}
}
if (empty($value) && $value !== '0') {
return '';
}
switch ($element['#formatter']) {
case 'us_0':
$output = number_format($value, 0, '.', ',');
break;
case 'us_1':
$output = number_format($value, 1, '.', ',');
break;
case 'us_2':
$output = number_format($value, 2, '.', ',');
break;
case 'be_0':
$output = number_format($value, 0, ',', '.');
break;
case 'be_1':
$output = number_format($value, 1, ',', '.');
break;
case 'be_2':
$output = number_format($value, 2, ',', '.');
break;
case 'fr_0':
$output = number_format($value, 0, ', ', ' ');
break;
case 'fr_1':
$output = number_format($value, 1, ', ', ' ');
break;
case 'fr_2':
$output = number_format($value, 2, ', ', ' ');
break;
default:
$output = $value;
break;
}
$prefixes = isset($field['prefix']) ? array_map('content_filter_xss', explode('|', $field['prefix'])) : array('');
$suffixes = isset($field['suffix']) ? array_map('content_filter_xss', explode('|', $field['suffix'])) : array('');
$prefix = (count($prefixes) > 1) ? format_plural($value, $prefixes[0], $prefixes[1]) : $prefixes[0];
$suffix = (count($suffixes) > 1) ? format_plural($value, $suffixes[0], $suffixes[1]) : $suffixes[0];
return $prefix . $output . $suffix;
}
/**
* Implementation of hook_widget_info().
*
* Here we indicate that the content module will handle
* the default value and multiple values for these widgets.
*
* Callbacks can be omitted if default handing is used.
* They're included here just so this module can be used
* as an example for custom modules that might do things
* differently.
*/
function number_widget_info() {
return array(
'number' => array(
'label' => t('Text field'),
'field types' => array('number_integer', 'number_decimal', 'number_float'),
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_DEFAULT,
),
),
);
}
/**
* Implementation of FAPI hook_elements().
*
* Any FAPI callbacks needed for individual widgets can be declared here,
* and the element will be passed to those callbacks for processing.
*
* Drupal will automatically theme the element using a theme with
* the same name as the hook_elements key.
*
* Includes a regex to check for valid values as an additional parameter
* the validator can use. The regex can be overridden if necessary.
*/
function number_elements() {
return array(
'number' => array(
'#input' => TRUE,
'#columns' => array('value'), '#delta' => 0,
'#process' => array('number_process'),
),
);
}
/**
* Implementation of hook_widget().
*
* Attach a single form element to the form. It will be built out and
* validated in the callback(s) listed in hook_elements. We build it
* out in the callbacks rather than here in hook_widget so it can be
* plugged into any module that can provide it with valid
* $field information.
*
* Content module will set the weight, field name and delta values
* for each form element. This is a change from earlier CCK versions
* where the widget managed its own multiple values.
*
* If there are multiple values for this field, the content module will
* call this function as many times as needed.
*
* @param $form
* the entire form array, $form['#node'] holds node information
* @param $form_state
* the form_state, $form_state['values'][$field['field_name']]
* holds the field's form values.
* @param $field
* the field array
* @param $items
* array of default values for this field
* @param $delta
* the order of this item in the array of subelements (0, 1, 2, etc)
*
* @return
* the form item for a single element for this field
*/
function number_widget(&$form, &$form_state, $field, $items, $delta = 0) {
$element = array(
'#type' => $field['widget']['type'],
'#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
);
return $element;
}
/**
* Process an individual element.
*
* Build the form element. When creating a form using FAPI #process,
* note that $element['#value'] is already set.
*
* The $fields array is in $form['#field_info'][$element['#field_name']].
*/
function number_process($element, $edit, $form_state, $form) {
$field_name = $element['#field_name'];
$field = $form['#field_info'][$field_name];
$field_key = $element['#columns'][0];
$value = isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : '';
$value = isset($field['decimal']) ? str_replace('.', $field['decimal'], $value) : $value;
$element[$field_key] = array(
'#type' => 'textfield',
'#default_value' => $value,
// Need to allow a slightly larger size that the field length to allow
// for some configurations where all characters won't fit in input field.
'#size' => isset($field['precision']) ? $field['precision'] + 2 : 12,
'#maxlength' => isset($field['precision']) ? $field['precision'] : 10,
'#attributes' => array('class' => 'number'),
// The following values were set by the content module and need
// to be passed down to the nested element.
'#title' => $element['#title'],
'#description' => $element['#description'],
'#required' => $element['#required'],
'#field_name' => $element['#field_name'],
'#type_name' => $element['#type_name'],
'#delta' => $element['#delta'],
'#columns' => $element['#columns'],
);
$prefixes = array();
$suffixes = array();
// Make sure we don't wipe out element validation added elsewhere.
if (empty($element['#element_validate'])) {
$element['#element_validate'] = array();
}
if (!empty($field['prefix'])) {
$prefixes = explode('|', $field['prefix']);
$element[$field_key]['#field_prefix'] = content_filter_xss(array_pop($prefixes));
}
if (!empty($field['suffix'])) {
$suffixes = explode('|', $field['suffix']);
$element[$field_key]['#field_suffix'] = content_filter_xss(array_pop($suffixes));
}
switch ($field['type']) {
case 'number_float':
$element['#element_validate'][] = 'number_float_validate';
break;
case 'number_integer':
$element['#element_validate'][] = 'number_integer_validate';
break;
case 'number_decimal':
$element['#element_validate'][] = 'number_decimal_validate';
$element['#decimal'] = isset($field['decimal']) ? $field['decimal'] : '.';
$element['#precision'] = isset($field['precision']) ? $field['precision'] : 10;
$element['#scale'] = isset($field['scale']) ? $field['scale'] : 2;
break;
}
// Used so that hook_field('validate') knows where to flag an error.
$element['_error_element'] = array(
'#type' => 'value',
'#value' => implode('][', array_merge($element['#parents'], array($field_key))),
);
return $element;
}
/**
* FAPI validation of an individual float element.
*/
function number_float_validate($element, &$form_state) {
$field_name = $element['#field_name'];
$type_name = $element['#type_name'];
$field = content_fields($field_name, $type_name);
$field_key = $element['#columns'][0];
$value = $element['#value'][$field_key];
if (($element[$field_key]['#required'] || !empty($value))) {
$start = $value;
$value = preg_replace('@[^-0-9\.]@', '', $value);
if ($start != $value) {
$error_field = implode('][', $element['#parents']) .']['. $field_key;
form_set_error($error_field, t('Only numbers and decimals are allowed in %field.', array('%field' => t($field['widget']['label']))));
}
else {
form_set_value($element[$field_key], $value, $form_state);
}
}
}
/**
* FAPI validation of an individual integer element.
*/
function number_integer_validate($element, &$form_state) {
$field_name = $element['#field_name'];
$type_name = $element['#type_name'];
$field = content_fields($field_name, $type_name);
$field_key = $element['#columns'][0];
$value = $element['#value'][$field_key];
if (($element[$field_key]['#required'] || !empty($value))) {
$start = $value;
$value = preg_replace('@[^-0-9]@', '', $value);
if ($start != $value) {
$error_field = implode('][', $element['#parents']) .']['. $field_key;
form_set_error($error_field, t('Only numbers are allowed in %field.', array('%field' => t($field['widget']['label']))));
}
else {
form_set_value($element[$field_key], $value, $form_state);
}
}
}
/**
* FAPI validation of an individual decimal element.
*/
function number_decimal_validate($element, &$form_state) {
$field_name = $element['#field_name'];
$type_name = $element['#type_name'];
$field = content_fields($field_name, $type_name);
$field_key = $element['#columns'][0];
$value = $element['#value'][$field_key];
if (($element[$field_key]['#required'] || !empty($value))) {
$decimal = $element['#decimal'] ? $element['#decimal'] : '.';
$start = $value;
$value = preg_replace('@[^-0-9\\'. $decimal .']@', '', $value);
if ($start != $value) {
$error_field = implode('][', $element['#parents']) .']['. $field_key;
form_set_error($error_field, t('Only numbers and the decimal character (%decimal) are allowed in %field.', array('%decimal' => $element['#decimal'], '%field' => t($field['widget']['label']))));
}
else {
$value = str_replace($element['#decimal'], '.', $value);
$value = round($value, $element['#scale']);
form_set_value($element[$field_key], $value, $form_state);
}
}
}
/**
* FAPI theme for an individual number element.
*
* The textfield is already rendered by the textfield
* theme and the HTML output lives in $element['#children'].
* Override this theme to make custom changes to the output.
*
* $element['#field_name'] contains the field name
* $element['#delta] is the position of this element in the group
*/
function theme_number($element) {
return $element['#children'];
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Provides tokens for number fields.
*/
/**
* Implementation of hook_token_list().
*/
function number_token_list($type = 'all') {
if ($type == 'field' || $type == 'all') {
$tokens = array();
$tokens['number']['raw'] = t('Raw number value');
$tokens['number']['formatted'] = t('Formatted number value');
return $tokens;
}
}
/**
* Implementation of hook_token_values().
*/
function number_token_values($type, $object = NULL, $options = array()) {
if ($type == 'field') {
$item = $object[0];
$tokens['raw'] = $item['value'];
$tokens['formatted'] = isset($item['view']) ? $item['view'] : '';
return $tokens;
}
}

Some files were not shown because too many files have changed in this diff Show more