first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
CHANGELOG for Diff 7.x-2.0+13-dev to 7.x-3.x
============================================
1) System variable names have been changed
------------------------------------------
Considerable changes have occurred.
2) hook_diff() was removed
--------------------------
This has been replaced by hook_entity_diff() as of Diff 7.x-3.x.
3) Field diffs are handled independently by Diff and the field module
---------------------------------------------------------------------
Field modules SHOULD NOT implement hook_entity_diff().
This is complicated and costly in terms of performance.
Two new field callbacks are defined to handle these.
a) MODULE_field_diff_view_prepare()
Optional: If you need to load data, use MODULE_field_diff_view_prepare().
b) MODULE_field_diff_view()
Recommended: You should implement this to generate the compared data.
If there is no corresponding hook for a field, the field comparison will try
to guess the value using $item['safe_value'] or $item['value'] properties.
If you need to make this configurable, there are two additional hooks:
c) MODULE_field_diff_default_options($field_type)
You should define any additioal settings here. This shares a global namespace
of the diff module, so you can overwrite core Diff settings here too.
In saying that, take care not to accidentially do this.
d) MODULE_field_diff_options_form($field_type, $settings)
This is where you insert Form API elements to configure your option settings.
4) Field diffs are now configurable
-----------------------------------
Each field type defined by core have configurable settings to control the
rendering of the comparison.
a) Global configuration
An administration page has been added to handle field type default settings.
This is the preferred way to configure field settings are these are global to
all fields of this type.
b) View mode display options
The display "Diff comparison" is used to control the fields that are displayed
when comparing different revisions.
The following is a walk-through on how you would configure the Basic page
(page) content types field configuration.
- Enable "Diff comparison" custom view mode
Navigate to admin/structure/types/manage/page/display and look at the
Custom Display Settings for this view mode. Check and save.
- Configure the display
After Saving this page, a new tab appears "Diff comparison", click this or
navigate directly to admin/structure/types/manage/page/display/diff_standard
- You can hide or show the fields that you want to display when doing
comparisons.
- If the field has no inbuilt diff support, then the renderred field items
will be compared.
5) Standard comparison preview / Inline diff view setting
---------------------------------------------------------
You can set the view modes used to compare the rendered node. This can be found
in the Diff settings in the Content Type settings page.
6) Optional CSS and new Boxes styles
------------------------------------
This takes the styles from WikiPedia to really spice up the diff page.
7) Optional JScript extras
--------------------------
This spices up the revision checkboxes on the revisions page.
8) Simple past revision token support
-------------------------------------
Use-case, email notifications when content has changes. If these support tokens,
then you can embed Diffs into these emails.
9) Extensive string review
--------------------------
See http://drupal.org/node/1785742
10) Inline block settings changes
---------------------------------
The inline block settings are now in the block configuration page.
11) And much more...
--------------------
The complete change log follows:
Diff 7.x-2.x
o #888680 by Deciphered, Alan D.: Allow modules to interact via drupal_alter()
o #1280892 by Alan D., crea: Diff should track the variables that it defines
o #1304658 by Alan D., kari.kaariainen: Remove links and comments from the comparison preview
o #1122206 by binford2k, Alan D.: Notices thrown by DiffEngine::process_chunk()
o #1175064 by zilverdistel, Alan D.: Provide variables for leading and trailing context
o #1673864 by Alan D.: Allow users to bypass the admin theme when viewing comparisons
o #1673876 by Alan D.: Use Drupal autoloading for classes
o #1673856 by Alan D.: Use hook_form_BASE_FORM_ID_alter() rather than hook_form_alter()
o #1673856 by Alan D.: Normalise line endings
o #114308 by Alan D.: add jQuery for hiding radios that shouldn't show diffs
o #1688840 by Alan D.: Enable new JScript behaviour by default
o #372957 by erykmynn, JuliaKM, lsrzj, andrew_rs, alexpott, et al: HTML Strip for Diff, WYSIWYG Friendly
(This was refactored in the 7.x-3.x branch from the commited 7.x-2.x code)
o #521212 by Alan D., blakehall: Make diff comparison page themable
o #1671484 by Alan D.: Show number of lines changed on revisions page
o #114699 by smokris, Alan D.: Diff module should support Token
o #372957 by c31ck: display either Hide or Show based on what clicking it will do at any time (HTML Strip for Diff)
This was altered for the 7.x-3.x branch.
o #1807510 & #1825202: Simplify Diff administration
o #1812162 by mitchell, Alan D.: 'Highlight changes' block appears on edit form
Node to Entity changes
----------------------
These are roughly tracked in the meta issue #1365750 Generalize API and Integrate with core field types
o (no issue) by Alan D.: Use entity specific system variables.
o (no issue) by Alan D.: View mode code, new hooks, new API. Massive patch!
Resolves:
o #248778: Taxonomy diff
o #1550698: Diff of "select from list" fields shows change in key, not change in value
o #1458814: File (and image) field support
o #1418760: Optional setting to honour the display settings
o #1347316: Selectable view mode for inline diffs and "Current revision" display view mode
o #1458906: Improve performances (of existing 7.x-2.x field rendering)
o #1424162: Diff in Taxonomy term description
o #1211282: Image diff support
The following patches will be posted in the corresponding project queues once
the 7.x-3.x branch is released:
o #1595702 by Alan D., mbilbille: Support of field collection module
o #1350604 by Alan D., johaziel: Datetime diff
o (no issue) by Alan D.: Email field Diff support
o (no issue) by Alan D.: Countries Diff support
o (no issue) by Alan D.: Name field Diff support
o (no issue) by Alan D.: Link field Diff support

File diff suppressed because it is too large Load Diff

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,124 @@
html.js .diff-js-hidden { display: none; }
/* Reset as many core themes as possible */
table.diff {
font-size: 0.923em;
margin: 0 0 10px;
border: 0 none;
width: 98%;
border-spacing: 5px;
table-layout: fixed ;
border-collapse: separate;
}
table.diff tr td:last-child {
border-right: inherit;
}
table.diff td,
table.diff th {
vertical-align: middle;
border: 0 none;
color: #000;
text-transform: none;
background: none;
border-spacing: 4px;
padding: 4px 8px;
}
table.diff tr, table.diff tr.even {
background: none;
}
table.diff tr th, table.diff tr th a, table.diff tr th a:hover {
color: inherit;
font-weight: bold;
}
table.diff tr.even,
table.diff tr.odd {
border-width: 0;
border-style: none;
background: transparent;
}
table.diff th a { display: inline; }
/* Main theming */
table.diff, td.diff-number {
background-color: white
}
table.diff td.diff-lineno {
font-weight: bold
}
table.diff td.diff-addedline, table.diff td.diff-deletedline, table.diff td.diff-context {
font-size: 88%;
vertical-align: top;
white-space: -moz-pre-wrap;
white-space: pre-wrap
}
table.diff td.diff-addedline, table.diff td.diff-deletedline {
border-style: solid;
border-width: 1px 1px 1px 4px;
border-radius: 0.33em
}
table.diff td.diff-context {
background: #f3f3f3;
color: #333333;
border-style: solid;
border-width: 1px 1px 1px 4px;
border-color: #e6e6e6;
border-radius: 0.33em;
}
table.diff td.diff-addedline {
border-color: #a3d3ff;
background: #ffffff;
border: 1px 1px 1px 3px;
}
table.diff td.diff-deletedline {
border-color: #ffe49c
}
.diffchange {
font-weight: bold;
text-decoration: none
}
table.diff td.diff-addedline .diffchange, table.diff td.diff-deletedline .diffchange {
border-radius: 0.33em;
padding: 0.25em 0
}
table.diff td.diff-addedline .diffchange {
background: #d8ecff
}
table.diff td.diff-deletedline .diffchange {
background: #feeec8
}
table.diff table.diff td {
padding: 0.33em 0.66em
}
table.diff td.diff-marker {
width: 2%;
text-align: right;
font-weight: bold;
font-size: 1.25em
}
table.diff col.diff-content {
width: 48%
}
table.diff table.diff td div {
word-wrap: break-word;
overflow: auto
}
td.diff-prevlink {
text-align: left;
}
td.diff-nextlink {
text-align: right;
}

View File

@@ -0,0 +1,86 @@
html.js .diff-js-hidden { display: none; }
/**
* Inline diff metadata
*/
.diff-inline-metadata {
padding:4px;
border:1px solid #ddd;
background:#fff;
margin:0px 0px 10px;
}
.diff-inline-legend { font-size:11px; }
.diff-inline-legend span,
.diff-inline-legend label { margin-right:5px; }
/**
* Inline diff markup
*/
span.diff-deleted { color:#ccc; }
span.diff-deleted img { border: solid 2px #ccc; }
span.diff-changed { background:#ffb; }
span.diff-changed img { border:solid 2px #ffb; }
span.diff-added { background:#cfc; }
span.diff-added img { border: solid 2px #cfc; }
/**
* Traditional split diff theming
*/
table.diff {
border-spacing: 4px;
margin-bottom: 20px;
table-layout: fixed;
width: 100%;
}
table.diff tr.even, table.diff tr.odd {
background-color: inherit;
border: none;
}
td.diff-prevlink {
text-align: left;
}
td.diff-nextlink {
text-align: right;
}
td.diff-section-title, div.diff-section-title {
background-color: #f0f0ff;
font-size: 0.83em;
font-weight: bold;
padding: 0.1em 1em;
}
td.diff-context {
background-color: #fafafa;
}
td.diff-deletedline {
background-color: #ffa;
width: 50%;
}
td.diff-addedline {
background-color: #afa;
width: 50%;
}
span.diffchange {
color: #f00;
font-weight: bold;
}
table.diff col.diff-marker {
width: 1.4em;
}
table.diff col.diff-content {
width: 50%;
}
table.diff th {
padding-right: inherit;
}
table.diff td div {
overflow: auto;
padding: 0.1ex 0.5em;
word-wrap: break-word;
}
table.diff td {
padding: 0.1ex 0.4em;
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* @file
* Administration page callbacks and forms.
*/
/**
* General configuration form for controlling the diff behaviour.
*/
function diff_admin_settings($form, $form_state) {
$form['diff_theme'] = array(
'#type' => 'select',
'#title' => t('CSS options'),
'#default_value' => variable_get('diff_theme', 'default'),
'#options' => array(
'default' => t('Classic'),
'boxes' => t('Boxes'),
),
'#empty_option' => t('- None -'),
'#description' => t('Alter the CSS used when displaying diff results.'),
);
$form['diff_default_state_node'] = array(
'#type' => 'select',
'#title' => t('Diff default state'),
'#default_value' => variable_get('diff_default_state_node', 'raw'),
'#options' => array(
'raw' => t('HTML view'),
'raw_plain' => t('Plain view'),
),
'#empty_option' => t('- None -'),
'#description' => t('Default display to show when viewing a diff, html tags in diffed result or as plain text.'),
);
$form['diff_radio_behavior'] = array(
'#type' => 'select',
'#title' => t('Diff radio behavior'),
'#default_value' => variable_get('diff_radio_behavior', 'simple'),
'#options' => array(
'simple' => t('Simple exclusion'),
'linear' => t('Linear restrictions'),
),
'#empty_option' => t('- None -'),
'#description' => t('<em>Simple exclusion</em> means that users will not be able to select the same revision, <em>Linear restrictions</em> means that users can only select older or newer revisions of the current selections.'),
);
$options = drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
$form['diff_context_lines_leading'] = array(
'#type' => 'select',
'#title' => t('Leading context lines'),
'#description' => t('This governs the number of unchanged leading context "lines" to preserve.'),
'#default_value' => variable_get('diff_context_lines_leading', 2),
'#options' => $options,
);
$form['diff_context_lines_trailing'] = array(
'#type' => 'select',
'#title' => t('Trailing context lines'),
'#description' => t('This governs the number of unchanged trailing context "lines" to preserve.'),
'#default_value' => variable_get('diff_context_lines_trailing', 2),
'#options' => $options,
);
return system_settings_form($form);
}
/**
* Global entity settings.
*/
function diff_admin_global_entity_settings($form, $form_state, $entity_type) {
$entity_info = entity_get_info($entity_type);
drupal_set_title(t('Diff settings for %entity_label entities', array('%entity_label' => $entity_info['label'])), PASS_THROUGH);
$form['diff_show_header_' . $entity_type] = array(
'#type' => 'checkbox',
'#title' => t('Show entity label header'),
'#default_value' => variable_get('diff_show_header_' . $entity_type, 1),
);
$form['diff_admin_path_' . $entity_type] = array(
'#type' => 'checkbox',
'#title' => t('Treat diff pages as administrative'),
'#description' => t('Diff pages are treated as administrative pages by default, although it is up to each module to enforce this and to implement this optional setting.'),
'#default_value' => variable_get('diff_admin_path_' . $entity_type, 1),
);
return system_settings_form($form);
}
/**
* Menu callback - provides an overview of Diff support and global settings.
*/
function diff_admin_field_overview() {
$build['info'] = array(
'#markup' => '<p>' . t('This table provides a summary of the field type support found on the system. It is recommended that you use global settings whenever possible to configure field comparison settings.') . '</p>',
);
$header = array(t('Type'), t('Module'), t('Operations'));
$rows = array();
// Skip field types which have no widget types.
$field_types = field_info_field_types();
$widgets = array();
foreach (field_info_widget_types() as $name => $widget_type) {
foreach ($widget_type['field types'] as $widget_field_type) {
if (isset($field_types[$widget_field_type])) {
$widgets[$widget_field_type][$name] = $widget_type['label'];
}
}
}
foreach ($field_types as $field_name => $field_type) {
if (!empty($widgets[$field_name])) {
$row = array();
$row[] = t('@field_label (%field_type)', array(
'@field_label' => $field_type['label'],
'%field_type' => $field_name,
));
$row[] = $field_type['module'];
$row[] = l(t('Global settings'), 'admin/config/content/diff/fields/' . $field_name);
$rows[] = $row;
}
}
$build['category_table'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('The system has no configurable fields.'),
);
return $build;
}
/**
* Menu form callback for the field settings.
*/
function diff_admin_global_field_settings($form, $form_state, $type) {
module_load_include('diff.inc', 'diff');
$field_types = field_info_field_types();
if (!isset($field_types[$type])) {
drupal_set_message(t('Invalid field type.'), 'error');
drupal_goto('admin/config/content/diff/fields');
}
$field_type = $field_types[$type];
// Set the title to give more context to this page.
drupal_set_title(t('Global settings for %label fields', array(
'%label' => $field_type['label'],
)), PASS_THROUGH);
$variable_name = "diff_{$field_type['module']}_field_{$type}_default_options";
$settings = variable_get($variable_name, array());
$settings = _diff_field_default_settings($field_type['module'], $type, $settings);
$func = $field_type['module'] . '_field_diff_options_form';
if (function_exists($func) && ($options_form = $func($type, $settings))) {
$form[$variable_name] = $options_form;
}
$form[$variable_name]['#tree'] = TRUE;
diff_global_settings_form($form[$variable_name], $form_state, $type, $settings);
return system_settings_form($form);
}

View File

@@ -0,0 +1,181 @@
<?php
/**
* @file
* Hooks provided by the diff module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Allow modules to provide a comparison about entities.
*
* @param object $old_entity
* The older entity revision.
* @param object $new_entity
* The newer entity revision.
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - view_mode: The view mode to use. Defaults to FALSE.
*
* @return array
* An associative array of values keyed by the entity property.
*
* @todo
* Investiagate options and document these.
*/
function hook_entity_diff($old_entity, $new_entity, $context) {
if ($context['entity_type'] == 'node') {
$type = node_type_get_type($new_entity);
$result['title'] = array(
'#name' => $type->title_label,
'#old' => array($old_entity->title),
'#new' => array($new_entity->title),
'#weight' => -5,
'#settings' => array(
'show_header' => FALSE,
),
);
}
}
/**
* Allow modules to alter a comparison about entities.
*
* @param array $entity_diffs
* An array of entity differences.
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - old_entity: The older entity.
* - new_entity: The newer entity.
* - view_mode: The view mode to use. Defaults to FALSE.
*
* @see hook_entity_diff()
*/
function hook_entity_diff_alter($entity_diffs, $context) {
}
/**
* Callback to the module that defined the field to prepare items comparison.
*
* This allows the module to alter all items prior to rendering the comparative
* values. It is mainly used to bulk load entities to reduce overheads
* associated with loading entities individually.
*
* @param array $old_items
* An array of field items from the older revision.
* @param array $new_items
* An array of field items from the newer revision.
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - bundle: The bundle name.
* - field: The field that the items belong to.
* - instance: The instance that the items belong to.
* - language: The language associated with $items.
* - old_entity: The older entity.
* - new_entity: The newer entity.
*
* @see MODULE_field_diff_view()
*/
function MODULE_field_diff_view_prepare(&$old_items, &$new_items, $context) {
$fids = array();
foreach (array_merge_recursive($old_items, $new_items) as $info) {
$fids[$info['fid']] = $info['fid'];
}
// A single load is much faster than individual loads.
$files = file_load_multiple($fids);
// For ease of processing, store a reference of the entity on the item array.
foreach ($old_items as $delta => $info) {
$old_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
}
foreach ($new_items as $delta => $info) {
$new_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
}
}
/**
* Callback to the module that defined the field to generate items comparisons.
*
* @param array $items
* An array of field items from the entity.
* @param array $context
* An associative array containing:
* - entity: The entity being compared.
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - bundle: The bundle name.
* - field: The field that the items belong to.
* - instance: The instance that the items belong to.
* - language: The language associated with $items.
* - old_entity: The older entity.
* - new_entity: The newer entity.
*
* @see MODULE_field_diff_view_prepare()
*/
function MODULE_field_diff_view($items, $context) {
$diff_items = array();
foreach ($items as $delta => $item) {
if (isset($item['file'])) {
$diff_items[$delta] = $item['file']->filename . ' [fid: ' . $item['fid'] . ']';
}
}
return $diff_items;
}
/**
* Allow other modules to interact with MODULE_field_diff_view_prepare().
*
* @param array $old_items
* An array of field items from the older revision.
* @param array $new_items
* An array of field items from the newer revision.
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - bundle: The bundle name.
* - field: The field that the items belong to.
* - instance: The instance that the items belong to.
* - language: The language associated with $items.
* - old_entity: The older entity.
* - new_entity: The newer entity.
*
* @see MODULE_field_diff_view_prepare()
*/
function hook_field_diff_view_prepare_alter($old_items, $new_items, $context) {
}
/**
* Allow other modules to interact with MODULE_field_diff_view().
*
* @param array $values
* An array of field items from the entity ready for comparison.
* @param array $items
* An array of field items from the entity.
* @param array $context
* An associative array containing:
* - entity: The entity being compared.
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - bundle: The bundle name.
* - field: The field that the items belong to.
* - instance: The instance that the items belong to.
* - language: The language associated with $items.
* - old_entity: The older entity.
* - new_entity: The newer entity.
*
* @see MODULE_field_diff_view()
*/
function hook_field_diff_view_alter($values, $items, $context) {
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,86 @@
html.js .diff-js-hidden { display:none; }
/**
* Inline diff metadata
*/
.diff-inline-metadata {
padding:4px;
border:1px solid #ddd;
background:#fff;
margin:0px 0px 10px;
}
.diff-inline-legend { font-size:11px; }
.diff-inline-legend span,
.diff-inline-legend label { margin-right:5px; }
/**
* Inline diff markup
*/
span.diff-deleted { color:#ccc; }
span.diff-deleted img { border: solid 2px #ccc; }
span.diff-changed { background:#ffb; }
span.diff-changed img { border:solid 2px #ffb; }
span.diff-added { background:#cfc; }
span.diff-added img { border: solid 2px #cfc; }
/**
* Traditional split diff theming
*/
table.diff {
border-spacing: 4px;
margin-bottom: 20px;
table-layout: fixed;
width: 100%;
}
table.diff tr.even, table.diff tr.odd {
background-color: inherit;
border: none;
}
td.diff-prevlink {
text-align: left;
}
td.diff-nextlink {
text-align: right;
}
td.diff-section-title, div.diff-section-title {
background-color: #f0f0ff;
font-size: 0.83em;
font-weight: bold;
padding: 0.1em 1em;
}
td.diff-deletedline {
background-color: #ffa;
width: 50%;
}
td.diff-addedline {
background-color: #afa;
width: 50%;
}
td.diff-context {
background-color: #fafafa;
}
span.diffchange {
color: #f00;
font-weight: bold;
}
table.diff col.diff-marker {
width: 1.4em;
}
table.diff col.diff-content {
width: 50%;
}
table.diff th {
padding-right: inherit;
}
table.diff td div {
overflow: auto;
padding: 0.1ex 0.5em;
word-wrap: break-word;
}
table.diff td {
padding: 0.1ex 0.4em;
}

View File

@@ -0,0 +1,384 @@
<?php
/**
* @file
* Includes the hooks defined by diff_hook_info().
*/
/**
* Implements hook_entity_diff().
*
* Helper function to invoke the depreciated hook_diff() for node entities.
*
* This manually invokes hook_diff() to avoid a function name clash with the
* PHP 5 (>= 5.3.0) date_diff() function or the Dates modules implementation.
*
* @param object $old_entity
* The older node revision.
* @param object $new_entity
* The newer node revision.
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - old_entity: The older entity.
* - new_entity: The newer entity.
* - view_mode: The view mode to use. Defaults to FALSE.
*/
function diff_entity_diff($old_entity, $new_entity, $context) {
$return = array();
$entity_type = $context['entity_type'];
$info = entity_get_info($entity_type);
if (!empty($info['fieldable'])) {
$return = diff_entity_fields_diff($old_entity, $new_entity, $context);
}
return $return;
}
/**
* Internal callback to handle fieldable entities.
*
* Field comparison is handled for core modules, but is expandable to any other
* fields if the module defines MODULE_field_diff_view().
*
* @param object $old_entity
* The older entity entity revision.
* @param object $new_entity
* The newer entity entity revision.
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - old_entity: The older entity.
* - new_entity: The newer entity.
* - view_mode: The view mode to use. Defaults to FALSE.
*
* @return array
* An associative array of values keyed by the field name and delta value.
*/
function diff_entity_fields_diff($old_entity, $new_entity, $context) {
$result = array();
$entity_type = $context['entity_type'];
$view_mode = $context['view_mode'];
$field_context = $context;
$actual_mode = FALSE;
list(,, $bundle_name) = entity_extract_ids($entity_type, $new_entity);
$instances = field_info_instances($entity_type, $bundle_name);
// Some fields piggy back the display settings, so we need to fake these by
// ensuring that the field mode is always set.
if (empty($view_mode)) {
$actual_mode = 'diff_standard';
$field_context['custom_settings'] = FALSE;
}
$view_mode_settings = field_view_mode_settings($entity_type, $bundle_name);
$actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings'])) ? $view_mode : 'default';
if (!isset($field_context['custom_settings'])) {
$field_context['custom_settings'] = $actual_mode && $actual_mode == $view_mode;
}
$field_context['old_entity'] = $old_entity;
$field_context['new_entity'] = $new_entity;
$field_context['bundle_name'] = $bundle_name;
foreach ($instances as $instance) {
// Any view mode is supported in relation to hiding fields, but only if
// selected (todo see if this is a valid option).
if ($actual_mode && $instance['display'][$actual_mode]['type'] == 'hidden') {
continue;
}
$field_name = $instance['field_name'];
$field = field_info_field($field_name);
$field_context['field'] = $field;
$field_context['instance'] = $instance;
$field_context['display'] = $instance['display'][$actual_mode];
// We provide a loose check on the field access.
if (field_access('view', $field, $entity_type) || field_access('edit', $field, $entity_type)) {
$langcode = field_language($entity_type, $new_entity, $field_name);
$field_context['language'] = $langcode;
$field_context['field'] = $field;
$field_context['instance'] = $instance;
$old_items = array();
if (!empty($old_entity->{$field_name}[$langcode])) {
$old_items = $old_entity->{$field_name}[$langcode];
}
$new_items = array();
if (!empty($new_entity->{$field_name}[$langcode])) {
$new_items = $new_entity->{$field_name}[$langcode];
}
// Load files containing the field callbacks.
_diff_autoload($field);
$field_context['settings'] = diff_get_field_settings($field_context);
// Reference fields can optionally prepare objects in bulk to reduce
// overheads related to multiple database calls. If a field considers
// that the delta values is meaningless, they can order and rearrange
// to provide cleaner results.
$func = $field['module'] . '_field_diff_view_prepare';
if (function_exists($func)) {
$func($old_items, $new_items, $field_context);
}
// Allow other modules to act safely on behalf of the core field module.
drupal_alter('field_diff_view_prepare', $old_items, $new_items, $field_context);
// These functions compiles the items into comparable arrays of strings.
$func = $field['module'] . '_field_diff_view';
if (!function_exists($func)) {
$func = 'diff_field_diff_view';
}
// These callbacks should be independent of revision.
$old_context = $field_context;
$old_context['entity'] = $old_entity;
$old_values = $func($old_items, $old_context);
$new_context = $field_context;
$new_context['entity'] = $new_entity;
$new_values = $func($new_items, $new_context);
// Allow other modules to act safely on behalf of the core field module.
drupal_alter('field_diff_view', $old_values, $old_items, $old_context);
drupal_alter('field_diff_view', $new_values, $new_items, $new_context);
$max = max(array(count($old_values), count($new_values)));
if ($max) {
$result[$field_name] = array(
'#name' => $instance['label'],
'#old' => array(),
'#new' => array(),
'#settings' => $field_context['settings'],
);
for ($delta = 0; $delta < $max; $delta++) {
if (isset($old_values[$delta])) {
$result[$field_name]['#old'][] = is_array($old_values[$delta]) ? implode("\n", $old_values[$delta]) : $old_values[$delta];
}
if (isset($new_values[$delta])) {
$result[$field_name]['#new'][] = is_array($new_values[$delta]) ? implode("\n", $new_values[$delta]) : $new_values[$delta];
}
}
$result[$field_name]['#old'] = implode("\n", $result[$field_name]['#old']);
$result[$field_name]['#new'] = implode("\n", $result[$field_name]['#new']);
if ($actual_mode) {
$result[$field_name]['#weight'] = $instance['display'][$actual_mode]['weight'];
}
}
}
}
return $result;
}
/**
* A generic handler for parsing field values.
*
* This callback can only handle the most basic of fields that populates the
* safe_value during field load or use the value column for data storage.
*
* @param array $items
* An array of field items.
* @param array $context
* An associative array containing:
* - entity: The entity that the items belong to.
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - bundle: The bundle name.
* - field: The field that the items belong to.
* - instance: The instance that the items belong to.
* - language: The language associated with $items.
* - old_entity: The older entity.
* - new_entity: The newer entity.
*
* @return array
* An array of strings representing the value, keyed by delta index.
*/
function diff_field_diff_view($items, $context) {
$diff_items = array();
$entity = clone $context['entity'];
$langcode = field_language($context['entity_type'], $entity, $context['field']['field_name']);
$view_mode = empty($context['view_mode']) ? 'diff_standard' : $context['view_mode'];
$element = field_view_field($context['entity_type'], $entity, $context['field']['field_name'], $view_mode, $langcode);
foreach (element_children($element) as $delta) {
$diff_items[$delta] = drupal_render($element[$delta]);
}
return $diff_items;
}
/**
* Helper function to get the settings for a given field or formatter.
*
* @param array $context
* This will get the settings for a field.
* - field (required): The field that the items belong to.
* - entity: The entity that we are looking up.
* - instance: The instance that the items belong to.
* - view_mode: The view mode to use. Defaults to FALSE.
*
* @return array
* The settings for this field type.
*/
function diff_get_field_settings($field_context) {
$field = $field_context['field'];
// Update saved settings from the global settings for this field type.
$settings = variable_get("diff_{$field['module']}_field_{$field['type']}_default_options", array());
$settings = _diff_field_default_settings($field['module'], $field['type'], $settings);
// Allow modules to alter the field settings based on the current context.
drupal_alter('diff_field_settings', $settings, $field_context);
return $settings;
}
/**
* Helper function to initiate any global form elements.
*/
function diff_global_settings_form(&$subform, $form_state, $type, $settings) {
$subform['show_header'] = array(
'#type' => 'checkbox',
'#title' => t('Show field title'),
'#default_value' => $settings['show_header'],
'#weight' => -5,
);
$subform['markdown'] = array(
'#type' => 'select',
'#title' => t('Markdown callback'),
'#default_value' => $settings['markdown'],
'#options' => array(
'drupal_html_to_text' => t('Drupal HTML to Text'),
'filter_xss' => t('Filter XSS (some tags)'),
'diff_filter_xss' => t('Filter XSS (all tags)'),
),
'#description' => t('These provide ways to clean markup tags to make comparisons easier to read.'),
'#empty_option' => t('- Do not process -'),
);
$subform['line_counter'] = array(
'#type' => 'radios',
'#title' => t('Line counter'),
'#default_value' => $settings['line_counter'],
'#description' => t('This outputs the (approximate) line numbers as a heading before every change.'),
'#options' => array(
'' => t('None. Counter ignore and not incremented.'),
'hidden' => t('Count lines but do not show line headers.'),
'line' => t('Count and show lines, restarting counter at 0.'),
'line_continuous' => t('Count and show lines, incrementing counter from last item.'),
),
);
/*
This would be cool, but to do anything else than inline with the text appears
to be very hard, requiring a refactoring of both the modules API but also the
DiffFormatter and Diff classes. Diff 8.x-4.x maybe.
$subform['show_delta'] = array(
'#type' => 'checkbox',
'#title' => t('Show delta values'),
'#default_value' => $settings['show_delta'],
);
$subform['delta_format'] = array(
'#type' => 'radios',
'#title' => t('Delta insertion method'),
'#default_value' => $settings['delta_format'],
'#options' => array(
'inline' => t('Prefix to item'),
'row' => t('Individual row'),
),
'#states' => array(
'invisible' => array(
"input[id$='show-delta']" => array('checked' => FALSE),
),
),
);
*/
}
/**
* Helper function to populate the settings array.
*/
function _diff_field_default_settings($module, $field_type, $settings = array()) {
// Load files containing the field callbacks.
_diff_autoload($module);
// Populate any missing values from CALLBACK_field_diff_default_options().
$func = $module . '_field_diff_default_options';
if (function_exists($func)) {
$settings += $func($field_type);
}
// Check for Diff support. If it doesn't exist, the default markdown should
// escape the field display, otherwise a raw format should be used.
$func = $module . '_field_diff_view';
// Global settings.
$settings += array(
'markdown' => function_exists($func) ? '' : 'drupal_html_to_text',
'line_counter' => '',
'show_header' => 1,
// Can we? This seems too hard to track in the DiffFormatter as all we
// have is a string or an array of strings.
//'show_delta' => 0,
//'delta_format' => 'row',
);
return $settings;
}
/**
* Private helper function to load field includes.
*
* @param array|string $field_or_module
* The field definition array or the module that implements the field.
*/
function _diff_autoload($field_or_module) {
$includes = &drupal_static(__FUNCTION__, FALSE);
if (!$includes) {
$includes = array(
'file' => module_exists('file'),
'image' => module_exists('image'),
'list' => module_exists('list'),
'taxonomy' => module_exists('taxonomy'),
'text' => module_exists('text'),
'number' => module_exists('number'),
);
}
$module = is_string($field_or_module) ? $field_or_module : $field_or_module['module'];
// Since field hooks are not real hooks, we manually load the field modules
// MODULE.diff.inc. We handle the five core field defining modules.
if (!isset($includes[$module])) {
module_load_include('diff.inc', $module);
$includes[$module] = 0;
}
elseif (!empty($includes[$module])) {
module_load_include('inc', 'diff', 'includes/' . $module);
$includes[$module] = 0;
}
}
/**
* Helper function to parse out the state in the diff results.
*/
function diff_extract_state($diff, $state = 'raw') {
$states = array(
0 => NULL,
1 => NULL,
);
if (isset($diff['#states'][$state])) {
if (isset($diff['#states'][$state]['#old'])) {
$states[0] = $diff['#states'][$state]['#old'];
}
if (isset($diff['#states'][$state]['#new'])) {
$states[1] = $diff['#states'][$state]['#new'];
}
}
return $states;
}

View File

@@ -0,0 +1,12 @@
name = Diff
description = Show differences between content revisions.
core = 7.x
files[] = DiffEngine.php
; Information added by drupal.org packaging script on 2012-11-13
version = "7.x-3.2"
core = "7.x"
project = "diff"
datestamp = "1352784357"

View File

@@ -0,0 +1,124 @@
<?php
/**
* @file
* Provides uninstallation functions.
*/
/**
* Implements hook_uninstall().
*/
function diff_uninstall() {
// Bulk delete entity based variables.
$prefixes = array(
'diff_enable_revisions_page_',
'diff_show_',
'diff_view_mode_',
'diff_admin_path_',
'diff_default_state_',
);
foreach ($prefixes as $prefix) {
db_delete('variable')
->condition('name', db_like($prefix) . '%', 'LIKE')
->execute();
}
// Delete global variables.
variable_del('diff_context_lines_trailing');
variable_del('diff_context_lines_leading');
variable_del('diff_theme');
variable_del('diff_radio_behavior', '');
foreach (field_info_fields() as $field) {
variable_del("diff_{$field['module']}_field_{$field['type']}_default_options");
}
}
/**
* Updates the existing system variables to target the entity type and bundle.
*/
function diff_update_7300() {
$node_types = array_keys(node_type_get_types());
foreach ($node_types as $bundle) {
$type_variables = array(
'show_preview_changes',
'enable_revisions_page',
'show_diff_inline',
);
foreach ($type_variables as $prefix) {
$setting = variable_get($prefix . '_' . $bundle, NULL);
if (isset($setting)) {
variable_del($prefix . '_' . $bundle);
variable_set('diff_' . $prefix . '_node_' . $bundle, $setting);
}
}
}
}
/**
* Removed diff_update_7301().
*/
/**
* Removed diff_update_7302().
*/
/**
* Renames some internal settings names.
*/
function diff_update_7303() {
// Get current values
$radio = variable_get('diff_script_revisioning', 'simple');
$leading = variable_get('diff_leading_context_lines', 2);
$trailing = variable_get('diff_trailing_context_lines', 2);
// Create new variable names
variable_set('diff_radio_behavior', $radio);
variable_set('diff_context_lines_leading', $leading);
variable_set('diff_context_lines_trailing', $trailing);
// Delete old variables
variable_del('diff_script_revisioning');
variable_del('diff_leading_context_lines');
variable_del('diff_trailing_context_lines');
}
/**
* Removes unused variable settings and merges inline diff block settings.
*/
function diff_update_7304() {
// This is now always applied to text fields.
variable_del('diff_normalise_text');
// Merge the content type settings for the inline diff block into a single variable.
// diff_update_7300() - show_diff_inline_TYPE to diff_show_diff_inline_node_TYPE
$node_types = array_keys(node_type_get_types());
$enabled_types = array();
foreach ($node_types as $node_type) {
if (variable_get('diff_show_diff_inline_node_' . $node_type, FALSE)) {
$enabled_types[$node_type] = $node_type;
}
variable_del('diff_show_diff_inline_node_' . $node_type);
}
variable_set('diff_show_diff_inline_node_bundles', $enabled_types);
// Warn users that these settings are altered.
drupal_set_message(t('Diff <em>Inline differences</em> content type settings are now located within the <em>Inline differences</em> block settings.'));
}
/**
* Updates to normalize the new view mode settings.
*/
function diff_update_7305() {
// Rebuild the menus.
variable_set('menu_rebuild_needed', TRUE);
// Removed the enforced entity view mode.
db_delete('variable')
->condition('name', db_like('diff_view_mode_standard_node_') . '%', 'LIKE')
->execute();
// Removes the configurable view mode for the inline diff block, as this
// is fairly meaningless and confusing to users.
db_delete('variable')
->condition('name', db_like('diff_view_mode_inline_') . '%', 'LIKE')
->execute();
}

View File

@@ -0,0 +1,623 @@
<?php
/**
* @file
* Provides functionality to show a diff between two node revisions.
*/
/**
* Number of items on one page of the revision list.
*/
define('REVISION_LIST_SIZE', 50);
/**
* Exposed sorting options.
*
* No sorting means sorting by delta value for fields.
*/
define('DIFF_SORT_NONE', '0');
/**
* Exposed sorting options.
*
* This normally sorts by the rendered comparison.
*/
define('DIFF_SORT_VALUE', '1');
/**
* Exposed sorting options.
*
* It is up to the field / entity to decide how to handle the sort.
*/
define('DIFF_SORT_CUSTOM', '-1');
/**
* Implements hook_help().
*/
function diff_help($path, $arg) {
switch ($path) {
case 'admin/help#diff':
$output = '<p>' . t('The Diff module replaces the normal <em>Revisions</em> node tab. Diff enhances the listing of revisions with an option to view the differences between any two content revisions. Access to this feature is controlled with the <em>View revisions</em> permission. The feature can be disabled for an entire content type on the content type configuration page. Diff also provides an optional <em>View changes</em> button while editing a node.') . '</p>';
return $output;
case 'node/%/revisions/%/view':
// The translated strings should match node_help('node/%/revisions').
return '<p>' . t('Revisions allow you to track differences between multiple versions of your content, and revert back to older versions.') . '</p>';
case 'node/%/revisions/view/%/%':
return '<p>' . t('Comparing two revisions:') . '</p>';
}
}
/**
* The various states that are available.
*/
function diff_available_states($entity_type = NULL) {
$states = array(
'raw' => t('Standard'),
'raw_plain' => t('Marked down'),
);
return $states;
}
/**
* Implements hook_menu().
*
* @todo: Review this.
*/
function diff_menu() {
/*
* By using MENU_LOCAL_TASK (and 'tab_parent') we can get the various
* revision-views to show the View|Edit|Revision-tabs of the node on top,
* and have the Revisions-tab open. To avoid creating/showing any extra tabs
* or sub-tabs (tasks below top level) for the various paths (i.e. "Diff",
* "Show latest" and "Show a specific revision") that need a revision-id (vid)
* parameter, we make sure to set 'tab_parent' a bit odd. This solution may
* not be the prettiest one, but by avoiding having two _LOCAL_TASKs sharing
* a parent that can be accessed by its full path, it seems to work as
* desired. Breadcrumbs work decently, at least the node link is among the
* crumbs. For some reason any breadcrumbs "before/above" the node is only
* seen at 'node/%node/revisions/%/view'.
*/
// Not used directly, but was created to get the other menu items to work.
$items['node/%node/revisions/list'] = array(
'title' => 'List revisions',
'page callback' => 'diff_diffs_overview',
'type' => MENU_DEFAULT_LOCAL_TASK,
'access callback' => 'diff_node_revision_access',
'access arguments' => array(1),
'file' => 'diff.pages.inc',
);
$items['node/%node/revisions/view'] = array(
'title' => 'Compare revisions',
'page callback' => 'diff_diffs_show',
'page arguments' => array(1, 4, 5, 6),
'type' => MENU_LOCAL_TASK,
'access callback' => 'diff_node_revision_access',
'access arguments' => array(1),
'tab_parent' => 'node/%/revisions/list',
'file' => 'diff.pages.inc',
);
$items['node/%node/revisions/view/latest'] = array(
'title' => 'Show latest difference',
'page callback' => 'diff_latest',
'page arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'access callback' => 'diff_node_revision_access',
'access arguments' => array(1),
'tab_parent' => 'node/%/revisions/view',
'file' => 'diff.pages.inc',
);
// Administrative settings.
$items['admin/config/content/diff'] = array(
'title' => 'Diff',
'description' => 'Diff settings.',
'file' => 'diff.admin.inc',
'page callback' => 'drupal_get_form',
'page arguments' => array('diff_admin_settings'),
'access arguments' => array('administer site configuration'),
);
$items['admin/config/content/diff/settings'] = array(
'title' => 'Settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['admin/config/content/diff/fields'] = array(
'title' => 'Fields',
'description' => 'Field support and settings overview.',
'file' => 'diff.admin.inc',
'page callback' => 'diff_admin_field_overview',
'access arguments' => array('administer site configuration'),
'type' => MENU_LOCAL_TASK,
);
$items['admin/config/content/diff/fields/%'] = array(
'title' => 'Global field settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('diff_admin_global_field_settings', 5),
'access arguments' => array('administer site configuration'),
'type' => MENU_VISIBLE_IN_BREADCRUMB,
'file' => 'diff.admin.inc',
);
$items['admin/config/content/diff/entities'] = array(
'title' => 'Entities',
'description' => 'Entity settings.',
'file' => 'diff.admin.inc',
'page callback' => 'drupal_get_form',
'page arguments' => array('diff_admin_global_entity_settings', 'node'),
'access arguments' => array('administer site configuration'),
'type' => MENU_LOCAL_TASK,
);
$items['admin/config/content/diff/entities/node'] = array(
'title' => 'Node',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
return $items;
}
/**
* Implements hook_menu_alter().
*/
function diff_menu_alter(&$callbacks) {
// Overwrite the default 'Revisions' page.
$callbacks['node/%node/revisions']['page callback'] = 'diff_diffs_overview';
$callbacks['node/%node/revisions']['module'] = 'diff';
$callbacks['node/%node/revisions']['file'] = 'diff.pages.inc';
$callbacks['node/%node/revisions/%/view']['tab_parent'] = 'node/%/revisions/list';
$callbacks['node/%node/revisions/%/revert']['tab_parent'] = 'node/%/revisions/%/view';
$callbacks['node/%node/revisions/%/delete']['tab_parent'] = 'node/%/revisions/%/view';
$callbacks['node/%node/revisions']['access callback']
= $callbacks['node/%node/revisions/%/view']['access callback']
= $callbacks['node/%node/revisions/%/revert']['access callback']
= $callbacks['node/%node/revisions/%/delete']['access callback'] = 'diff_node_revision_access';
}
/**
* Implements hook_admin_paths_alter().
*/
function diff_admin_paths_alter(&$paths) {
// By default, treat all diff pages as administrative.
if (variable_get('diff_admin_path_node', 1)) {
$paths['node/*/revisions/view/*/*'] = TRUE;
}
}
/**
* Access callback for the node revisions page.
*/
function diff_node_revision_access($node, $op = 'view') {
$may_revision_this_type = variable_get('diff_enable_revisions_page_node_' . $node->type, TRUE) || user_access('administer nodes');
return $may_revision_this_type && _node_revision_access($node, $op);
}
/**
* Implements hook_hook_info().
*/
function diff_hook_info() {
$hooks['entity_diff'] = array(
'group' => 'diff',
);
$hooks['diff'] = array(
'group' => 'diff',
);
$hooks['field_diff_view_prepare_alter'] = array(
'group' => 'diff',
);
$hooks['field_diff_view_alter'] = array(
'group' => 'diff',
);
return $hooks;
}
/**
* Implements hook_entity_info_alter().
*
* Although the module only provides an UI for comparing nodes, it has an
* extendable API for any entity, so we supply two view modes for all entities.
* - diff_standard: This view mode is used to tell the module how to compare
* individual fields. This is used on the revisions page.
*/
function diff_entity_info_alter(&$entity_info) {
foreach (array_keys($entity_info) as $entity_type) {
if (!empty($entity_info[$entity_type]['view modes'])) {
$entity_info[$entity_type]['view modes'] += array(
'diff_standard' => array(
'label' => t('Revision comparison'),
'custom settings' => FALSE,
),
);
}
}
}
/**
* Implements hook_block_info().
*/
function diff_block_info() {
return array(
'inline' => array(
'info' => t('Inline differences'),
),
);
}
/**
* Implements hook_block_configure().
*/
function diff_block_configure($delta = '') {
$form = array();
switch ($delta) {
case 'inline':
$form['bundles'] = array(
'#type' => 'checkboxes',
'#title' => t('Enabled content types'),
'#default_value' => variable_get('diff_show_diff_inline_node_bundles', array()),
'#options' => node_type_get_names(),
'#description' => t('Show this block only on pages that display content of the given type(s).'),
);
break;
}
return $form;
}
/**
* Implements hook_block_save().
*/
function diff_block_save($delta = '', $edit = array()) {
switch ($delta) {
case 'inline':
variable_set('diff_show_diff_inline_node_bundles', $edit['bundles']);
break;
}
}
/**
* Implements hook_block_view().
*/
function diff_block_view($delta) {
if ($delta === 'inline' && user_access('view revisions') && ($node = menu_get_object()) && arg(2) !== 'edit') {
$enabled_types = variable_get('diff_show_diff_inline_node_bundles', array());
if (!empty($enabled_types[$node->type])) {
$block = array();
$revisions = node_revision_list($node);
if (count($revisions) > 1) {
$block['subject'] = t('Highlight changes');
$block['content'] = drupal_get_form('diff_inline_form', $node, $revisions);
}
return $block;
}
}
}
/**
* Implements hook_node_view_alter().
*/
function diff_node_view_alter(&$build) {
$node = $build['#node'];
if (user_access('view revisions') && in_array($node->type, variable_get('diff_show_diff_inline_node_bundles', array()))) {
// Ugly but cheap way to check that we are viewing a node's revision page.
if (arg(2) === 'revisions' && arg(3) === $node->vid) {
module_load_include('inc', 'diff', 'diff.pages');
$old_vid = _diff_get_previous_vid(node_revision_list($node), $node->vid);
$build = array('#markup' => diff_inline_show($node, $old_vid));
}
$build['#prefix'] = isset($build['#prefix']) ? "<div id='diff-inline-{$node->nid}'>" . $build['#prefix'] : "<div id='diff-inline-{$node->nid}'>";
$build['#suffix'] = isset($build['#suffix']) ? $build['#suffix'] . "</div>" : "</div>";
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function diff_form_node_form_alter(&$form, $form_state) {
// Add a 'View changes' button on the node edit form.
$node = $form['#node'];
if (variable_get('diff_show_preview_changes_node_' . $node->type, TRUE) && !empty($node->nid)) {
$form['actions']['preview_changes'] = array(
'#type' => 'submit',
'#value' => t('View changes'),
'#weight' => 12,
'#submit' => array('diff_node_form_build_preview_changes'),
);
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function diff_form_node_type_form_alter(&$form, $form_state) {
if (isset($form['type'])) {
$type = $form['#node_type'];
$form['diff'] = array(
'#title' => t('Compare revisions'),
'#type' => 'fieldset',
'#group' => 'additional_settings',
'#tree' => FALSE,
);
$form['diff']['diff_show_preview_changes_node'] = array(
'#type' => 'checkbox',
'#title' => t('Show <em>View changes</em> button on node edit form'),
'#weight' => 10,
'#default_value' => variable_get('diff_show_preview_changes_node_' . $type->type, TRUE),
);
$form['diff']['diff_enable_revisions_page_node'] = array(
'#type' => 'checkbox',
'#title' => t('Enable the <em>Revisions</em> page for this content type'),
'#weight' => 11,
'#default_value' => variable_get('diff_enable_revisions_page_node_' . $type->type, TRUE),
);
$options = array();
$info = entity_get_info('node');
foreach ($info['view modes'] as $view_mode => $view_mode_info) {
$options[$view_mode] = $view_mode_info['label'];
}
$form['diff']['diff_view_mode_preview_node'] = array(
'#type' => 'select',
'#title' => t('Standard comparison preview'),
'#description' => t('Governs the <em>Current revision</em> view mode when doing standard comparisons.'),
'#options' => $options,
'#weight' => 13,
'#default_value' => variable_get('diff_view_mode_preview_node_' . $type->type, 'full'),
'#empty_value' => '',
'#empty_option' => t('- Do not display -'),
);
}
}
/**
* Implements hook_node_type_update().
*
* This tracks the diff settings in case the node content type is renamed.
*/
function diff_node_type_update($info) {
if (!empty($info->old_type) && $info->old_type != $info->type) {
$type_variables = array(
'diff_show_preview_changes_node',
'diff_enable_revisions_page_node',
'diff_view_mode_preview_node',
);
foreach ($type_variables as $prefix) {
$setting = variable_get($prefix . '_' . $info->old_type, NULL);
if (isset($setting)) {
variable_del($prefix . '_' . $info->old_type);
variable_set($prefix . '_' . $info->type, $setting);
}
}
// Block settings are combined in a single variable.
$inline_block_types = variable_get('diff_show_diff_inline_node_bundles', array());
if (isset($inline_block_types[$info->old_type])) {
if (!empty($inline_block_types[$info->old_type])) {
$inline_block_types[$info->type] = $info->type;
}
unset($inline_block_types[$info->old_type]);
variable_set('diff_show_diff_inline_node_bundles', $inline_block_types);
}
}
}
/**
* Implements hook_node_type_delete().
*/
function diff_node_type_delete($info) {
variable_del('diff_show_preview_changes_node_' . $info->type);
variable_del('diff_enable_revisions_page_node_' . $info->type);
variable_del('diff_view_mode_preview_node_' . $info->type);
}
/**
* Submit handler for the 'View changes' action.
*
* @see node_form_build_preview()
*/
function diff_node_form_build_preview_changes($form, &$form_state) {
module_load_include('inc', 'diff', 'diff.pages');
$old_node = clone node_load($form_state['values']['nid']);
$node = node_form_submit_build_node($form, $form_state);
// Create diff of old node and edited node.
$rows = _diff_body_rows($old_node, $node);
$header = _diff_default_header(t('Original'), t('Changes'));
$changes = theme('table__diff__preview', array(
'header' => $header,
'rows' => $rows,
'attributes' => array('class' => 'diff'),
'colgroups' => _diff_default_cols(),
'sticky' => FALSE,
));
// Prepend diff to edit form.
$form_state['node_preview'] = $changes;
$form_state['rebuild'] = TRUE;
}
/**
* Implements hook_theme().
*/
function diff_theme() {
return array(
'diff_node_revisions' => array(
'render element' => 'form',
'file' => 'diff.theme.inc',
),
'diff_header_line' => array(
'arguments' => array('lineno' => NULL),
'file' => 'diff.theme.inc',
),
'diff_content_line' => array(
'arguments' => array('line' => NULL),
'file' => 'diff.theme.inc',
),
'diff_empty_line' => array(
'arguments' => array('line' => NULL),
'file' => 'diff.theme.inc',
),
'diff_inline_form' => array(
'render element' => 'form',
'file' => 'diff.theme.inc',
),
'diff_inline_metadata' => array(
'arguments' => array('node' => NULL),
'file' => 'diff.theme.inc',
),
'diff_inline_chunk' => array(
'arguments' => array('text' => '', 'type' => NULL),
'file' => 'diff.theme.inc',
),
);
}
/**
* Render the table rows for theme('table').
*
* @param string $a
* The source string to compare from.
* @param string $b
* The target string to compare to.
* @param boolean $show_header
* Display diff context headers. For example, "Line x".
* @param array $line_stats
* This structure tracks line numbers across multiple calls to DiffFormatter.
*
* @return array
* Array of rows usable with theme('table').
*/
function diff_get_rows($a, $b, $show_header = FALSE, &$line_stats = NULL) {
$a = is_array($a) ? $a : explode("\n", $a);
$b = is_array($b) ? $b : explode("\n", $b);
if (!isset($line_stats)) {
$line_stats = array(
'counter' => array('x' => 0, 'y' => 0),
'offset' => array('x' => 0, 'y' => 0),
);
}
$formatter = new DrupalDiffFormatter();
// Header is the line counter.
$formatter->show_header = $show_header;
$formatter->line_stats = &$line_stats;
$diff = new Diff($a, $b);
return $formatter->format($diff);
}
/**
* Render and markup a diff of two strings into HTML markup.
*
* @param string $a
* The source string to compare from.
* @param string $b
* The target string to compare to.
*
* @return string
* String containing HTML markup.
*/
function diff_get_inline($a, $b) {
$diff = new DrupalDiffInline($a, $b);
return $diff->render();
}
/**
* Form builder: Inline diff controls.
*/
function diff_inline_form($form, $form_state, $node, $revisions) {
$form = array();
$form['node'] = array(
'#type' => 'value',
'#value' => $node,
);
$form['revision'] = array(
'#type' => 'select',
'#options' => array(0 => t('- No highlighting -')),
'#default_value' => (arg(2) === 'revisions' && arg(3) === $node->vid) ? $node->vid : 0,
'#ajax' => array(
'callback' => 'diff_inline_ajax',
'wrapper' => "node-{$node->nid}",
'method' => 'replace',
),
);
foreach ($revisions as $revision) {
$form['revision']['#options'][$revision->vid] = t('@revision by @name', array(
'@revision' => format_date($revision->timestamp, 'short'),
'@name' => format_username($revision),
));
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('View'),
'#submit' => array('diff_inline_form_submit'),
'#attributes' => array('class' => array('diff-js-hidden')),
);
return $form;
}
/**
* AHAH callback for rendering the inline diff of a node.
*/
function diff_inline_ajax($form, $form_state) {
module_load_include('inc', 'diff', 'diff.pages');
$node = $form['node']['#value'];
$vid = isset($form_state['values']['revision']) ? $form_state['values']['revision'] : 0;
return "<div id='node-{$node->nid}'>" . diff_inline_show($node, $vid) . "</div>";
}
/**
* Form submission handler for diff_inline_form() for JS-disabled clients.
*/
function diff_inline_form_submit(&$form, &$form_state) {
if (isset($form_state['values']['revision'], $form_state['values']['node'])) {
$node = $form_state['values']['node'];
$vid = $form_state['values']['revision'];
$form_state['redirect'] = "node/{$node->nid}/revisions/{$vid}/view";
}
}
/**
* A helper function to normalise system differences.
*
* This handles differences in:
* - line endings: Mac, Windows and UNIX all use different line endings.
*/
function diff_normalise_text($text) {
$text = str_replace(array("\r\n", "\r"), "\n", $text);
return $text;
}
/**
* A wrapper function for filter_xss() to exclude all tags.
*/
function diff_filter_xss($string) {
return filter_xss($string, array());
}
/**
* Helper function to load any CSS or JScript files required by a page or form.
*/
function diff_build_attachments($jscript = FALSE) {
$attachments = array();
$theme = variable_get('diff_theme', 'default');
if ($theme) {
$attachments['css'] = array(
drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css",
);
}
$type = variable_get('diff_radio_behavior', 'simple');
if ($jscript && $type) {
$attachments['js'] = array(
drupal_get_path('module', 'diff') . "/js/diff.js",
array(
'data' => array('diffRevisionRadios' => $type),
'type' => 'setting',
),
);
}
return $attachments;
}

View File

@@ -0,0 +1,632 @@
<?php
/**
* @file
* Menu callbacks for hook_menu().
*/
/**
* Menu callback - show latest diff for a given node.
*/
function diff_latest($node) {
$revisions = node_revision_list($node);
$new = array_shift($revisions);
$old = array_shift($revisions);
drupal_goto("node/{$node->nid}/revisions/view/{$old->vid}/{$new->vid}");
}
/**
* Menu callback - an overview table of older revisions.
*
* Generate an overview table of older revisions of a node and provide
* an input form to select two revisions for a comparison.
*/
function diff_diffs_overview($node) {
drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH);
return drupal_get_form('diff_node_revisions', $node);
}
/**
* Input form to select two revisions.
*/
function diff_node_revisions($form, $form_state, $node) {
$form['nid'] = array(
'#type' => 'hidden',
'#value' => $node->nid,
);
$revision_list = node_revision_list($node);
if (count($revision_list) > REVISION_LIST_SIZE) {
// If the list of revisions is longer than the number shown on one page
// split the array.
$page = isset($_GET['page']) ? $_GET['page'] : '0';
$revision_chunks = array_chunk(node_revision_list($node), REVISION_LIST_SIZE);
$revisions = $revision_chunks[$page];
// Set up global pager variables as would 'pager_query' do.
// These variables are then used in the theme('pager') call later.
global $pager_page_array, $pager_total, $pager_total_items;
$pager_total_items[0] = count($revision_list);
$pager_total[0] = ceil(count($revision_list) / REVISION_LIST_SIZE);
$pager_page_array[0] = max(0, min($page, ((int) $pager_total[0]) - 1));
}
else {
$revisions = $revision_list;
}
$revert_permission = FALSE;
if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
$revert_permission = TRUE;
}
$delete_permission = FALSE;
if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) {
$delete_permission = TRUE;
}
foreach ($revisions as $revision) {
$operations = array();
$revision_ids[$revision->vid] = '';
$revision_log = ($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '';
if ($revision->current_vid > 0) {
$form['info'][$revision->vid] = array(
'#markup' => t('!date by !username', array(
'!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"),
'!username' => theme('username', array('account' => $revision)))) . $revision_log,
);
}
else {
$diff_date = l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view");
$form['info'][$revision->vid] = array(
'#markup' => t('!date by !username', array(
'!date' => $diff_date,
'!username' => theme('username', array('account' => $revision)))
) . $revision_log,
);
if ($revert_permission) {
$operations[] = array(
'#markup' => l(t('Revert'), "node/$node->nid/revisions/$revision->vid/revert"),
);
}
if ($delete_permission) {
$operations[] = array(
'#markup' => l(t('Delete'), "node/$node->nid/revisions/$revision->vid/delete"),
);
}
// Set a dummy, even if the user has no permission for the other
// operations, so that we can check if the operations array is
// empty to know if the row denotes the current revision.
$operations[] = array();
}
$form['operations'][$revision->vid] = $operations;
}
$new_vid = key($revision_ids);
next($revision_ids);
$old_vid = key($revision_ids);
$form['diff']['old'] = array(
'#type' => 'radios',
'#options' => $revision_ids,
'#default_value' => $old_vid,
);
$form['diff']['new'] = array(
'#type' => 'radios',
'#options' => $revision_ids,
'#default_value' => $new_vid,
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Compare'));
if (count($revision_list) > REVISION_LIST_SIZE) {
$form['#suffix'] = theme('pager');
}
$form['#attached'] = diff_build_attachments(TRUE);
return $form;
}
/**
* Submit code for input form to select two revisions.
*/
function diff_node_revisions_submit($form, &$form_state) {
// The ids are ordered so the old revision is always on the left.
$old_vid = min($form_state['values']['old'], $form_state['values']['new']);
$new_vid = max($form_state['values']['old'], $form_state['values']['new']);
$form_state['redirect'] = 'node/' . $form_state['values']['nid'] . '/revisions/view/' . $old_vid . '/' . $new_vid;
}
/**
* Validation for input form to select two revisions.
*/
function diff_node_revisions_validate($form, &$form_state) {
$old_vid = $form_state['values']['old'];
$new_vid = $form_state['values']['new'];
if ($old_vid == $new_vid || !$old_vid || !$new_vid) {
form_set_error('diff', t('Select different revisions to compare.'));
}
}
/**
* Create a comparison for the node between versions 'old_vid' and 'new_vid'.
*
* @param object $node
* Node on which to perform comparison
* @param integer $old_vid
* Version ID of the old revision.
* @param integer $new_vid
* Version ID of the new revision.
*/
function diff_diffs_show($node, $old_vid, $new_vid, $state = NULL) {
// Attaches the CSS.
$build['#attached'] = diff_build_attachments();
$default_state = variable_get('diff_default_state_node', 'raw');
if (empty($state)) {
$state = $default_state;
}
$state = str_replace('-', '_', $state);
if (!array_key_exists($state, diff_available_states())) {
$state = $default_state;
}
// Same title as the 'Revisions' tab. Blocked by non-page requests.
if (node_is_page($node)) {
drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH);
}
$node_revisions = node_revision_list($node);
$old_node = node_load($node->nid, $old_vid);
$new_node = node_load($node->nid, $new_vid);
// Generate table header (date, username, log message).
$old_header = t('!date by !username', array(
'!date' => l(format_date($old_node->revision_timestamp), "node/$node->nid/revisions/$old_node->vid/view", array('absolute' => 1)),
'!username' => theme('username', array('account' => $node_revisions[$old_vid])),
));
$new_header = t('!date by !username', array(
'!date' => l(format_date($new_node->revision_timestamp), "node/$node->nid/revisions/$new_node->vid/view", array('absolute' => 1)),
'!username' => theme('username', array('account' => $node_revisions[$new_vid])),
));
$old_log = $old_node->log != '' ? '<p class="revision-log">' . filter_xss($old_node->log) . '</p>' : '';
$new_log = $new_node->log != '' ? '<p class="revision-log">' . filter_xss($new_node->log) . '</p>' : '';
// Generate previous diff/next diff links.
$nav_suffix = ($default_state != $state) ? '/' . str_replace('_', '-', $state) : '';
$next_vid = _diff_get_next_vid($node_revisions, $new_vid);
if ($next_vid) {
$next_link = l(t('Next difference >'), 'node/' . $node->nid . '/revisions/view/' . $new_vid . '/' . $next_vid . $nav_suffix, array('absolute' => 1));
}
else {
$next_link = '';
}
$prev_vid = _diff_get_previous_vid($node_revisions, $old_vid);
if ($prev_vid) {
$prev_link = l(t('< Previous difference'), 'node/' . $node->nid . '/revisions/view/' . $prev_vid . '/' . $old_vid . $nav_suffix, array('absolute' => 1));
}
else {
$prev_link = '';
}
$header = _diff_default_header($old_header, $new_header);
$rows = array();
if ($old_log || $new_log) {
$rows['logs'] = array(
array(
'data' => $old_log,
'colspan' => 2,
),
array(
'data' => $new_log,
'colspan' => 2,
),
);
}
$rows['navigation'] = array(
array(
'data' => $prev_link,
'class' => array('diff-prevlink'),
'colspan' => 2,
),
array(
'data' => $next_link,
'class' => array('diff-nextlink'),
'colspan' => 2,
),
);
$links = array();
foreach (diff_available_states('node') as $alternative_state => $label) {
if ($alternative_state == $state) {
// @todo: Should we show both or just alternatives?
}
$links[$alternative_state] = array(
'title' => $label,
'href' => "node/{$node->nid}/revisions/view/{$old_vid}/{$new_vid}" . ($alternative_state == $default_state ? '' : '/' . str_replace('_', '-', $alternative_state)),
);
}
if (count($links) > 1) {
$state_links = theme('links', array(
'links' => $links,
'attributes' => array('class' => array('links', 'inline')),
));
$rows['states'] = array(
array(
'data' => $state_links,
'class' => 'diff-links',
'colspan' => 4,
),
);
}
$rows = array_merge($rows, _diff_body_rows($old_node, $new_node, $state));
$build['diff_table'] = array(
'#theme' => 'table__diff__standard',
'#header' => $header,
'#rows' => $rows,
'#attributes' => array('class' => array('diff')),
'#colgroups' => _diff_default_cols(),
'#sticky' => FALSE,
);
// Allow users to hide or set the display mode of the preview.
if (node_is_page($node) && $view_mode = variable_get('diff_view_mode_preview_node_' . $new_node->type, 'full')) {
$header = '';
if ($node->vid == $new_vid) {
$header .= '<div class="diff-section-title">' . t('Current revision:') . '</div>';
}
else {
$header .= '<div class="diff-section-title">' . t('Revision of @new_date:', array('@new_date' => format_date($new_node->revision_timestamp))) . '</div>';
}
$build['diff_preview']['header']['#markup'] = $header;
// Don't include node links or comments when viewing the diff.
$build['diff_preview']['content'] = node_view($new_node, $view_mode);
if (isset($build['diff_preview']['content']['links'])) {
unset($build['diff_preview']['content']['links']);
}
if (isset($build['diff_preview']['content']['comments'])) {
unset($build['diff_preview']['content']['comments']);
}
}
return $build;
}
/**
* Creates an array of rows which represent the difference between nodes.
*
* @param object $old_node
* Node for comparison which will be displayed on the left side.
* @param object $new_node
* Node for comparison which will be displayed on the right side.
* @param boolean $state
* The state to render for the diff.
*/
function _diff_body_rows($old_node, $new_node, $state = 'raw') {
// This is an unique index only, so no need for drupal_static().
static $table_row_counter = 0;
if ($theme = variable_get('diff_theme', 'default')) {
drupal_add_css(drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css");
}
module_load_include('inc', 'diff', 'includes/node');
$rows = array();
$any_visible_change = FALSE;
$context = array(
'entity_type' => 'node',
'states' => array($state),
'view_mode' => 'diff_standard',
);
$node_diffs = diff_compare_entities($old_node, $new_node, $context);
// Track line numbers between multiple diffs.
$line_stats = array(
'counter' => array('x' => 0, 'y' => 0),
'offset' => array('x' => 0, 'y' => 0),
);
// Render diffs for each.
foreach ($node_diffs as $node_diff) {
$show_header = !empty($node_diff['#name']);
// These are field level settings.
if ($show_header && isset($node_diff['#settings']['show_header'])) {
$show_header = $show_header && $node_diff['#settings']['show_header'];
}
// Line counting and line header options.
if (empty($node_diff['#settings']['line_counter'])) {
$line_counter = FALSE;
}
else {
$line_counter = $node_diff['#settings']['line_counter'];
}
// Every call to 'line' resets the counters.
if ($line_counter) {
$line_stats['counter']['x'] = 0;
$line_stats['counter']['y'] = 0;
if ($line_counter == 'line' && 0) {
$line_stats['offset']['x'] = 0;
$line_stats['offset']['y'] = 0;
}
$line_stats_ref = $line_stats;
}
else {
$line_stats_ref = NULL;
}
list($old, $new) = diff_extract_state($node_diff, $state);
if ($node_diff_rows = diff_get_rows($old, $new, $line_counter && $line_counter != 'hidden', $line_stats_ref)) {
if ($line_counter && $line_counter != 'line') {
$line_stats['offset']['x'] += $line_stats_ref['counter']['x'];
$line_stats['offset']['y'] += $line_stats_ref['counter']['y'];
}
if ($show_header) {
$rows['diff-header-' . $table_row_counter++] = array(
array(
'data' => t('Changes to %name', array('%name' => $node_diff['#name'])),
'class' => 'diff-section-title',
'colspan' => 4,
),
);
}
// To avoid passing counter to the Diff engine, index rows manually here
// to allow modules to interact with the table. i.e. no array_merge().
foreach ($node_diff_rows as $row) {
$rows['diff-row-' . $table_row_counter++] = $row;
}
$any_visible_change = TRUE;
}
}
if (!$any_visible_change) {
$rows['diff-empty-' . $table_row_counter++] = array(
array(
'data' => t('No visible changes'),
'class' => 'diff-section-title',
'colspan' => 4,
),
);
// @todo: revise this.
// Needed to keep safari happy.
$rows['diff-empty-' . $table_row_counter++] = array(
array('data' => ''),
array('data' => ''),
array('data' => ''),
array('data' => ''),
);
}
return $rows;
}
/**
* Generic callback to compare two entities.
*/
function diff_compare_entities($left_entity, $right_entity, $context) {
$entity_type = $context['entity_type'];
list(, , $bundle) = entity_extract_ids($entity_type, $right_entity);
$context['bundle'] = $bundle;
$context['old_entity'] = $left_entity;
$context['new_entity'] = $right_entity;
$context += array(
'states' => array('raw'),
'view_mode' => FALSE,
'language' => LANGUAGE_NONE,
);
$diff = module_invoke_all('entity_diff', $left_entity, $right_entity, $context);
// Allow other modules to interact directly with the results.
drupal_alter('entity_diff', $diff, $context);
// We start off assuming all form elements are in the correct order.
$diff['#sorted'] = TRUE;
// Field rows. Recurse through all child elements.
$count = 0;
foreach (element_children($diff) as $key) {
if (!isset($diff[$key]['#states'])) {
$diff[$key]['#states'] = array();
}
// Ensure that the element follows the new #states format.
if (isset($diff[$key]['#old'])) {
$diff[$key]['#states']['raw']['#old'] = $diff[$key]['#old'];
unset($diff[$key]['#old']);
}
if (isset($diff[$key]['#new'])) {
$diff[$key]['#states']['raw']['#new'] = $diff[$key]['#new'];
unset($diff[$key]['#new']);
}
// If requested, we can convert the .
foreach (array('raw', 'rendered') as $state) {
if (in_array($state . '_plain', $context['states'])) {
diff_markdown_state($diff[$key], $state);
}
}
// Assign a decimal placeholder weight to preserve original array order.
if (!isset($diff[$key]['#weight'])) {
$diff[$key]['#weight'] = $count / 1000;
}
else {
// If one child element has a weight then we will need to sort later.
unset($diff['#sorted']);
}
$count++;
}
// One of the children has a #weight.
if (!isset($diff['#sorted'])) {
uasort($diff, 'element_sort');
}
// Process the array and get line counts per field.
array_walk($diff, 'diff_process_state_lines');
return $diff;
}
function diff_process_state_lines(&$diff, $key) {
foreach ($diff['#states'] as $state => $data) {
if (isset($data['#old'])) {
if (is_string($data['#old'])) {
$diff['#states'][$state]['#old'] = explode("\n", $data['#old']);
}
$diff['#states'][$state]['#count_old'] = count($diff['#states'][$state]['#old']);
}
else {
$diff['#states'][$state]['#count_old'] = 0;
}
if (isset($data['#new'])) {
if (is_string($data['#new'])) {
$diff['#states'][$state]['#new'] = explode("\n", $data['#new']);
}
$diff['#states'][$state]['#count_new'] = count($diff['#states'][$state]['#new']);
}
else {
$diff['#states'][$state]['#count_new'] = 0;
}
}
}
/**
* Helper function to render plain states from the corresponding raw state.
*
* @param array $diff
* The Diff Engine output array.
* @param string $state
* The state to markdown.
*/
function diff_markdown_state(&$diff, $state) {
list($plain_old, $plain_new) = diff_extract_state($diff, $state . '_plain');
list($old, $new) = diff_extract_state($diff, $state);
$markdown = FALSE;
if (isset($diff['#settings']) && !empty($diff['#settings']['markdown'])) {
if (function_exists($diff['#settings']['markdown'])) {
$markdown = $diff['#settings']['markdown'];
}
}
if (!isset($plain_old) && isset($old)) {
if (is_array($old)) {
$diff['#states'][$state . '_plain']['#old'] = $markdown ? array_map($markdown, $old) : $old;
}
else {
$diff['#states'][$state . '_plain']['#old'] = $markdown ? $markdown($old) : $old;
}
}
if (!isset($plain_new) && isset($new)) {
if (is_array($new)) {
$diff['#states'][$state . '_plain']['#new'] = $markdown ? array_map($markdown, $new) : $new;
}
else {
$diff['#states'][$state . '_plain']['#new'] = $markdown ? $markdown($new) : $new;
}
}
}
/**
* Get the entry in the revisions list after $vid.
*
* @param array $node_revisions
* Array of node revision IDs in descending order.
* @param int $vid
* Version ID to look for.
*
* @return boolean|integer
* Returns FALSE if $vid is the last entry.
*/
function _diff_get_next_vid($node_revisions, $vid) {
$previous = NULL;
foreach ($node_revisions as $revision) {
if ($revision->vid == $vid) {
return ($previous ? $previous->vid : FALSE);
}
$previous = $revision;
}
return FALSE;
}
/**
* Get the entry in the revision list before $vid.
*
* @param array $node_revisions
* Array of node revision IDs in descending order.
* @param integer $vid
* Version ID to look for.
*
* @return boolean|integer
* Returns FALSE if $vid is the first entry.
*/
function _diff_get_previous_vid($node_revisions, $vid) {
$previous = NULL;
foreach ($node_revisions as $revision) {
if ($previous && $previous->vid == $vid) {
return $revision->vid;
}
$previous = $revision;
}
return FALSE;
}
/**
* Helper function to create default 'cols' array for diff table.
*/
function _diff_default_cols() {
return array(
array(
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
),
);
}
/**
* Helper function to create default 'header' array for diff table.
*/
function _diff_default_header($old_header = '', $new_header = '') {
return array(
array(
'data' => $old_header,
'colspan' => 2,
),
array(
'data' => $new_header,
'colspan' => 2,
),
);
}
/**
* Show the inline diff for a given node, vid.
*
* If vid = 0 or no previous vid exists for the given revision returns the
* normally rendered content of the specified revision.
*/
function diff_inline_show($node, $vid = 0, $metadata = TRUE) {
$new_node = $vid ? node_load($node->nid, $vid, TRUE) : clone $node;
node_build_content($new_node);
$new = drupal_render($new_node->content);
$old = $vid ? _diff_get_previous_vid(node_revision_list($node), $vid) : 0;
if ($old) {
$old_node = node_load($node->nid, $old, TRUE);
node_build_content($old_node);
$old = drupal_render($old_node->content);
$output = $metadata ? theme('diff_inline_metadata', array('node' => $new_node)) : '';
$output .= diff_get_inline($old, $new);
return $output;
}
return $new;
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* @file
* Themeable function callbacks for diff.module.
*/
/**
* Theme function to display the revisions formular.
*/
function theme_diff_node_revisions($vars) {
$form = $vars['form'];
$output = '';
// Overview table:
$header = array(
t('Revision'),
array('data' => drupal_render($form['submit']), 'colspan' => 2),
array('data' => t('Operations'), 'colspan' => 2),
);
if (isset($form['info']) && is_array($form['info'])) {
foreach (element_children($form['info']) as $key) {
$row = array();
if (isset($form['operations'][$key][0])) {
// Note: even if the commands for revert and delete are not permitted,
// the array is not empty since we set a dummy in this case.
$row[] = drupal_render($form['info'][$key]);
$row[] = drupal_render($form['diff']['old'][$key]);
$row[] = drupal_render($form['diff']['new'][$key]);
$row[] = drupal_render($form['operations'][$key][0]);
$row[] = drupal_render($form['operations'][$key][1]);
$rows[] = array(
'data' => $row,
'class' => array('diff-revision'),
);
}
else {
// The current revision (no commands to revert or delete).
$row[] = array(
'data' => drupal_render($form['info'][$key]),
'class' => array('revision-current'),
);
$row[] = array(
'data' => drupal_render($form['diff']['old'][$key]),
'class' => array('revision-current'),
);
$row[] = array(
'data' => drupal_render($form['diff']['new'][$key]),
'class' => array('revision-current'),
);
$row[] = array(
'data' => t('current revision'),
'class' => array('revision-current'),
'colspan' => '2',
);
$rows[] = array(
'data' => $row,
'class' => array('error diff-revision'),
);
}
}
}
$output .= theme('table__diff__revisions', array(
'header' => $header,
'rows' => $rows,
'sticky' => FALSE,
'attributes' => array('class' => 'diff-revisions'),
));
$output .= drupal_render_children($form);
return $output;
}
/**
* Theme functions
*/
/**
* Theme function for a header line in the diff.
*/
function theme_diff_header_line($vars) {
return '<strong>' . t('Line @lineno', array('@lineno' => $vars['lineno'])) . '</strong>';
}
/**
* Theme function for a content line in the diff.
*/
function theme_diff_content_line($vars) {
return '<div>' . $vars['line'] . '</div>';
}
/**
* Theme function for an empty line in the diff.
*/
function theme_diff_empty_line($vars) {
return $vars['line'];
}
/**
* Theme function for inline diff form.
*/
function theme_diff_inline_form($vars) {
if ($theme = variable_get('diff_theme', 'default')) {
drupal_add_css(drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css");
}
return drupal_render_children($vars['form']);
}
/**
* Display inline diff metadata.
*/
function theme_diff_inline_metadata($vars) {
if ($theme = variable_get('diff_theme', 'default')) {
drupal_add_css(drupal_get_path('module', 'diff') . "/css/diff.{$theme}.css");
}
$node = $vars['node'];
$output = "<div class='diff-inline-metadata clear-block'>";
$output .= "<div class='diff-inline-byline'>";
$output .= t('Updated by !name on @date', array(
'!name' => theme('username', array('account' => user_load($node->revision_uid))),
'@date' => format_date($node->revision_timestamp, 'small'),
));
$output .= "</div>";
$output .= "<div class='diff-inline-legend clear-block'>";
$output .= "<label>" . t('Legend') . "</label>";
$output .= theme('diff_inline_chunk', array('text' => t('Added'), 'type' => 'add'));
$output .= theme('diff_inline_chunk', array('text' => t('Changed'), 'type' => 'change'));
$output .= theme('diff_inline_chunk', array('text' => t('Deleted'), 'type' => 'delete'));
$output .= "</div>";
$output .= "</div>";
return $output;
}
/**
* Theme a span of changed text in an inline diff display.
*/
function theme_diff_inline_chunk($vars) {
switch ($vars['type']) {
case 'add':
return "<span class='diff-added'>{$vars['text']}</span>";
case 'change':
return "<span class='diff-changed'>{$vars['text']}</span>";
case 'delete':
return "<span class='diff-deleted'>{$vars['text']}</span>";
default:
return $vars['text'];
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* @file
* Builds placeholder replacement tokens for diff-related data.
*/
/**
* Implements hook_token_info().
*/
function diff_token_info() {
$node['diff'] = array(
'name' => t('Latest differences'),
'description' => t('The differences between the current revision and the previous revision of this node.'),
);
$node['diff-markdown'] = array(
'name' => t('Latest differences (marked down)'),
'description' => t('The differences between the current revision and the previous revision of this node, but with a marked-down form of each revision used for comparison.'),
);
return array(
'tokens' => array('node' => $node),
);
}
/**
* Implements hook_tokens().
*/
function diff_tokens($type, $tokens, array $data = array(), array $options = array()) {
$sanitize = !empty($options['sanitize']);
$replacements = array();
if ($type == 'node' && !empty($data['node'])) {
$node = $data['node'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Basic diff standard comparison information.
case 'diff':
case 'diff-markdown':
$revisons = node_revision_list($node);
if (count($revisons) == 1) {
$replacements[$original] = t('(No previous revision available.)');
}
else {
module_load_include('inc', 'diff', 'diff.pages');
$old_vid = _diff_get_previous_vid($revisons, $node->vid);
$state = $name == 'diff' ? 'raw' : 'raw_plain';
$build = diff_diffs_show($node, $old_vid, $node->vid, $state);
unset($build['diff_table']['#rows']['states']);
unset($build['diff_table']['#rows']['navigation']);
unset($build['diff_preview']);
$output = drupal_render_children($build);
$replacements[$original] = $sanitize ? check_plain($output) : $output;
}
break;
}
}
}
return $replacements;
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* @file
* Provide diff field functions for the file module.
*/
/**
* Diff field callback for preloading file entities.
*/
function file_field_diff_view_prepare(&$old_items, &$new_items, $context) {
$fids = array();
foreach (array_merge_recursive($old_items, $new_items) as $info) {
$fids[$info['fid']] = $info['fid'];
}
$files = file_load_multiple($fids);
foreach ($old_items as $delta => $info) {
$old_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
}
foreach ($new_items as $delta => $info) {
$new_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
}
}
/**
* Diff field callback for parsing file field comparative values.
*/
function file_field_diff_view($items, $context) {
$field = $context['field'];
$instance = $context['instance'];
$settings = $context['settings'];
$diff_items = array();
foreach ($items as $delta => $item) {
if (isset($item['file'])) {
$output = array();
// We populate as much as possible to allow the best flexability in any
// string overrides.
$t_args = array();
foreach ($item as $key => $value) {
if (is_scalar($value)) {
$t_args['!' . $key] = $value;
}
}
// Some states do not have the file properties in the item, so put these
// out of the main file object.
if (!empty($item['file'])) {
$file_values = (array) $item['file'];
foreach ($file_values as $key => $value) {
if (is_scalar($value) && !isset($t_args['!' . $key])) {
$t_args['!' . $key] = $value;
}
}
}
$output['file'] = t('File: !filename', $t_args);
if ($settings['compare_description_field'] && !empty($instance['settings']['description_field'])) {
if (!empty($item['description'])) {
$output['description'] = t('Description: !description', $t_args);
}
}
if ($settings['show_id']) {
$output['fid'] = t('File ID: !fid', $t_args);
}
if ($settings['compare_display_field'] && !empty($field['settings']['display_field'])) {
$output['display'] = $item['display'] ? t('Displayed') : t('Hidden');
}
$diff_items[$delta] = implode('; ', $output);
}
}
return $diff_items;
}
/**
* Provide default field comparison options.
*/
function file_field_diff_default_options($field_type) {
return array(
'show_id' => 0,
'compare_display_field' => 0,
'compare_description_field' => 0,
);
}
/**
* Provide a form for setting the field comparison options.
*/
function file_field_diff_options_form($field_type, $settings) {
$options_form = array();
$options_form['show_id'] = array(
'#type' => 'checkbox',
'#title' => t('Show file ID'),
'#default_value' => $settings['show_id'],
);
$options_form['compare_description_field'] = array(
'#type' => 'checkbox',
'#title' => t('Compare description field'),
'#default_value' => $settings['compare_description_field'],
'#description' => t('This is only used if the "Enable <em>Description</em> field" is checked in the instance settings.'),
);
$options_form['compare_display_field'] = array(
'#type' => 'checkbox',
'#title' => t('Compare display state field'),
'#default_value' => $settings['compare_display_field'],
'#description' => t('This is only used if the "Enable <em>Display</em> field" is checked in the field settings.'),
);
return $options_form;
}

View File

@@ -0,0 +1,112 @@
<?php
/**
* @file
* Provide diff field functions for the image module.
*/
/**
* Diff field callback for preloading the image file entities.
*/
function image_field_diff_view_prepare(&$old_items, &$new_items, $context) {
$fids = array();
foreach (array_merge_recursive($old_items, $new_items) as $info) {
$fids[$info['fid']] = $info['fid'];
}
$files = file_load_multiple($fids);
foreach ($old_items as $delta => $info) {
$old_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
}
foreach ($new_items as $delta => $info) {
$new_items[$delta]['file'] = isset($files[$info['fid']]) ? $files[$info['fid']] : NULL;
}
}
/**
* Diff field callback for parsing image field comparative values.
*/
function image_field_diff_view($items, $context) {
$instance = $context['instance'];
$settings = $context['settings'];
$diff_items = array();
foreach ($items as $delta => $item) {
if (isset($item['file'])) {
$output = array();
// We populate as much as possible to allow the best flexability in any
// string overrides.
$t_args = array();
foreach ($item as $key => $value) {
if (is_scalar($value)) {
$t_args['!' . $key] = $value;
}
}
// Some states do not have the file properties in the item, so put these
// out of the main file object.
if (!empty($item['file'])) {
$file_values = (array) $item['file'];
foreach ($file_values as $key => $value) {
if (is_scalar($value) && !isset($t_args['!' . $key])) {
$t_args['!' . $key] = $value;
}
}
}
$output[] = t('Image: !filename', $t_args);
if ($settings['compare_alt_field'] && !empty($instance['settings']['alt_field'])) {
if (!empty($item['alt'])) {
$output[] = t('Alt: !alt', $t_args);
}
}
if ($settings['compare_title_field'] && !empty($instance['settings']['title_field'])) {
if (!empty($item['title'])) {
$output[] = t('Title: !title', $t_args);
}
}
if ($settings['show_id']) {
$output[] = t('File ID: !fid', $t_args);
}
$diff_items[$delta] = implode('; ', $output);
}
}
return $diff_items;
}
/**
* Provide default field comparison options.
*/
function image_field_diff_default_options($field_type) {
return array(
'show_id' => 0,
'compare_alt_field' => 0,
'compare_title_field' => 0,
);
}
/**
* Provide a form for setting the field comparison options.
*/
function image_field_diff_options_form($field_type, $settings) {
$options_form = array();
$options_form['show_id'] = array(
'#type' => 'checkbox',
'#title' => t('Show image ID'),
'#default_value' => $settings['show_id'],
);
$options_form['compare_alt_field'] = array(
'#type' => 'checkbox',
'#title' => t('Compare <em>Alt</em> field'),
'#default_value' => $settings['compare_alt_field'],
'#description' => t('This is only used if the "Enable <em>Alt</em> field" is checked in the instance settings.'),
);
$options_form['compare_title_field'] = array(
'#type' => 'checkbox',
'#title' => t('Compare <em>Title</em> field'),
'#default_value' => $settings['compare_title_field'],
'#description' => t('This is only used if the "Enable <em>Title</em> field" is checked in the instance settings.'),
);
return $options_form;
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* @file
* Provide diff field functions for the List module.
*/
/**
* Diff field callback for parsing list field comparative values.
*/
function list_field_diff_view($items, $context) {
$field = $context['field'];
$instance = $context['instance'];
$settings = $context['settings'];
$diff_items = array();
$allowed_values = list_allowed_values($field, $instance, $context['entity_type'], $context['entity']);
foreach ($items as $delta => $item) {
// Fairly complex condition to prevent duplicate "key (key)" type rendering.
if (isset($allowed_values[$item['value']]) &&
$allowed_values[$item['value']] != $item['value'] &&
strlen($allowed_values[$item['value']])) {
switch ($settings['compare']) {
case 'both':
$diff_items[$delta] = $allowed_values[$item['value']] . ' (' . $item['value'] . ')';
break;
case 'label':
$diff_items[$delta] = $allowed_values[$item['value']];
break;
default:
$diff_items[$delta] = $item['value'];
break;
}
}
else {
// If no match was found for the label, fall back to the key.
$diff_items[$delta] = $item['value'];
}
}
return $diff_items;
}
/**
* Provide default field comparison options.
*/
function list_field_diff_default_options($field_type) {
return array(
'compare' => 'label',
);
}
/**
* Provide a form for setting the field comparison options.
*/
function list_field_diff_options_form($field_type, $settings) {
$options_form = array();
$options_form['compare'] = array(
'#type' => 'radios',
'#title' => t('Comparison method'),
'#options' => array(
'label' => t('Label'),
'key' => t('Key'),
'both' => t('Label (key)'),
),
'#default_value' => $settings['compare'],
);
return $options_form;
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* @file
* Provide diff functions for the node module.
*/
/**
* Implements hook_entity_diff().
*
* This function compares core node properties. This is currently limited to:
* - title: The title of the node.
*
* @param object $old_node
* The older node revision.
* @param object $new_node
* The newer node revision.
* @param array $context
* An associative array containing:
* - entity_type: The entity type; e.g., 'node' or 'user'.
* - old_entity: The older entity.
* - new_entity: The newer entity.
* - view_mode: The view mode to use. Defaults to FALSE. If no view mode is
* given, the recommended fallback view mode is 'default'.
* - states: An array of view states. These could be one of:
* - raw: The raw value of the diff, the classic 7.x-2.x view.
* - rendered: The rendered HTML as determined by the view mode. Only
* return markup for this state if the value is normally shown
* by this view mode. The user will most likely be able to see
* the raw or raw_plain state, so this is optional.
*
* The rendering state is a work in progress.
*
* Conditionally, you can get these states, but setting these will override
* the user selectable markdown method.
*
* - raw_plain: As raw, but text should be markdowned.
* - rendered_plain: As rendered, but text should be markdowned.
*
* @return array
* An associative array of values keyed by the entity property.
*
* This is effectively an unnested Form API-like structure.
*
* States are returned as follows:
*
* $results['line'] = array(
* '#name' => t('Line'),
* '#states' => array(
* 'raw' => array(
* '#old' => '<p class="line">This was the old line number [tag].</p>',
* '#new' => '<p class="line">This is the new line [tag].</p>',
* ),
* 'rendered' => array(
* '#old' => '<p class="line">This was the old line number <span class="line-number">57</span>.</p>',
* '#new' => '<p class="line">This is the new line <span class="line-number">57</span>.</p>',
* ),
* ),
* );
*
* For backwards compatability, no changes are required to support states,
* but it is recommended to provide a better UI for end users.
*
* For example, the following example is equivalent to returning the raw
* state from the example above.
*
* $results['line'] = array(
* '#name' => t('Line'),
* '#old' => '<p class="line">This was the old line number [tag].</p>',
* '#new' => '<p class="line">This is the new line [tag].</p>',
* );
*/
function node_entity_diff($old_node, $new_node, $context) {
$result = array();
if ($context['entity_type'] == 'node') {
$type = node_type_get_type($new_node);
$result['title'] = array(
'#name' => $type->title_label,
'#states' => array(),
'#weight' => -5,
'#settings' => array(
// Global setting - 'diff_show_header_' . $entity_type
'show_header' => variable_get('diff_show_header_node', 1),
),
);
foreach ($context['states'] as $state) {
switch ($state) {
case 'rendered':
$result['title']['#states'][$state] = array(
'#old' => l($old_node->title, 'node/' . $old_node->title),
'#new' => l($new_node->title, 'node/' . $new_node->title),
);
break;
// We specify a default so that the title is allows compared.
case 'raw':
default:
$result['title']['#states'][$state] = array(
'#old' => array($old_node->title),
'#new' => array($new_node->title),
);
break;
}
}
}
return $result;
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* @file
* Provide diff field functions for the Number module.
*/
/**
* Diff field callback for parsing number field comparative values.
*/
function number_field_diff_view($items, $context) {
$diff_items = array();
foreach ($items as $delta => $item) {
$diff_items[$delta] = $item['value'];
}
return $diff_items;
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* @file
* Implements pusdeo-hook hook_field_diff_view() for the Taxonomy module.
*/
/**
* Diff field callback for preloading term entities.
*/
function taxonomy_field_diff_view_prepare(&$old_items, &$new_items, $context) {
$tids = array();
foreach (array_merge_recursive($old_items, $new_items) as $info) {
$tids[$info['tid']] = $info['tid'];
}
$terms = taxonomy_term_load_multiple($tids);
foreach ($old_items as $delta => $info) {
$old_items[$delta]['term'] = isset($terms[$info['tid']]) ? $terms[$info['tid']] : NULL;
}
foreach ($new_items as $delta => $info) {
$new_items[$delta]['term'] = isset($terms[$info['tid']]) ? $terms[$info['tid']] : NULL;
}
}
/**
* Diff field callback for parsing term field comparative values.
*/
function taxonomy_field_diff_view($items, $context) {
$settings = $context['settings'];
$instance = $context['instance'];
$diff_items = array();
foreach ($items as $delta => $item) {
if (!empty($item['tid'])) {
if ($item['tid'] == 'autocreate') {
$diff_items[$delta] = t('!term_name (new)', array('!term_name' => $item['name']));
}
elseif (empty($item['term'])) {
$diff_items[$delta] = t('Missing term reference (!tid)', array('!tid' => $item['tid']));
}
else {
$output = array();
$output['name'] = $item['term']->name;
if ($settings['show_id']) {
$output['tid'] = t('Term ID: !tid', array('!tid' => $item['term']->tid));
}
$diff_items[$delta] = implode('; ', $output);
}
}
}
if (!empty($settings['sort']) && !empty($diff_items)) {
if ($settings['sort'] == DIFF_SORT_VALUE || $instance['widget']['type'] == 'taxonomy_autocomplete') {
usort($diff_items, 'uasort_taxonomy_field_diff_terms');
}
}
return $diff_items;
}
/**
* Helper function for sorting terms.
*/
function uasort_taxonomy_field_diff_terms($a, $b) {
// We need to use t() to test for string overrides.
$missing_text = t('Missing term reference');
$a_missing = strpos($a, $missing_text) === 0;
$b_missing = strpos($b, $missing_text) === 0;
if ($a_missing && $b_missing) {
return strnatcmp($a, $b);
}
elseif ($a_missing xor $b_missing) {
return $a_missing ? 100 : -100;
}
return strnatcmp($a, $b);
}
/**
* Provide default field comparison options.
*/
function taxonomy_field_diff_default_options($field_type) {
return array(
'show_id' => 0,
'sort' => DIFF_SORT_CUSTOM,
);
}
/**
* Provide a form for setting the field comparison options.
*/
function taxonomy_field_diff_options_form($field_type, $settings) {
$options_form = array();
$options_form['show_id'] = array(
'#type' => 'checkbox',
'#title' => t('Show term ID'),
'#default_value' => $settings['show_id'],
);
$options_form['sort'] = array(
'#type' => 'radios',
'#title' => t('Sort'),
'#options' => array(
DIFF_SORT_NONE => t('No sort'),
DIFF_SORT_VALUE => t('Sort'),
DIFF_SORT_CUSTOM => t('Sort if free tagging field'),
),
'#default_value' => $settings['sort'],
);
return $options_form;
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* @file
* Provide diff field functions for the Text module.
*/
/**
* Diff field callback for parsing text field comparative values.
*/
function text_field_diff_view($items, $context) {
$field = $context['field'];
$instance = $context['instance'];
$settings = $context['settings'];
$diff_items = array();
foreach ($items as $delta => $item) {
$diff_items[$delta] = array();
// Compute the format for appending to the label.
$format_text = '';
if ($instance['settings']['text_processing'] && $settings['compare_format']) {
$format_id = empty($item['format']) ? filter_fallback_format() : $item['format'];
if ($format = filter_format_load($format_id)) {
$format_text = $format->name;
}
else {
$format_text = t('Missing format !format', array('!format' => $format_id));
}
}
// Compare the summary fields.
$summary = $field['type'] == 'text_with_summary' && $settings['compare_summary'];
if ($summary) {
// Allow users to optionally clean system specific characters.
if (empty($item['summary'])) {
$diff_items[$delta][] = t('Summary field is empty.');
}
else {
if ($format_text) {
$diff_items[$delta][] = t('Summary (!text_format):', array('!text_format' => $format_text));
}
else {
$diff_items[$delta][] = t('Summary:');
}
$diff_items[$delta][] = diff_normalise_text($item['summary']);
}
}
// Only show label if field has summary displayed.
if ($summary) {
if ($format_text) {
$diff_items[$delta][] = t('Content (!text_format):', array('!text_format' => $format_text));
}
else {
$diff_items[$delta][] = t('Content:');
}
}
// Allow users to optionally clean system specific characters.
$diff_items[$delta][] = diff_normalise_text($item['value']);
// If no summary, append the format selection to the bottom of the screen.
// This prevents adding the "Content (format)" label.
if ($format_text && !$summary) {
$diff_items[$delta][] = t('Text format: !text_format', array('!text_format' => $format_text));
}
$diff_items[$delta] = $diff_items[$delta];
}
return $diff_items;
}
/**
* Provide default field comparison options.
*/
function text_field_diff_default_options($field_type) {
// Overrides the global 'markdown' setting which does not escape HTML.
$settings = array(
'compare_format' => 0,
'markdown' => 'drupal_html_to_text',
'line_counter' => '',
);
if ($field_type == 'text_with_summary') {
$settings += array(
'compare_summary' => 0,
);
}
return $settings;
}
/**
* Provide a form for setting the field comparison options.
*/
function text_field_diff_options_form($field_type, $settings) {
$options_form = array();
$options_form['compare_format'] = array(
'#type' => 'checkbox',
'#title' => t('Compare format'),
'#default_value' => $settings['compare_format'],
'#description' => t('This is only used if the "Text processing" instance settings are set to <em>Filtered text (user selects text format)</em>.'),
);
if ($field_type == 'text_with_summary') {
$options_form['compare_summary'] = array(
'#type' => 'checkbox',
'#title' => t('Compare summary separately'),
'#default_value' => $settings['compare_summary'],
'#description' => t('This is only used if the "Summary input" option is checked in the instance settings.'),
);
}
return $options_form;
}

View File

@@ -0,0 +1,58 @@
(function ($) {
Drupal.behaviors.diffRevisions = {
attach: function (context, settings) {
var $rows = $('table.diff-revisions tbody tr');
function updateDiffRadios() {
var newTd = false;
var oldTd = false;
if (!$rows.length) {
return true;
}
$rows.removeClass('selected').each(function() {
var $row = $(this);
$row.removeClass('selected');
var $inputs = $row.find('input[type="radio"]');
var $oldRadio = $inputs.filter('[name="old"]').eq(0);
var $newRadio = $inputs.filter('[name="new"]').eq(0);
if (!$oldRadio.length || !$newRadio.length) {
return true;
}
if ($oldRadio.attr('checked')) {
oldTd = true;
$row.addClass('selected');
$oldRadio.css('visibility', 'visible');
$newRadio.css('visibility', 'hidden');
} else if ($newRadio.attr('checked')) {
newTd = true;
$row.addClass('selected');
$oldRadio.css('visibility', 'hidden');
$newRadio.css('visibility', 'visible');
} else {
if (Drupal.settings.diffRevisionRadios == 'linear') {
if (newTd && oldTd) {
$oldRadio.css('visibility', 'visible');
$newRadio.css('visibility', 'hidden');
} else if (newTd) {
$newRadio.css('visibility', 'visible');
$oldRadio.css('visibility', 'visible');
} else {
$newRadio.css('visibility', 'visible');
$oldRadio.css('visibility', 'hidden');
}
} else {
$newRadio.css('visibility', 'visible');
$oldRadio.css('visibility', 'visible');
}
}
});
return true;
}
if (Drupal.settings.diffRevisionRadios) {
$rows.find('input[name="new"], input[name="old"]').click(updateDiffRadios);
updateDiffRadios();
}
}
};
})(jQuery);

View File

@@ -0,0 +1,136 @@
Diff module - http://drupal.org/project/diff
============================================
Diff enhances usage of node revisions by adding the following features:
- Diff between node revisions on the 'Revisions' tab to view all the changes
between any two revisions of a node.
- Highlight changes inline while viewing a node to quickly see color-coded
additions, changes, and deletions.
- Preview changes as a diff before updating a node.
It is also an API to compare any entities although this functionality is not
exposed by the core Diff module.
REQUIREMENTS
------------
Drupal 7.x
INSTALLATION
------------
1. Place the Diff module into your modules directory.
This is normally the "sites/all/modules" directory.
2. Go to admin/build/modules. Enable the module.
The Diff modules is found in the Other section.
Read more about installing modules at http://drupal.org/node/70151
See the configuration section below.
UPGRADING
---------
Any updates should be automatic. Just remember to run update.php!
CONFIGURATION
-------------
Unlike the earlier version, the module now has a lot of configurable settings.
Global settings can be found under Configuration > Content > Diff
i.e. http://www.example.com/admin/config/content/diff
Entity specific settings would be listed under the entities settings. This
module only handles Node revisioning functionality, and these are detailed
below.
1) Node revisioning settings
Diff needs to be configured to be used with specific node types on your site.
To enable any of Diff's options on a content type's settings page.
e.g. http://www.example.com/admin/structure/types/manage/page
a) Diff options
Under "Compare revisions", enable the settings that you want;
i) "Show View changes button on node edit form" adds a new "Preview" like
submit button to node editing pages. This shows a diff preview.
ii) "Enable the Revisions page for this content type" adds the revisioning
tab to content. This allows users to compare between various revisions
that they have access to.
iii) "Standard comparison preview" option allows you to control how the most
current revision is show on the revision comparision page.
b) Publishing options
It is strongly advised that you also enable the automatic creation of
revisions on any content types you want to use this with. If you do not do
this, chances are there will be limited revisioning information available to
compare.
Under "Publishing options", enable "Create new revision".
2) Field revisioning settings
Global settings per field type can be found here:
http://www.example.com/admin/config/content/diff/fields
"Show field title" toggles field title visibility on the comparison page.
"Markdown callback" is the callback used to render the field when viewing the
page in the "Marked down" page view.
"Line counter" is an optional. This shows the approximate line number where
the change occurred. This is an approximate counter only.
Other fields add additional settings here.
3) Entity revisioning settings
Global configurable settings limited to node entities.
a) Show entity label header
This provides a field like title for the entity label field.
i.e. For nodes, this provides a header for the node's title.
b) Treat diff pages as administrative
By default, the revisioning pages are administrative, i.e. they will use the
administration theme. You can block this by unchecking this option.
4) Global settings
A small number of new features have been added to the 7.x-3.x branch, these
include the ability to change the leading and trailing lines in the comparison,
a new CSS theme for the diff pages, new JScript options for the revisioning
selection form and options to help prevent cross operating systems in relation
to line endings.
http://www.example.com/admin/config/content/diff
Technical
---------
- Diff compares the raw data, not the filtered output, making it easier to see
changes to HTML entities, etc.
- The diff engine itself is a GPL'ed php diff engine from phpwiki.
API
---
See diff.api.php
Maintainers
-----------
- realityloop (Brian Gilbert)
- Alan D. (Alan Davison)
- dww (Derek Wright)
- moshe (Moshe Weitzman)
- rötzi (Julian)
- yhahn (Young Hahn)