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,39 @@
CHANGELOG for Panels 3.0 for Drupal 7
Panels 7.x-3.0-dev
==================
#1025716: Panels fields broken in last update.
#1056464 by EclipseGc: Fix broken delete statement in mini panel delete.
#954324 by EclipseGc: Fix broken delete statement in mini panel uninstall.
#1008120: "classes" not passing through to rounded shadow pane stylizer style, preventing style from working.
Panels 7.x-3.0-alpha2 (10-Jan-2011)
=====================
#920266 by dereine: Typo in cache.inc caused wsods in rare circumstances.
#879482 by mvc: Ensure Panels and CTools module files are loaded during update. Apparently could cause WSOD if not.
#917614 by jskulski: IPE broken in IE.
#906520: Improve CSS for rounded shadow boxes on IE7.
#932632 by mikeytown2: Fix notice in .install file.
#927840: Add clear-block to dashboard HTML to ensure themes do not do weird things to it.
#869766: Fix occasional problem with flexible layout pushing the entire layout to the left in certain fixed-width only configurations.
#949310: E_STRICT violation on declaration of render_pane() method of display renderers.
#940002: Custom style modal was broken.
#953484: Panes were not properly using classes array.
#941532: panel nodes had some serious problems do to hook_node_*.
#954324: Mini panels failed during uninstall.
#827628: "Add content" dialog could lose content with the same title as other content.
Fix the naked style to support content that needs to be rendered.
#958072: Fix panels_node_node_access to not throw warnings on menu access tests.
#965286: Panel node update was trying to use db_insert() instead of db_update().
#964334: Panels breaks views' row styles with the panels fields style.
#941802: Fix radio layout butotn.
#980696 by das-peter: Update calls to drupal_set_html_head() to D7.
#961662 by Nick Lewis: Account for pager info in simple caching.
#980870 by das-peter: CSS handling during caching broken.
#970076: Remove old hook_update functions.
#978768 by linclark: Fix notice with panel fields.
#977296: Regions with _ such as with two column bricks would not save content.
#987902: Fix flexible layout splitter resize brokenness.
#967734 by das-peter and intoxination: Upgrade node_get_types() in wizard.
#1020824: Finish fixing node template page wizard.
Fix landing page wizard.

View File

@@ -0,0 +1,91 @@
Known Issue http://drupal.org/node/191771
'Node' panes can have two titles or have two title areas.
Cause:
Content that comes into a pane is already formatted, and this happens
in theme('node'). theme('node') assumes it will be printing a title
most of the time. However, Panels wants the titles of panes to be
consistent, so it removes the title from the node to prevent your
node.tpl.php from printing it. The result is often an empty h2 which
has odd effects.
Solution:
Add an if statement to your node.tpl.php to prevent printing that h2
if $node->title is empty.
Known Issue http://drupal.org/node/186454
Internet Explorer is really bad about making the rightmost panel
fall beneath the others.
Cause:
Internet explorer calculates margins and padding differntly from
everyone else, and this makes it entirely too easy for widths
to add up to greater than the amount of allotted space, despite
using percentage widths.
Solution:
There are two solutions to this problem:
1) In your theme, try to eliminate padding from the the <div>
that directly contains your content; you can do this by
adding an empty <div> inside it that surrounds the content
and very specifically is set to margin: 0 and padding: 0
2) if that doesn't work, override the widths of the panel-panel
divs and reduce them by 1 or 2%; usually this will give IE
enough space to quit pushing things around.
Known Issue http://drupal.org/node/154351
TinyMCE, FCKEditor and other wysiwyg editors really blow up on Panels
content editing.
Cause:
The modal dialogs that Panels uses are very particular about javascript
and these editors are too much for them. Also, these editors get
cranky about complicated forms with several text areas.
Solution:
Disable these editors on all of your panels admin pages. The important
URLs are admin/panels/* and panels/ajax/*. More details instructions
may follow if someone familiar with these systems submits a patch at
the above drupal.org URL.
Known Issue http://drupal.org/node/180650
The rounded corners style shows up as just a small graphic rather than
a full box around the panels as it shoujld.
Cause:
The rounded corners CSS relies on the ID for the panel, but the ID is
optional.
Solution:
Make sure your panel has an ID of some sort. With mini panels there is
no easy workaround as mini panels currently do not have IDs of their
own.
Known Issue http://drupal.org/node/165745
You see a message similar to this:
Table 'drupal.panels_info' doesn't exist query: SELECT * FROM panels_info
WHERE path = 'front_page_new' in...
The important piece of information is 'panels_info'.
Cause:
The Meta Tags module (also known as nodewords.module) directly reads the
the panels tables and modifies its forms to add the tags. Unfortunately
for this module, Panels has changed *greatly* in the leap from 1.0 to
2.0 and the tables aren't the same. However, the nodewords module doesn't
yet know this. Look in the nodewords issue queue for panels patches and
you should find something.
Known Issue http://drupal.org/node/153399
The drag and drop content UI doesn't seem to work at all under Safari.
Cause:
Safari 2 has some serious problems with the javascript code.
Solution:
Upgrade to Safari 3 if possible. If not, use an an alternative browser
such as Firefox or Opera.
Known Issue http://drupal.org/node/207859
When using the secure pages module, the Panels administrative UI gives
unhelpful "An error occurred" popups when trying to add or edit content.
Cause:
The secure pages module tries to move the entire administrative section
of the site to HTTPS, but Panels' AJAX calls are using a path that
secure pages doesn't know about. When trying to make non-secure ajax calls
from a secure page, the browser denies the call.
Solution:
The solution is to simply add panels/* to your Secure Pages configuration.

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,6 @@
Welcome to Panels 3.
A little documentation should go here, but Panels 3 is alsoi a beast - you're
best off checking the online handbook on Drupal.org, or this issue:
http://drupal.org/node/887560.

View File

@@ -0,0 +1,22 @@
Upgrading from Panels-6.x-3.x to Panels-7.x-3.x
- Style and layout plugins may no longer be registered by a central hook.
Only the plugin directories method may be used.
- Layout 'panels function' is now 'regions function'.
- Layout 'panels' key is now 'regions'.
- panels_get_pane_title() deprecated.
- panels_plugin_get_function() deprecated.
- panels_required_context removed. These were deprecated long ago and
existed only to prevent crashes.
- panels_optional_context removed.
- $renderer->plugins['layout']['panels'] changed to $renderer->plugin['layout']['regions']
- display_renderer class is now in 'renderer', not 'handler'.

View File

@@ -0,0 +1,62 @@
.dashboard-entry .dashboard-link {
font-size: 120%;
font-weight: bold;
}
.dashboard-entry .dashboard-icon img {
vertical-align: middle;
}
.dashboard-title {
font-weight: bold;
font-size: 140%;
margin-bottom: .5em;
}
.dashboard-link form input {
margin: 0;
}
.dashboard-link form select {
margin: 0;
}
.dashboard-left {
width: 47%;
float: left;
}
.dashboard-right {
margin-left: 2em;
width: 47%;
float: left;
}
.dashboard-question {
margin-top: 14em;
padding: 1em;
text-align: center;
}
.dashboard-content table {
margin: 0;
width: 100%;
}
.dashboard-content {
padding: 0 1em;
}
.panels-dashboard .links {
text-align: right;
}
.dashboard-pages .page-manager-page-operations {
text-align: right;
}
.dashboard-block {
padding-bottom: 1em;
border-bottom: 1px dotted #ddd;
margin-bottom: 1em;
}

View File

@@ -0,0 +1,50 @@
div.panel-pane div.admin-links {
font-size: xx-small;
margin-right: 1em;
}
div.panel-pane div.admin-links li a {
color: #ccc;
}
div.panel-pane div.admin-links li {
padding-bottom: 2px;
background: white;
z-index: 201;
}
div.panel-pane div.admin-links:hover a,
div.panel-pane div.admin-links-hover a {
color: #000;
}
div.panel-pane div.admin-links a:before {
content: "[";
}
div.panel-pane div.admin-links a:after {
content: "]";
}
div.panel-pane div.panel-hide {
display: none;
}
/** For IE we add the class via js; for other browsers we rely on :hover **/
div.panel-pane div.panel-hide-hover,
div.panel-pane:hover div.panel-hide {
display: block;
position: absolute;
z-index: 200;
margin-top: -1.5em;
}
div.panel-pane div.node {
margin: 0;
padding: 0;
}
div.panel-pane div.feed a {
float: right;
}

View File

@@ -0,0 +1,165 @@
.layout-link {
float: left;
padding: 1em;
width: 125px;
height: 160px;
}
.layout-link img {
margin-left: auto;
margin-right: auto;
}
/* general style for the layout-icon */
.layout-icon .caption {
width: 90px;
margin-bottom: 1em;
}
/* styles for the basic panel-%implementation% edit page */
.layout-container,
.right-container {
float: right;
padding: 0 0 0 .5em;
margin: 0;
width: 48.5%;
}
.info-container,
.left-container {
padding-right: .5em;
width: 48.5%;
}
.right-container fieldset,
.left-container fieldset,
.layout-container fieldset {
margin-top: 0;
}
.layout-container .form-item {
margin: 0;
}
.layout-container .form-submit {
margin-top: 1em;
}
.layout-container .layout-icon,
.left-container .layout-icon {
float: right;
margin-left: .5em;
}
.content-list ol {
padding-left: 0;
list-style-position: inside;
}
.content-list dt {
font-weight: bold;
}
.content-list dd {
margin-left: 2em;
}
/* styles for the choose layout page */
.panels-layouts-checkboxes .form-checkboxes .form-item,
#panels-choose-layout .form-type-radio,
.panels-choose-layout .form-type-radio {
float: left;
margin-right: .5em;
width: 90px;
}
.panels-layouts-checkboxes .form-checkboxes .form-item .layout-icon,
#panels-choose-layout .form-type-radio .form-item .layout-icon,
.panels-choose-layout .form-type-radio .form-item .layout-icon {
float: none;
height: 11em;
width: 90px;
}
.panels-layouts-checkboxes .form-checkboxes .option input,
#panels-choose-layout .form-type-radio input,
.panels-choose-layout .form-type-radio input {
width: 50px;
display: block;
text-align: center;
}
.panels-layouts-checkboxes .form-submit,
#panels-choose-layout .form-submit {
clear: left;
}
.panels-layouts-checkboxes .panels-layout-list label,
#panels-choose-layout .panels-layout-list label {
width: 300px;
float: left;
clear: left;
background: url(../images/go-right.png) right no-repeat;
margin-right: 20px;
}
.panels-layouts-checkboxes .panels-layouts-category {
font-weight: bold;
width: 100%;
float: left;
}
.panels-layouts-checkboxes .description {
clear: left;
}
.change-layout-display .layout-icon {
float: left;
}
.change-layout-display > img {
padding: 25px 25px 25px 0;
float: left;
}
table .operation {
text-align: right;
padding-right: 6px;
}
table .argument-operation input {
padding: 0;
margin: 0;
position: relative;
top: 3px;
}
.panels-admin-view {
padding: 1em;
border: 1px dotted black;
margin-bottom: 1em;
}
tr.changed td {
background-color: #FFFFDD !important;
}
tr.changed td span.star {
font-weight: bold;
color: #E09010;
}
td select {
margin: 0;
padding: 0;
}
.panels-style-settings,
.panels-style-settings-box,
#panels-style-setting {
float: left;
}
.panels-style-settings-box .form-item {
margin: 0 1em 0 0;
}

View File

@@ -0,0 +1,670 @@
#panels-dnd-main {
margin: 0.5em 0;
}
#panels-dnd-main div.panel-region {
padding: 0 0 .5em 0;
border: 1px dashed #ddd;
background: #f8f8f8;
-webkit-border-radius: 0.333em;
-moz-border-radius: 0.333em;
}
#panels-dnd-main div.panel-region h2.label {
color: #555;
text-shadow: #fff 1px 1px 1px;
text-align: center;
font-size: 13pt;
margin: 0 0 .5em 0;
padding-right: 16px;
vertical-align: middle;
}
#panels-dnd-main div.panel-region .pane-add {
float: left;
margin: 2px;
background: #999;
border: 1px solid #fff;
}
#panels-dnd-main div.panel-region .pane-add-link {
position: relative;
display: block;
width: 16px;
height: 16px;
float: left;
margin: 2px 2px 2px 4px;
}
div.panels-set-title-hide .panels-set-title {
display: none !important;
}
/* Add Icon */
#panels-dnd-main div.panel-region .pane-add-link {
}
#panels-dnd-main div.panel-region .pane-add-link img {
display: none;
}
#panels-dnd-main div.panel-region .pane-add-link a.ctools-dropdown-image-link {
border: none;
width: 16px;
height: 18px;
float: left;
background: url('../images/sprite.png') no-repeat 0 -1178px;
/* background: url('../images/sprite.png') no-repeat -166px -582px; */
}
.panel-portlet {
padding: 0em;
background: #ffffff;
border: 1px solid #bbb;
}
div.panels-set-title-hide .panel-pane-is-title {
border: 1px solid #bbb;
}
.panel-pane-is-title {
border: 2px solid #777;
}
/* Cog Icon */
.panel-portlet .buttons a img {
display: none;
margin: 0;
}
.panel-portlet .buttons a.ctools-dropdown-image-link {
border: none;
width: 16px;
height: 16px;
margin: 0 5px 0 0;
float: none;
display: block;
background: url('../images/sprite.png') no-repeat 0 -1178px;
}
#panels-dnd-main .panel-pane,
#panels-dnd-main .helperclass {
margin: .5em;
}
#panels-dnd-main-form .inline-icon-help {
vertical-align: middle;
margin: 2px 1px;
}
.panel-pane.hidden-pane {
background: #f8f8f8;
}
.panel-portlet .pane-content {
margin: .5em 0 .5em 0;
padding: 0 .25em 0 .25em;
display: none; /* initially hidden */
}
.panel-portlet .grab-title {
width: 100%;
height: 20px;
margin: 0 0 0.5em 0;
overflow: hidden;
background: #b3b3b3;
color: #fff;
text-shadow: #555 1px 1px 1px;
border-color: #999;
font-weight: bold;
}
.panel-portlet .grabber {
cursor: move;
background: #b3b3b3 url('../images/bg-shade-medium.png') repeat-x 0 100%;
}
.panel-portlet.hidden-pane .grab-title {
background-color: #888;
}
.panel-portlet .changed div.grab-title {
background-color: #FFFFDD !important;
border-bottom: 1px solid #3D9CD7 !important;
color: black !important;
}
.panel-portlet .changed.hidden-pane div.grab-title {
background-color: #B4B488 !important;
border-bottom: 1px solid #3D9CD7 !important;
}
.panel-portlet .changed div.grab-title span.star {
font-weight: bold;
color: #E09010;
}
.panel-portlet .grabber:hover {
color: #fff;
background-color: #2F78A5;
}
.panel-portlet.hidden-pane .grab-title:hover {
background-color: #666;
}
.panel-portlet .grabber:active {
background-color: red;
}
.panel-portlet .grabber:hover,
.panel-portlet .grabber:active {
background: #858585 url('../images/bg-shade-dark.png') repeat-x 0 100%;
color: #fff;
text-shadow: #333 1px 1px 1px;
border-color: #858585;
}
.panel-portlet .grab-title .text {
margin-left: 3px;
font-size: 90%;
line-height: 20px;
}
.panel-portlet .buttons {
float: right;
padding: 0;
margin: 0;
}
.panel-portlet .buttons input {
margin: 0;
padding: 0;
display: inline;
}
.panel-portlet .buttons a img {
margin: 2px 1px;
}
.panel-portlet .pane-title {
font-size:110%;
cursor: pointer;
}
.panel-portlet .panel-pane-collapsible {
margin: 0;
padding: 0;
}
.panel-portlet .toggle {
float: left;
width: 21px;
height: 21px;
cursor: pointer;
background: url('../images/sky.png') no-repeat 6px -245px;
}
.panel-portlet .toggle-collapsed {
background: url('../images/sky.png') no-repeat 6px -1021px;
}
/* CSS to guide a user to a place to drop */
#panels-dnd-main .helperclass {
border: 1px dashed red;
}
#panels-dnd-main .hoverclass {
border: 1px solid red !important;
}
/* CSS for an area if something can be dropped in it */
.panels-modal-content {
background: #fff;
color: #000;
padding: 0;
margin: 2px;
border: 1px solid #000;
width: 600px;
text-align: left;
}
.panels-modal-content .modal-title {
font-size: 120%;
font-weight: bold;
color: white;
overflow: hidden;
white-space: nowrap;
}
.panels-modal-content .modal-header {
background-color: #2385c2;
padding: 0 .25em 0 1em;
}
.panels-modal-content .modal-header a {
color: white;
float: right;
}
.panels-modal-content .modal-content {
padding: 0 1em;
overflow: auto;
width: 575px;
height: 400px;
}
.panels-modal-content .modal-form {
}
.panels-modal-content .form-checkboxes .form-item {
float: left;
width: 24%;
}
.panels-hidden,
.panels-js-only {
display: none;
}
a.close {
color: white;
}
a.close:hover {
text-decoration: none;
}
a.close img {
position: relative;
top: 1px;
}
.panels-section-title {
clear: left;
border-bottom: 1px solid #ddf;
margin-bottom: .5em;
text-align: left;
}
.panels-section-decorator {
}
.panels-add-content-modal .panels-modal-add-category {
display: block;
border-bottom: 1px solid white;
padding-left: .5em;
margin-left: -2px;
position: relative;
}
.panels-add-content-modal .panels-modal-add-category.active {
background: url(../images/arrow-active.png) center right no-repeat white;
border-right: none;
}
.panels-add-content-modal {
background: url(../images/bg-content-modal.png);
height: 100%;
margin: -1em;
padding-top: 1em;
padding-left: 175px;
position: relative;
}
.panels-section-columns {
height: 100%;
overflow: auto;
}
.panels-section-column {
width: 48%;
float: left;
}
.panels-section-column .inside {
padding: 0 1em;
}
.panels-section-column-categories {
width: 173px;
margin-left: -173px;
}
.panels-categories-description {
padding: 0 1em;
text-align: center;
vertical-align: center;
}
* html .panels-section-column-categories {
left: 173px;
position: relative;
}
.panels-section-column-categories .panels-categories-box {
border-top: 1px solid white;
margin-bottom: 1em;
}
.panels-section-column-categories .inside {
padding: 0;
}
.panels-section-column-categories .content-type-button {
padding-left: 10px;
}
.panels-modal-add-category {
color: #5b5b5b !important;
font-weight: bold;
line-height: 2em;
}
.panels-section {
margin-bottom: 1em;
}
.panels-section-column .content-type-button {
font-size: 8pt;
line-height: 1em;
overflow: hidden;
text-align: left;
}
.content-type-button img {
border: 2px solid white;
float: left;
}
.content-type-button img:hover {
border: 2px solid blue;
}
.content-type-button div {
width: 85%;
top: -5px;
left: 2px;
float: left;
padding-left: 3px;
padding-top: 5px;
}
#panels-preview .modal-throbber-wrapper {
width: 100%;
text-align: center;
margin-left: auto;
margin-right: auto;
}
/** modal forms CSS **/
.panels-modal-content .form-item label {
width: 8em;
float: left;
}
.panels-modal-content .form-item label.option {
width: auto;
float: none;
}
.panels-modal-content .form-item .description {
clear: left;
}
.panels-modal-content .form-item .description .tips {
margin-left: 2em;
}
.panels-modal-content .no-float .form-item * {
float: none;
}
.panels-modal-content .modal-form .no-float label {
width: auto;
}
.panels-modal-content .modal-form fieldset,
.panels-modal-content .modal-form .form-checkboxes {
clear: left;
}
#edit-configuration-nid {
clear: left;
}
.option-text-aligner .form-item {
float: left;
padding: .25em 1em .25em 0;
margin: 0;
}
.option-text-aligner {
clear: both;
width: 100%;
padding: 0;
margin: 0;
}
#panels-dnd-main div.panel-pane div.ctools-dropdown-container-wrapper {
margin-left: -158px;
margin-top: -4px;
}
/*
html.js div.panels-display-links div.ctools-dropdown-container {
width: 275px;
}
html.js div.panels-display-links div.ctools-dropdown-container ul li li a {
width: 250px;
}
html.js div.panels-display-links div.ctools-dropdown-container ul li a {
width: 270px;
}
*/
#panels-dnd-main .panel-pane .pane-title {
padding: 0.25em 0.5em;
}
#panels-dnd-main .panel-pane .pane-title:after {
font-size: 0.8em;
color: crimson;
letter-spacing: normal;
display: block;
}
#panels-dnd-main .panel-pane.hidden-pane .pane-title:after {
content: " status: hidden";
}
#panels-dnd-main .panel-pane.changed .pane-title:after {
content: " status: changes not saved";
}
#panels-dnd-main .panel-pane.hidden-pane.changed .pane-title:after {
content: " status: hidden & changed";
}
/* @end */
/* @group CTools Dropdown */
#panels-dnd-main .ctools-dropdown a.ctools-dropdown-text-link,
html.js div.panels-display-links a.ctools-dropdown-text-link {
background: url('../images/arrow-down-light.png') 0 3px no-repeat!important;
padding-left: 12px;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container,
html.js div.panels-display-links div.ctools-dropdown-container {
width: 160px!important;
background: #fff url('../images/bg-shade-white-lrg.png') repeat-x 0 100%;
border: solid 1px #ddd!important;
margin: 0!important;
/* padding: 0.5em!important; */
-webkit-border-radius: 0.333em;
-moz-border-radius: 0.333em;
-webkit-box-shadow: 0.333em 0.333em 0.333em rgba(0, 0, 0, 0.25);
font-size: 0.9em;
font-weight: bold;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li,
html.js div.panels-display-links div.ctools-dropdown-container ul li {
text-decoration: none;
padding: 0;
margin: 0;
color: #555!important;
text-shadow: #fff 1px 1px 1px;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li span.text,
html.js div.panels-display-links div.ctools-dropdown-container ul li span.text {
font-style: normal;
color: #000;
font-weight: bold;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li a,
html.js div.panels-display-links div.ctools-dropdown-container ul li a {
color: #555!important;
font-weight: normal;
width: auto;
padding: 0 10px;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li span.panels-text,
html.js div.panels-display-links div.ctools-dropdown-container ul li span.panels-text {
width: auto;
padding: 0 10px;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li .panels-italic,
html.js div.panels-display-links div.ctools-dropdown-container ul li .panels-italic {
font-style: italic;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li span.dropdown-header,
html.js div.panels-display-links div.ctools-dropdown-container ul li span.dropdown-header {
background-color: #fefefe;
padding: 0 10px;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li .panels-sub-menu ul li a,
html.js div.panels-display-links div.ctools-dropdown-container ul li .panels-sub-menu ul li a,
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li .panels-sub-menu span.panels-text,
html.js div.panels-display-links div.ctools-dropdown-container ul li .panels-sub-menu span.panels-text {
width:auto;
padding: 0 20px;
}
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container hr,
html.js div.panels-display-links div.ctools-dropdown-container hr {
border: 0;
color: #ddd;
background-color: #ddd;
height: 1px;
}
/*
html.js #panels-dnd-main div.ctools-dropdown div.ctools-dropdown-container ul li a:hover,
html.js div.panels-display-links div.ctools-dropdown-container ul li a:hover {
background: none!important;
color: #000!important;
}
*/
/* @end */
/* @group Modal */
/* Account for the extra div coming from ctools_modal_form_render() */
div.messages div.messages {
background: none;
border: none;
margin: 0;
padding: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
div.ctools-modal-content .modal-header {
background: #fff url('../images/bg-shade-light.png') repeat-x bottom left;
color: #777;
display: block;
font-weight: 700;
letter-spacing: normal;
padding: 0.25em 1em;
-moz-border-radius-topleft: 0.5em;
-moz-border-radius-topright: 0.5em;
-webkit-border-top-left-radius: 0.5em;
-webkit-border-top-right-radius: 0.5em;
}
div.ctools-modal-content .modal-title {
font-size: 120%;
font-weight: bold;
text-shadow: #fff 1px 1px 1px;
color: #555;
}
div.ctools-modal-content a.close {
color: #666;
font-weight: normal;
padding-left: 1.6em;
background: url('../images/sprite.png') no-repeat -166px -1686px;
}
div.ctools-modal-content a.close img {
display: none;
}
/** modal forms CSS **/
div.ctools-modal-content .form-item label {
width: 100%;
float: none;
clear: both;
}
div.ctools-modal-content .resizable-textarea {
width: 100%;
margin-left: 0;
margin-right: 0;
}
div.ctools-modal-content {
font-size: 12px;
border: solid 1px #ddd;
-webkit-border-radius: 0.5em;
-moz-border-radius: 0.5em;
-webkit-box-shadow: -1em 1em 1em rgba(0, 0, 0, 0.5);
}
#modalBackdrop {
position: fixed!important;
background-color: #000!important;
}
/* @end */
/** Override that obnoxious float on throbber. **/
#panels-dnd-main .progress-disabled {
float: none;
}
#panels-dnd-main .progress-disabled + .ajax-progress {
float: right;
position: relative;
top: -2em;
}
/** Override that obnoxious float on throbber. **/
.panels-categories-box .progress-disabled {
float: none;
}
.panels-categories-box .progress-disabled + .ajax-progress {
float: right;
position: relative;
top: -2em;
margin-bottom: -2em;
}
.panels-section-columns .progress-disabled {
display: block;
float: left;
}
.panels-section-columns .ajax-progress .throbber {
float: right;
position: relative;
top: -2em;
margin-bottom: -2em;
}

View File

@@ -0,0 +1,14 @@
div.panels-page-type-container {
clear: left;
}
div.panels-page-type-container .form-checkboxes .form-item {
float: left;
width: 15em;
}
#panels-page-settings .form-submit {
display: block;
clear: left;
}

View File

@@ -0,0 +1,44 @@
<p>Panels is a pluggable rendering engine. Apart from its datastructure, almost nothing in Panels is hardcoded; every level of its rendering process is controlled by plugins (of the <a href="topic:ctools/plugins">CTools</a> variety), and therefore can be overriden. Some of these plugins' behaviors are obvious from the UI, whereas others are much more under-the-hood.</p>
<p>Most site builders and module authors will find that plugins are the only way they need to interact with Panels to do what they want. However, if you are building whole new UIs/applications that are to be driven by the Panels engine, you'll also need the more traditional API that manages Panels editing and CRUD. Panels Node and Mini Panels are examples of 'applications' in this sense, as is <a href="http://drupal.org/project/og_panels">OG Panels</a>.</p>
<p>This page is divided into two sections, the first dealing with Panels' rendering system & the plugins the drive it, and the second with the requirements for creating a Panels-driven application/UI. The former is a good starting place and should be of interest to anyone wanting to begin working with the Panels API.</p>
<h2>The Panels Rendering System</h2>
<p>Panels' rendering system takes a fully-loaded panels_display object and turns it into HTML. It doesn't care how that object was built or where it comes from - it's just a rendering engine. So long as the calling code provides a well-formed panels_display object, the engine will spit out HTML. Exactly how that process works is determined entirely by plugins.</p>
<p>Panels utilizes eight different types of plugins within its rendering system, four owned by Panels and four by CTools. Let's run through that list.</p>
<dl>
<dt><a href="topic:panels/plugins-layout">Layout plugins</a></dt>
<dd><em>Owned by Panels.</em></dd>
<dd>Layout plugins provide the HTML output skeleton for any panel: they define a set of regions and (optionally) accompanying CSS/JS, very analogous to how themes define block regions. While layout plugins with complex logic are possible (the Flexible layout, for example), most layouts are nothing more than a region list, a tpl.php skeleton, and some CSS.</dd>
<dt><a href="topic:panels/plugins-style">Style plugins</a></dt>
<dd><em>Owned by Panels.</em></dd>
<dd>Style plugins control the markup that wraps both individual panes and whole panel regions. A basic style plugin need not provide more than some basic CSS and a theme function or tpl.php file.</dd>
<dt><a href="topic:ctools/plugins-style-bases">Style Bases</a></dt>
<dd><em>Owned by CTools.</em></dd>
<dd>Panels implements the 'style_bases' plugin type in order to interact with the Stylizer system, which technically lives in CTools, though true 'ownership' is a little blurred. Regardless, Panels uses this plugin to interact with Stylaizer in order to allow the creation of style plugins in the UI.</dd>
<dd>This plugin is probably of interest only to module developers; site builders can safely skip it.</dd>
<dt><a href="topic:ctools/context-context">Context plugins</a></dt>
<dd><em>Owned by CTools.</em></dd>
<dd>Context plugins are abstracted wrappers around bits of data - a node, user, or taxonomy term are easy examples. Panels uses context plugins so that it can work with all these different bits of data without caring about the details of the datastructure itself. Context can be a confusing concept, and you can learn more in the <a href="topic:ctools/context">CTools help</a>.</dd>
<dd>Note that <a href="topic:ctools/context-arguments">argument</a> and <a href="topic:ctools/context-relationships">relationship</a> plugins are technically also used by Panels, but because they're really means for creating more context, they don't get separate entries in this list.</dd>
<dt><a href="topic:ctools/plugins-content">Content Type plugins</a></dt>
<dd><em>Owned by CTools: can utilize context.</em></dd>
<dd>At the heart of the Panels rendering process, content type plugins define the rendering logic for every pane. Offering Panels a renderable chunk from your module means defining one of these. For those accustomed to the block system, they can be thought of as a more verbose block definition, contained neatly inside a single plugin.</dd>
<dd>Content type plugins rely on context to provide them with source data, when necessary; this makes content plugins nice and portable.</dd>
<dt><a href="topic:ctools/context-access">Access plugins</a></dt>
<dd><em>Owned by CTools: can utilize context.</em></dd>
<dd>Access plugins are essentially portable access checking logic, responsible for answering "Yes" or "No" given some contextual data. Panels uses these to define 'visibility rules' on individual panes. The other place these are commonly seen is in defining 'Selection rules' on Page Manager variants.</dd>
<dd>If your module introduces new permissioning concepts or access logic (NOT a basic hook_perm() implementation), you may want to consider also encapsulating that logic in one or more of these plugins. For example, Organic Groups might implement an access plugin to allow/deny based on group membership, or the Date module might implement allow/deny logic based on request time.</dd>
<dt><a href="topic:panels/plugins-cache">Cache plugins</a></dt>
<dd><em>Owned by Panels; should utilize context.</em></dd>
<dd>Panels cache plugins define caching strategies that can be applied to individual panes, or to whole displays. Caching plugins must deal with two basic issues: the cache backend to use for reading/writing, and TTL-managing strategies.</dd>
<dd>TTL strategies can (and should) integrate heavily with the context, as that is where the real differential benefit of Panels-based caching is vs. standard core caching.</dd>
<dt><a href="topic:panels/plugins-renderers">Display Renderers</a></dt>
<dd><em>Owned by Panels: utilizes everything else.</em></dd>
<dd>In the simplest terms, display renderers take a fully-loaded panels_display object and render it. They are very complex, powerful plugins - one could use them to cause Panels to bypass every single other plugin in this list. Fortunately, there is virtually no circumstance under which one would need to implement these plugins in the process of building a single site; only contrib developers working on highly complex use cases are likely to ever need them.</dd>
<dd>In short, each display renderer plugin <strong>is</strong> a self-contained Panels rendering engine. If you want to grok the Panels rendering system, study these.</dd>
</dl>
<h2>Panels-based Applications</h2>
<p>At its core, Panels is a rendering engine with no UI (though it does provide common elements for reuse in admin UIs). So if you're working with Panels via the web UI, it means you're working with a Panels application. The three that ship with this version of Panels are Mini Panels, Panel Nodes, and the integration with CTools' Page Manager.</p>
<p>Panels also implements a task_handler plugin, which is owned by Page Manager in CTools. Task handlers aren't strictly a part of Panels' rendering system itself though (strictly speaking, they wrap it), so we won't cover them here.</p>

View File

View File

@@ -0,0 +1,74 @@
[advanced help settings]
line break = TRUE
[glossary]
title = Panels glossary of terms
weight = 100
[about]
title = About Panels
weight = -100
[common-tasks]
title = Accomplishing common tasks in Panels
weight = -50
[tutorials]
title = Panels tutorials and how-tos
weight = -30
[tutorial-landing]
title = Creating a simple landing page
parent = tutorials
[tutorial-node]
title = Styling a node page
parent = tutorials
[tutorial-vocabulary]
title = Styling a vocabulary
parent = tutorials
[apps]
title = Panels-based applications
parent = api
[panels-pages]
title = Panel pages, via Page Manager
parent = apps
[panels-mini]
title = Mini panels
parent = apps
[panels-node]
title = Panel nodes
parent = apps
[api]
title = Working with the Panels API
weight = 50
[render-api]
title = Panels plugins
parent = api
[plugins-layout]
title = Layout plugins
parent = render-api
weight = -10
[plugins-style]
title = Style plugins
parent = render-api
weight = -7
[plugins-cache]
title = Cache plugins
parent = render-api
weight = -4
[plugins-renderers]
title = Display Renderer plugins
parent = render-api
weight = -1

View File

@@ -0,0 +1,87 @@
<h2>Getting Started:</h2>
<p>Layout plugins are one of the simplest and most powerful sections of the Panels API. There are two different ways that a layout can be implemented via Panels. Panels supports both module and theme implementations of Panels. The module implementation requires that hook_ctools_plugin_directory define the directory in which your layout plugins exist. (This same hook defines the directory for all Panels plugins) Alternately, if you intend on implementing a layout in a theme this can be done primary through the theme's info file. The CTools help does a great job of actually explaining this portion of the API <a href="topic:ctools/plugins-implementing">ctools: plugins</a>.</p>
<p>For purposes of this example, our module name is going to be "layout_sample" and our plugin will be "first_layout".</p>
<h2>Directory Structure:</h2>
<p>We're going to assume that you've laid your directory structure out very similarly to how panels does it. Something like this is rather likely:
<pre>layout_sample
layout_sample.info
layout_sample.module
plugins
layouts
first_layout
first_layout.css
first_layout.inc
first_layout.png
layout-sample-first-layout.tpl.php</pre>
The name of our .inc file is going to be the key to the entire layout plugin.</p>
<h2>The .module File:</h2>
<p>First, declare where your custom layouts reside by implementing the CTools hook <code>hook_ctools_plugin_directory()</code>:
<pre>
/**
* Implements hook_ctools_plugin_directory().
*/
function layout_sample_ctools_plugin_directory($module, $plugin) {
if ($module == 'panels' && $plugin == 'layouts') {
return 'plugins/layouts';
}
}
</pre>
<h2>The .inc File:</h2>
<p>We will start with the first_layout.inc file as it's the most important file we're dealing with here. First_layout.inc will look similar to the following:
<pre>
$plugin = array(
'title' => t('First Layout'),
'icon' => 'first_layout.png',
'theme' => 'layout_sample_first_layout',
'css' => 'first_layout.css',
'panels' => array(
'main' => t('Main region'),
'right' => t('Right region'),
),
);
</pre>
The include file defines all the other files that our layout will utilize in order to be truly useful. The array is fairly self explanitory but for the sake of specificity:
<ol>
<li><strong>Title:</strong><br />The title of our layout. (Utilized within the panels administration screens)</li>
<li><strong>Icon:</strong><br />The graphical representation of our layout. (Utilized within the panels administration screens)</li>
<li><strong>Theme:</strong><br />The template file of our layout. (Sharp eyed readers will note that the theme definition utilizes underscores instead of dashes, and does not have ".tpl.php" after it. This is refering to the layout-sample-first-layout.tpl.php file all the same, it is simply how the naming convention works. Utilize dashes in the tpl file name and underscores when refering to it in your include file.)</li>
<li><strong>CSS:</strong><br />The css file to be utilized for our layout. (Utilized within the panels administration screens, AND when viewing the actual panel itself.)</li>
<li><strong>Panels:</strong><br />Defines all the various regions within your panel. This will be further utilized within our tpl.php file.</li>
</ol>
There are many additional properties that can be added to the include file. For purposes of this document we'll also make mention of the 'admin css' property. 'Admin css' is especially useful when utilizing a fixed width layout with fixed with panel regions. This can break under most administrative circumstances, and panels provides you with the ability to give an additional css layout for the administrative section. It's a simple nicety and looks like this:
<pre>
$plugin = array(
'title' => t('First Layout'),
'icon' => 'first_layout.png',
'theme' => 'layout_sample_first_layout',
'css' => 'first_layout.css',
'admin css' => 'first_layout_admin.css',
'panels' => array(
'main' => t('Main region'),
'right' => t('Right region'),
),
);
</pre>
</p>
<h2>The tpl.php File:</h2>
<p>The tpl.php file is very similar to any other template file within drupal. The difference here is that we're being passed an array of regions through $content, and we also have a css id available to us for the entire panel in the form of $css_id. The template is very straight forward and will look similar to the following:
<pre>&lt;div class="panel-display panel-stacked-twothirds-onethird clearfix" &lt;?php if (!empty($css_id)) { print "id=\"$css_id\""; } ?&gt;&gt;
&lt;div class="panel-panel panel-col-first panel-region-main"&gt;
&lt;div class="inside"&gt;&lt;?php print $content['main']; ?&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="panel-panel panel-col-last panel-region-right"&gt;
&lt;div class="inside"&gt;&lt;?php print $content['right']; ?&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</pre>
This is simply an example of what the html could look like. You can alter an update this html to fit your own needs.
</p>
<h2>The Other Files:</h2>
<p>The css and png files are as simple as any other css or png file you've ever utilized. Panels provides some images for its graphical representations of its layouts. I would heavily encourage you to modify these to suit your needs. The CSS files (admin and non) will be included at the appropriate times. Simply set them up to fit your purposes. If you're utilizing fixed width panel regions it's probably smart to provide an admin css file as well with your panel layout.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

View File

@@ -0,0 +1,83 @@
<?php
/**
* @file
* Contains template preprocess files for the add content modal themes.
*/
/**
* Preprocess the primary entry level theme.
*/
function template_preprocess_panels_add_content_modal(&$vars) {
// Process the list of categories.
foreach ($vars['categories'] as $key => $category_info) {
// 'root' category is actually displayed under the categories, so
// skip it.
if ($key == 'root') {
continue;
}
$class = 'panels-modal-add-category';
if ($key == $vars['category']) {
$class .= ' active';
}
$url = $vars['renderer']->get_url('select-content', $vars['region'], $key);
$vars['categories_array'][] = ctools_ajax_text_button($category_info['title'], $url, '', $class);
}
// Now render the top level buttons (aka the root category) if any.
$vars['root_content'] = '';
if (!empty($vars['categories']['root'])) {
foreach ($vars['categories']['root']['content'] as $content_type) {
$vars['root_content'] .= theme('panels_add_content_link', array('renderer' => $vars['renderer'], 'region' => $vars['region'], 'content_type' => $content_type));
}
}
}
/**
* Process the panels add content modal.
*
* This is run here so that preprocess can make changes before links are
* actually rendered.
*/
function template_process_panels_add_content_modal(&$vars) {
$content = !empty($vars['categories'][$vars['category']]['content']) ? $vars['categories'][$vars['category']]['content'] : array();
// If no category is selected or the category is empty or our special empty
// category render a 'header' that will appear instead of the columns.
if (empty($vars['category']) || empty($content) || $vars['category'] == 'root') {
$vars['header'] = t('Content options are divided by category. Please select a category from the left to proceed.');
}
else {
$titles = array_keys($content);
natcasesort($titles);
// This will default to 2 columns in the theme definition but could be
// changed by a preprocess. Ensure there is at least one column.
$columns = max(1, $vars['column_count']);
$vars['columns'] = array_fill(1, $columns, '');
$col_size = count($titles) / $columns;
$count = 0;
foreach ($titles as $title) {
$which = floor($count++ / $col_size) + 1;
$vars['columns'][$which] .= theme('panels_add_content_link', array('renderer' => $vars['renderer'], 'region' => $vars['region'], 'content_type' => $content[$title]));
}
}
$vars['messages'] = theme('status_messages');
}
/**
* Preprocess the add content link used in the modal.
*/
function template_preprocess_panels_add_content_link(&$vars) {
$vars['title'] = filter_xss_admin($vars['content_type']['title']);
$vars['description'] = isset($vars['content_type']['description']) ? $vars['content_type']['description'] : $vars['title'];
$vars['icon'] = ctools_content_admin_icon($vars['content_type']);
$vars['url'] = $vars['renderer']->get_url('add-pane', $vars['region'], $vars['content_type']['type_name'], $vars['content_type']['subtype_name']);
$vars['image_button'] = ctools_ajax_image_button($vars['icon'], $vars['url'], $vars['description'], 'panels-modal-add-config');
$vars['text_button'] = ctools_ajax_text_button($vars['title'], $vars['url'], $vars['description'], 'panels-modal-add-config');
}

View File

@@ -0,0 +1,205 @@
<?php
/**
* @file callbacks.inc
* Minor menu callbacks for Panels helpers.
*/
/**
* A central administrative page for Panels.
*/
function panels_admin_page() {
// @todo I think this should return a renderable array somehow?
return theme('panels_dashboard');
}
function panels_dashboard_final_blocks(&$vars) {
// Add in links for missing modules that we still want to mention:
if (empty($vars['links']['page_manager'])) {
$vars['links']['page_manager'] = array(
'weight' => -100,
'title' => t('Panel page'),
'description' => '<em>' . t('You must activate the page manager module for this functionality.') . '</em>',
);
}
if (empty($vars['links']['panels_mini'])) {
$vars['links']['panels_mini'] = array(
'title' => t('Mini panel'),
'description' => '<em>' . t('You must activate the Mini panels module for this functionality.') . '</em>',
);
}
if (empty($vars['links']['panels_node'])) {
$vars['links']['panels_mini'] = array(
'title' => t('Panel node'),
'description' => '<em>' . t('You must activate the panel node module for this functionality.') . '</em>',
);
}
}
/**
* Implementation of hook_panels_dashboard_blocks().
*
* Adds page information to the Panels dashboard.
*/
function panels_panels_dashboard_blocks(&$vars) {
$vars['links']['panels_layout'] = array(
'title' => l(t('Custom layout'), 'admin/structure/panels/layouts/add'),
'description' => t('Custom layouts can add more, site-specific layouts that you can use in your panels.'),
);
// Load all mini panels and their displays.
ctools_include('export');
$items = ctools_export_crud_load_all('panels_layout');
$count = 0;
$rows = array();
foreach ($items as $item) {
$rows[] = array(
check_plain($item->admin_title),
array(
'data' => l(t('Edit'), "admin/structure/panels/layouts/list/$item->name/edit"),
'class' => 'links',
),
);
// Only show 10.
if (++$count >= 10) {
break;
}
}
if ($rows) {
$content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
}
else {
$content = '<p>' . t('There are no custom layouts.') . '</p>';
}
$vars['blocks']['panels_layout'] = array(
'title' => t('Manage custom layouts'),
'link' => l(t('Go to list'), 'admin/structure/panels/layouts'),
'content' => $content,
'class' => 'dashboard-layouts',
'section' => 'right',
);
}
function template_preprocess_panels_dashboard(&$vars) {
ctools_add_css('panels-dashboard', 'panels');
ctools_include('plugins');
$vars['image_path'] = ctools_image_path('', 'panels');
$vars['links'] = array();
$vars['blocks'] = array();
foreach (module_implements('panels_dashboard_blocks') as $module) {
$function = $module . '_panels_dashboard_blocks';
$function($vars);
}
// Add in any default links for modules that are not active
panels_dashboard_final_blocks($vars);
// If page manager module is enabled, add a very low eight block to
// list the page wizards.
if (module_exists('page_manager')) {
$vars['blocks']['wizards'] = array(
'weight' => -101,
'section' => 'right',
'title' => t('Page wizards'),
'content' => '',
'class' => 'dashboard-wizards',
);
ctools_include('page-wizard');
$plugins = page_manager_get_page_wizards();
uasort($plugins, 'ctools_plugin_sort');
foreach ($plugins as $id => $plugin) {
if (isset($plugin['type']) && $plugin['type'] == 'panels') {
$link = array(
'title' => l($plugin['title'], 'admin/structure/pages/wizard/' . $id),
'description' => $plugin['description'],
);
$vars['blocks']['wizards']['content'] .= theme('panels_dashboard_link', array('link' => $link));
}
}
}
uasort($vars['links'], 'ctools_plugin_sort');
$vars['blocks']['links'] = array(
'weight' => -100,
'section' => 'left',
'title' => t('Create new') . '...',
'content' => '',
'class' => 'dashboard-create',
);
// Turn the links into a block
foreach ($vars['links'] as $link) {
$vars['blocks']['links']['content'] .= theme('panels_dashboard_link', array('link' => $link));
}
uasort($vars['blocks'], 'ctools_plugin_sort');
$vars['left'] = '';
$vars['right'] = '';
// Render all the blocks
foreach ($vars['blocks'] as $block) {
$section = !empty($block['section']) ? $block['section'] : 'left';
$vars[$section] .= theme('panels_dashboard_block', array('block' => $block));
}
}
function panels_admin_settings_page() {
$form = array();
if (module_exists('page_manager')) {
foreach (page_manager_get_tasks() as $task) {
if ($function = ctools_plugin_get_function($task, 'admin settings')) {
$function($form);
}
}
}
ctools_include('content');
foreach (ctools_get_content_types() as $content) {
if ($function = ctools_plugin_get_function($content, 'admin settings')) {
$function($form);
}
}
ctools_include('plugins', 'panels');
$pipelines = panels_get_renderer_pipelines();
$options = array();
foreach ($pipelines as $key => $value) {
$options[$key] = $value->admin_title;
}
if (count($options) > 1) {
$form['panels_renderer_default'] = array(
'#type' => 'select',
'#title' => t('Default renderer'),
'#options' => $options,
'#default_value' => variable_get('panels_renderer_default', 'standard'),
'#description' => t('The default renderer for new panel pages.'),
);
}
if (empty($form)) {
return array('#value' => t('There are currently no settings to change, but additional plugins or modules may provide them in the future.'));
}
return system_settings_form($form);
}
/**
* Settings for panel contexts created by the page manager.
*/
function panels_admin_panel_context_page() {
ctools_include('common', 'panels');
return drupal_get_form('panels_common_settings', 'panels_page');
}

View File

@@ -0,0 +1,614 @@
<?php
/**
* @file
* Functions used by more than one panels client module.
*/
/**
* Class definition for the allowed layouts governing structure.
*
* @ingroup mainapi
*
* This class is designed to handle panels allowed layouts data from start to finish, and sees
* action at two times:\n
* - When a client module wants to generate a form allowing an admin to create or edit a set
* of allowed layouts. In this case, either a new panels_allowed_layouts object is created
* or one is retrieved from storage and panels_allowed_layouts::set_allowed() is called to
* generate the allowed layouts form. \n
* - When a client module is calling panels_edit_layout(), a saved instantiation of this object
* can be called up and passed in to the fourth parameter, and only the allowed layouts saved
* in that object will be displayed on the form. \n
* Because the panels API does not impose a data structure on the allowed_layouts data, client
* modules can create as many of these objects as they want, and organize them around any concept:
* node types, date published, author roles...anything.
*
* To call the settings form, instantiate this class - or, if your client module's needs are
* heavy-duty, extend this class and instantiate your subclass - assign values to any relevant
* desired members, and call panels_allowed_layouts::set_allowed(). See the documentation on
* that method for a sample implementation.
*
* Note that when unserializing saved tokens of this class, you must
* run panels_load_include('common') before unserializing in order to ensure
* that the object is properly loaded.
*
* Client modules extending this class should implement a save() method and use it for
* their custom data storage routine. You'll need to rewrite other class methods if
* you choose to go another route.
*
* @see panels_edit_layout()
* @see _panels_edit_layout()
*
*/
class panels_allowed_layouts {
/**
* Specifies whether newly-added layouts (as in, new .inc files) should be automatically
* allowed (TRUE) or disallowed (FALSE) for $this. Defaults to TRUE, which is more
* permissive but less of an administrative hassle if/when you add new layouts. Note
* that this parameter will be derived from $allowed_layouts if a value is passed in.
*/
var $allow_new = TRUE;
/**
* Optional member. If provided, the Panels API will generate a drupal variable using
* variable_set($module_name . 'allowed_layouts', serialize($this)), thereby handling the
* storage of this object entirely within the Panels API. This object will be
* called and rebuilt by panels_edit_layout() if the same $module_name string is passed in
* for the $allowed_types parameter. \n
* This is primarily intended for convenience - client modules doing heavy-duty implementations
* of the Panels API will probably want to create their own storage method.
* @see panels_edit_layout()
*/
var $module_name = NULL;
/**
* An associative array of all available layouts, keyed by layout name (as defined
* in the corresponding layout plugin definition), with value = 1 if the layout is
* allowed, and value = 0 if the layout is not allowed.
* Calling array_filter(panels_allowed_layouts::$allowed_layout_settings) will return an associative array
* containing only the allowed layouts, and wrapping that in array_keys() will
* return an indexed version of that array.
*/
var $allowed_layout_settings = array();
/**
* Hack-imitation of D6's $form_state. Used by the panels_common_set_allowed_types()
* form to indicate whether the returned value is in its 'render', 'failed-validate',
* or 'submit' stage.
*/
var $form_state;
/**
* Constructor function; loads the $allowed_layout_settings array with initial values according
* to $start_allowed
*
* @param bool $start_allowed
* $start_allowed determines whether all available layouts will be marked
* as allowed or not allowed on the initial call to panels_allowed_layouts::set_allowed()
*
*/
function panels_allowed_layouts($start_allowed = TRUE) {
// TODO would be nice if there was a way to just fetch the names easily
foreach ($this->list_layouts() as $layout_name) {
$this->allowed_layout_settings[$layout_name] = $start_allowed ? 1 : 0;
}
}
/**
* Manage panels_common_set_allowed_layouts(), the FAPI code for selecting allowed layouts.
*
* MAKE SURE to set panels_allowed_layouts::allow_new before calling this method. If you want the panels API
* to handle saving these allowed layout settings, panels_allowed_layouts::module_name must also be set.
*
* Below is a sample implementation; refer to the rest of the class documentation to understand all the
* specific pieces. Values that are intended to be replaced are wrapped with <>.
*
* \n @code
* function docdemo_allowed_layouts() {
* ctools_include('common', 'panels');
* if (!is_a($allowed_layouts = unserialize(variable_get('panels_common_allowed_layouts', serialize(''))), 'panels_allowed_layouts')) {
* $allowed_layouts = new panels_allowed_layouts();
* $allowed_layouts->allow_new = TRUE;
* $allowed_layouts->module_name = '<client_module_name>';
* }
* $result = $allowed_layouts->set_allowed('<Desired client module form title>');
* if (in_array($allowed_layouts->form_state, array('failed-validate', 'render'))) {
* return $result;
* }
* elseif ($allowed_layouts->form_state == 'submit') {
* drupal_goto('</path/to/desired/redirect>');
* }
* }
* @endcode \n
*
* If $allowed_layouts->form_state == 'failed-validate' || 'render', then you'll need to return
* $result as it contains the structured form HTML generated by drupal_render_form() and is ready
* to be passed through index.php's call to theme('page', ...).
*
* However, if $allowed_layouts->form_state == 'submit', then the form has been submitted and we should
* react. It's really up to your client module how you handle the rest; panels_allowed_layouts::save() (or
* panels_allowed_layouts::api_save(), if that's the route you're going) will have already been called,
* so if those methods handle your save routine, then all there is left to do is handle redirects, if you
* want. The current implementation of the allowed layouts form currently never redirects, so it's up to
* you to control where the user ends up next.
*
* @param string $title
* Used to set the title of the allowed layouts form. If no value is given, defaults to
* 'Panels: Allowed Layouts'.
*
* @return mixed $result
* - On the first passthrough when the form is being rendered, $result is the form's structured
* HTML, ready to be pushed to the screen with a call to theme('page', ...).
* - A successful second passthrough indicates a successful submit, and
* $result === panels_allowed_layouts::allowed_layout_settings. Returning it is simply for convenience.
*/
function set_allowed($title = 'Panels: Allowed Layouts') {
$this->sync_with_available();
$form_id = 'panels_common_set_allowed_layouts';
// TODO switch to drupal_build_form(); need to pass by ref
$form = drupal_retrieve_form($form_id, $this, $title);
if ($result = drupal_process_form($form_id, $form)) {
// successful submit
$this->form_state = 'submit';
return $result;
}
$this->form_state = isset($_POST['op']) ? 'failed-validate' : 'render';
$result = drupal_render_form($form_id, $form);
return $result;
}
/**
* Checks for newly-added layouts and deleted layouts. If any are found, updates panels_allowed_layouts::allowed_layout_settings;
* new additions are made according to panels_allowed_layouts::allow_new, while deletions are unset().
*
* Note that any changes made by this function are not saved in any permanent location.
*/
function sync_with_available() {
$layouts = $this->list_layouts();
foreach (array_diff($layouts, array_keys($this->allowed_layout_settings)) as $new_layout) {
$this->allowed_layout_settings[$new_layout] = $this->allow_new ? 1 : 0;
}
foreach (array_diff(array_keys($this->allowed_layout_settings), $layouts) as $deleted_layout) {
unset($this->allowed_layout_settings[$deleted_layout]);
}
}
/**
* Use panels_allowed_layouts::module_name to generate a variable for variable_set(), in which
* a serialized version of $this will be stored.
*
* Does nothing if panels_allowed_layouts::module_name is not set.
*
* IMPORTANT NOTE: if you use variable_get() in a custom client module save() method, you MUST
* wrap $this in serialize(), then unserialize() what you get from variable_get(). Failure to
* do so will result in an incomplete object. The following code will work:
* @code
* $allowed_layouts = unserialize(variable_get('your_variable_name', serialize(''));
* @endcode
*
* If you don't serialize the second parameter of variable_get() and the variable name you provide
* can't be found, an E_STRICT warning will be generated for trying to unserialize an entity
* that has not been serialized.
*
*/
function save() {
if (!is_null($this->module_name)) {
variable_set($this->module_name . "_allowed_layouts", serialize($this));
}
}
/**
* Snag a list of the current layouts for internal use.
*
* Data is not saved in a class member in order to ensure that it's
* fresh.
*
* @return array $layouts
* An indexed array of the system names for all currently available layouts.
*/
function list_layouts() {
static $layouts = array();
if (empty($layouts)) {
ctools_include('plugins', 'panels');
$layouts = array_keys(panels_get_layouts());
}
return $layouts;
}
}
/**
* A common settings page for Panels modules, because this code is relevant to
* any modules that don't already have special requirements.
*/
function panels_common_settings($form, &$form_state, $module_name = 'panels_common') {
ctools_include('plugins', 'panels');
ctools_include('content');
$content_types = ctools_get_content_types();
$skip = FALSE;
$default_types = variable_get($module_name . '_default', NULL);
if (!isset($default_types)) {
$default_types = array('other' => TRUE);
$skip = TRUE;
}
foreach ($content_types as $id => $info) {
if (empty($info['single'])) {
$default_options[$id] = t('New @s', array('@s' => $info['title']));
if ($skip) {
$default_types[$id] = TRUE;
}
}
}
$default_options['other'] = t('New content of other types');
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
);
$form['common'] = array(
'#type' => 'fieldset',
'#title' => t('New content behavior'),
'#group' => 'additional_settings',
'#weight' => -10,
);
$form['common']['panels_common_default'] = array(
'#type' => 'checkboxes',
'#description' => t('Select the default behavior of new content added to the system. If checked, new content will automatically be immediately available to be added to Panels pages. If not checked, new content will not be available until specifically allowed here.'),
'#options' => $default_options,
'#default_value' => array_keys(array_filter($default_types)),
);
$form_state['skip'] = $skip;
if ($skip) {
$form['markup'] = array('#value' => t('<p>Click Submit to be presented with a complete list of available content types set to the defaults you selected.</p>'));
}
else {
// Rebuild the entire list, setting appropriately from defaults. Give
// each type its own checkboxes set unless it's 'single' in which
// case it can go into our fake other set.
$available_content_types = ctools_content_get_all_types();
$allowed_content_types = variable_get($module_name . '_allowed_types', array());
foreach ($available_content_types as $id => $types) {
foreach ($types as $type => $info) {
$key = $id . '-' . $type;
$checkboxes = empty($content_types[$id]['single']) ? $id : 'other';
$options[$checkboxes][$key] = $info['title'];
if (!isset($allowed_content_types[$key])) {
$allowed[$checkboxes][$key] = isset($default_types[$id]) ? $default_types[$id] : $default_types['other'];
}
else {
$allowed[$checkboxes][$key] = $allowed_content_types[$key];
}
}
}
$form['content_types'] = array(
'#tree' => TRUE,
);
// cheat a bit
$content_types['other'] = array('title' => t('Other'), 'weight' => 10);
foreach ($content_types as $id => $info) {
if (isset($allowed[$id])) {
$form['content_types'][$id] = array(
'#type' => 'fieldset',
'#group' => 'additional_settings',
'#title' => t('Allowed @s content', array('@s' => $info['title'])),
);
$form['content_types'][$id]['options'] = array(
'#prefix' => '<div class="panels-page-type-container">',
'#suffix' => '</div>',
'#type' => 'checkboxes',
'#options' => $options[$id],
'#default_value' => array_keys(array_filter($allowed[$id])),
'#checkall' => TRUE,
);
}
}
}
// Layout selection.
panels_common_allowed_layouts_form($form, $form_state, $module_name);
$form['allowed'] = array(
'#type' => 'value',
'#value' => isset($allowed) ? array_keys($allowed) : array(),
);
$form['module_name'] = array(
'#type' => 'value',
'#value' => $module_name,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
ctools_add_css('panels_page', 'panels');
return $form;
}
/**
* Submit hook for panels_common_settings
*/
function panels_common_settings_validate($form, &$form_state) {
panels_common_allowed_layouts_form_validate($form, $form_state);
}
/**
* Submit hook for panels_common_settings
*/
function panels_common_settings_submit($form, &$form_state) {
panels_common_allowed_layouts_form_submit($form, $form_state);
$module_name = $form_state['values']['module_name'];
variable_set($module_name . '_default', $form_state['values']['panels_common_default']);
if (!$form_state['skip']) {
// merge the broken apart array neatly back together
$allowed = $form_state['values']['allowed'];
$allowed_content_types = array();
foreach ($form_state['values']['allowed'] as $allowed) {
$allowed_content_types = array_merge($allowed_content_types, $form_state['values']['content_types'][$allowed]['options']);
}
variable_set($module_name . '_allowed_types', $allowed_content_types);
}
drupal_set_message(t('Your changes have been saved.'));
}
/**
* Based upon the settings, get the allowed types for this node.
*/
function panels_common_get_allowed_types($module, $contexts = array(), $has_content = FALSE, $default_defaults = array(), $default_allowed_types = array()) {
// Get a list of all types that are available
$default_types = variable_get($module . '_default', $default_defaults);
$allowed_types = variable_get($module . '_allowed_types', $default_allowed_types);
// By default, if they haven't gone and done the initial setup here,
// let all 'other' types (which will be all types) be available.
if (!isset($default_types['other'])) {
$default_types['other'] = TRUE;
}
ctools_include('content');
$content_types = ctools_content_get_available_types($contexts, $has_content, $allowed_types, $default_types);
return $content_types;
}
/**
* The FAPI code for generating an 'allowed layouts' selection form.
*
* NOTE: Because the Panels API does not guarantee a particular method of storing the data on allowed layouts,
* it is not_possible for the Panels API to implement any checks that determine whether reductions in
* the set of allowed layouts conflict with pre-existing layout selections. $displays in that category
* will continue to function with their current layout as normal until the user/owner/admin attempts
* to change layouts on that display, at which point they will have to select from the new set of
* allowed layouts. If this is not the desired behavior for your client module, it's up to you to
* write a validation routine that determines what should be done with conflicting layouts.
*
* Remember that changing layouts where panes have already been created can result in data loss;
* consult panels_change_layout() to see how the Panels API handles that process. Running
* drupal_execute('panels_change_layout', ...) is one possible starting point.
*
* @ingroup forms
*
* @param array $allowed_layouts
* The set of allowed layouts that should be used as the default values
* for this form. If none is provided, then by default no layouts will be restricted.
*/
function panels_common_allowed_layouts_form(&$form, &$form_state, $module_name) {
// Fetch our allowed layouts from variables.
$allowed_layouts = panels_common_get_allowed_layout_object($module_name);
$layouts = panels_get_layouts();
foreach ($layouts as $id => $layout) {
$options[$id] = panels_print_layout_icon($id, $layout, check_plain($layout['title']));
}
$form_state['allowed_layouts'] = &$allowed_layouts;
ctools_add_js('layout', 'panels');
$form['layout_selection'] = array(
'#type' => 'fieldset',
'#title' => t('Select allowed layouts'),
'#group' => 'additional_settings',
'#weight' => 10,
);
$form['layout_selection']['layouts'] = array(
'#type' => 'checkboxes',
'#options' => $options,
'#description' => t('Check the boxes for all layouts you want to allow users choose from when picking a layout. You must allow at least one layout.'),
'#default_value' => array_keys(array_filter($allowed_layouts->allowed_layout_settings)),
'#prefix' => '<div class="clearfix panels-layouts-checkboxes">',
'#suffix' => '</div>',
'#checkall' => TRUE,
);
return $form;
}
function panels_common_allowed_layouts_form_validate($form, &$form_state) {
$selected = array_filter($form_state['values']['layouts']);
if (empty($selected)) {
form_set_error('layouts', 'You must choose at least one layout to allow.');
}
}
function panels_common_allowed_layouts_form_submit($form, &$form_state) {
foreach ($form_state['values']['layouts'] as $layout => $setting) {
$form_state['allowed_layouts']->allowed_layout_settings[$layout] = (bool) $setting;
}
$form_state['allowed_layouts']->save();
}
/**
* Get the allowed layout object for the given module.
*/
function panels_common_get_allowed_layout_object($module_name) {
$allowed_layouts = unserialize(variable_get($module_name . "_allowed_layouts", serialize('')));
// if no parameter was provided, or the variable_get failed
if (!$allowed_layouts) {
// still no dice. simply creates a dummy version where all layouts
// are allowed.
$allowed_layouts = new panels_allowed_layouts();
$allowed_layouts->allow_new = TRUE;
$allowed_layouts->module_name = $module_name;
}
// sanitize allowed layout listing; this is redundant if the
// $allowed_layouts param was null, but the data is cached anyway
$allowed_layouts->sync_with_available();
return $allowed_layouts;
}
/**
* Get the allowed layouts for the given module.
*/
function panels_common_get_allowed_layouts($module_name) {
ctools_include('plugins', 'panels');
$available_layouts = panels_get_layouts();
if (empty($module_name)) {
return $available_layouts;
}
else if (is_object($module_name)) {
$allowed_layouts = $module_name;
}
else {
$allowed_layouts = panels_common_get_allowed_layout_object($module_name);
}
$allowed = array_filter($allowed_layouts->allowed_layout_settings);
$order = array();
foreach ($available_layouts as $name => $plugin) {
if (!empty($allowed[$name])) {
$order[$name] = $plugin['category'] . ':' . $plugin['title'];
}
}
// Sort
$layouts = array();
asort($order);
foreach ($order as $name => $junk) {
$layouts[$name] = $available_layouts[$name];
}
return $layouts;
}
/**
* Create a visible list of content in a display.
* Note that the contexts must be pre-loaded.
*/
function theme_panels_common_content_list($vars) {
$display = $vars['display'];
$layout = panels_get_layout($display->layout);
$content = '<dl class="content-list">';
foreach (panels_get_regions($layout, $display) as $panel_id => $title) {
$content .= "<dt>$title</dt><dd>";
if (!empty($display->panels[$panel_id])) {
$content .= '<ol>';
foreach ($display->panels[$panel_id] as $pid) {
$content .= '<li>' . panels_get_pane_title($display->content[$pid], $display->context) . '</li>';
}
$content .= '</ol>';
}
else {
$content .= t('Empty');
}
$content .= '</dd>';
}
$content .= '</dl>';
return $content;
}
/**
* Print a selector of layouts, each linked to the next step.
*
* Most operations use radio buttons for selecting layouts, but some will
* give each layout as a link that goes to the next step. This function
* makes it easy to simply provide a list of allowed layouts and the base
* path.
*
* One limitation is that it will only append the layout name to the end, so
* if the actual layout name is needed in the middle, that can't happen.
*
* @return
* The rendered output.
*/
function panels_common_print_layout_links($layouts, $base_path, $link_options = array(), $current_layout = NULL) {
$output = '';
$categories = array();
ctools_include('cleanstring');
$default_category = '';
foreach ($layouts as $id => $layout) {
$category = ctools_cleanstring($layout['category']);
$categories[$category] = $layout['category'];
if ($id == $current_layout) {
$default_category = $category;
}
$options[$category][$id] = panels_print_layout_link($id, $layout, $base_path . '/' . $id, $link_options, $current_layout);
}
$form = array();
$form['categories'] = array(
'#title' => t('Category'),
'#type' => 'select',
'#options' => $categories,
'#name' => 'categories',
'#id' => 'edit-categories',
'#value' => $default_category,
'#parents' => array('categories'),
'#access' => (count($categories) > 1) ? TRUE : FALSE,
);
$output .= drupal_render($form);
$output .= '<div class="panels-choose-layout panels-layouts-checkboxes clearfix">';
// We're doing these dependencies completely manualy, which is unusual, but
// the process code only supports doing them in a form.
// @todo modify dependent.inc to make this easier.
$dependencies = array();
foreach ($options as $category => $links) {
$dependencies['panels-layout-category-' . $category] = array(
'values' => array('edit-categories' => array($category)),
'num' => 1,
'type' => 'hide',
);
$output .= '<div id="panels-layout-category-' . $category . '-wrapper">';
$output .= '<div id="panels-layout-category-' . $category . '" class="form-checkboxes clearfix">';
$output .= (count($categories) > 1) ? '<div class="panels-layouts-category">' . $categories[$category] . '</div>' : '';
foreach ($links as $key => $link) {
$output .= $link;
}
$output .= '</div></div>';
}
$output .= '</div>';
ctools_add_js('dependent');
$js['CTools']['dependent'] = $dependencies;
drupal_add_js($js, 'setting');
return $output;
}

View File

@@ -0,0 +1,328 @@
<?php
/*
* @file
* Core Panels API include file containing various display-editing functions.
* This includes all the basic editing forms (content, layout, layout settings)
* as well as the ajax modal forms associated with them.
*/
/**
* Handle calling and processing of the form for editing display content.
*
* Helper function for panels_edit().
*
* @see panels_edit() for details on the various behaviors of this function.
*/
function _panels_edit($display, $destination, $content_types, $title = FALSE) {
$did = $display->did;
if (!$did) {
$display->did = $did = 'new';
}
// Load the display being edited from cache, if possible.
if (!empty($_POST) && is_object($cache = panels_edit_cache_get($did))) {
$display = $cache->display;
}
else {
$cache = panels_edit_cache_get_default($display, $content_types, $title);
}
// Get a renderer.
$renderer = panels_get_renderer_handler('editor', $display);
$renderer->cache = $cache;
$output = $renderer->edit();
if (is_object($output) && $destination) {
return panels_goto($destination);
}
return $output;
}
/**
* Form definition for the panels display editor
*
* No validation function is necessary, as all 'validation' is handled
* either in the lead-up to form rendering (through the selection of
* specified content types) or by the validation functions specific to
* the ajax modals & content types.
*
* @ingroup forms
* @see panels_edit_display_submit()
*/
function panels_edit_display_form($form, &$form_state) {
$display = &$form_state['display'];
$renderer = &$form_state['renderer'];
// Make sure there is a valid cache key.
$cache_key = isset($display->cache_key) ? $display->cache_key : $display->did;
$display->cache_key = $cache_key;
// Annoyingly, theme doesn't have access to form_state so we have to do this.
$form['#display'] = $display;
// The flexible layout maker wants to be able to edit a display without
// actually editing a display, so we provide this 'setting' to allow
// that to go away.
if (empty($form_state['no display settings'])) {
$links = $renderer->get_display_links();
}
else {
$renderer->no_edit_links = TRUE;
$links = '';
}
$form['hide']['display-settings'] = array(
'#markup' => $links,
);
$form += panels_edit_display_settings_form($form, $form_state);
$form['panel'] = array('#tree' => TRUE);
$form['panel']['pane'] = array('#tree' => TRUE);
$form['display'] = array(
'#markup' => $renderer->render(),
);
foreach ($renderer->plugins['layout']['regions'] as $region_id => $title) {
// Make sure we at least have an empty array for all possible locations.
if (!isset($display->panels[$region_id])) {
$display->panels[$region_id] = array();
}
$form['panel']['pane'][$region_id] = array(
// Use 'hidden' instead of 'value' so the js can access it.
'#type' => 'hidden',
'#default_value' => implode(',', (array) $display->panels[$region_id]),
);
}
if (empty($form_state['no buttons'])) {
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#id' => 'panels-dnd-save',
'#submit' => array('panels_edit_display_form_submit'),
'#save-display' => TRUE,
);
$form['buttons']['cancel'] = array(
'#type' => 'submit',
'#value' => t('Cancel'),
);
}
// Build up the preview portion of the form, if necessary.
if (empty($form_state['no preview'])) {
$form['preview'] = array(
'#tree' => TRUE,
'#prefix' => '<h2>' . t('Live preview') . '</h2>' . '<div id="panels-live-preview">',
'#suffix' => '</div>',
);
ctools_context_replace_form($form['preview'], $display->context);
$form['preview']['button'] = array(
'#type' => 'submit',
'#value' => t('Preview'),
'#attributes' => array('class' => array('use-ajax-submit')),
'#id' => 'panels-live-preview-button',
'#submit' => array('panels_edit_display_form_submit', 'panels_edit_display_form_preview'),
);
}
return $form;
}
/**
* Handle form validation of the display content editor.
*/
function panels_edit_display_form_validate($form, &$form_state) {
panels_edit_display_settings_form_validate($form, $form_state);
}
/**
* Handle form submission of the display content editor.
*
* This reads the location of the various panes from the form, which will
* have been modified from the ajax, rearranges them and then saves
* the display.
*/
function panels_edit_display_form_submit($form, &$form_state) {
$display = &$form_state['display'];
$old_content = $display->content;
$display->content = array();
if (!empty($form_state['values']['panel']['pane'])) {
foreach ($form_state['values']['panel']['pane'] as $region_id => $panes) {
$display->panels[$region_id] = array();
if ($panes) {
$pids = explode(',', $panes);
// need to filter the array, b/c passing it in a hidden field can generate trash
foreach (array_filter($pids) as $pid) {
if ($old_content[$pid]) {
$display->panels[$region_id][] = $pid;
$old_content[$pid]->panel = $region_id;
$display->content[$pid] = $old_content[$pid];
// If the panel has region locking, make sure that the region
// the panel is in is applicable. This can happen if the panel
// was moved and then the lock changed and the server didn't
// know.
if (!empty($old_content[$pid]->locks) && $old_content[$pid]->locks['type'] == 'regions') {
$old_content[$pid]->locks['regions'][$region_id] = $region_id;
}
}
}
}
}
}
panels_edit_display_settings_form_submit($form, $form_state);
}
/**
* Submission of the preview button. Render the preview and put it into
* the preview widget area.
*/
function panels_edit_display_form_preview(&$form, &$form_state) {
$display = &$form_state['display'];
ctools_include('ajax');
$display->context = ctools_context_replace_placeholders($display->context, $form_state['values']['preview']);
$display->skip_cache = TRUE;
$output = panels_render_display($display);
// Add any extra CSS that some layouts may have added specifically for this.
if (!empty($display->add_css)) {
$output = "<style type=\"text/css\">\n$display->add_css</style>\n" . $output;
}
$commands = array();
$commands[] = array(
'command' => 'panel_preview',
'output' => $output,
);
print ajax_render($commands);
ajax_footer();
exit;
}
/**
* Form for display settings.
*/
function panels_edit_display_settings_form($form, &$form_state) {
$display = &$form_state['display'];
$layout = panels_get_layout($display->layout);
$form_state['layout'] = $layout;
ctools_include('dependent');
if ($form_state['display_title']) {
$form['display_title'] = array (
'#tree' => TRUE,
);
$form['display_title']['hide_title'] = array(
'#type' => 'select',
'#title' => t('Title type'),
'#default_value' => (int) $display->hide_title,
'#options' => array(
PANELS_TITLE_NONE => t('No title'),
PANELS_TITLE_FIXED => t('Manually set'),
PANELS_TITLE_PANE => t('From pane'),
),
);
$form['display_title']['title'] = array(
'#type' => 'textfield',
'#default_value' => $display->title,
'#title' => t('Title'),
'#description' => t('The title of this panel. If left blank, a default title may be used. Set to No Title if you want the title to actually be blank.'),
'#process' => array('ctools_dependent_process'),
'#dependency' => array('edit-display-title-hide-title' => array(PANELS_TITLE_FIXED)),
'#maxlength' => 255,
);
if (!empty($display->context)) {
$form['display_title']['title']['#description'] .= ' ' . t('You may use substitutions in this title.');
// We have to create a manual fieldset because fieldsets do not support IDs.
// Use 'hidden' instead of 'markup' so that the process will run.
// Add js for collapsible fieldsets manually
// drupal_add_js('misc/form.js');
// drupal_add_js('misc/collapse.js');
// $form['display_title']['contexts_prefix'] = array(
// '#type' => 'hidden',
// '#id' => 'edit-display-substitutions',
// '#prefix' => '<div><fieldset id="edit-display-substitutions" class="collapsed collapsible"><legend>' . t('Substitutions') . '</legend><div class="fieldset-wrapper">',
// '#process' => array('ctools_dependent_process'),
// '#dependency' => array('edit-display-title-hide-title' => array(PANELS_TITLE_FIXED)),
// );
$rows = array();
foreach ($display->context as $context) {
foreach (ctools_context_get_converters('%' . check_plain($context->keyword) . ':', $context) as $keyword => $title) {
$rows[] = array(
check_plain($keyword),
t('@identifier: @title', array('@title' => $title, '@identifier' => $context->identifier)),
);
}
}
$header = array(t('Keyword'), t('Value'));
$form['display_title']['contexts'] = array(
'#type' => 'fieldset',
'#title' => t('Substitutions'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#value' => theme('table', array('header' => $header, 'rows' => $rows)),
// '#process' => array('form_process_fieldset', 'ctools_dependent_process'),
// '#id' => 'edit-display-title-context',
// '#dependency' => array('edit-display-title-hide-title' => array(PANELS_TITLE_FIXED)),
);
// $form['display_title']['contexts_suffix'] = array(
// '#value' => '</div></fieldset></div>',
// );
}
}
// TODO doc the ability to do this as part of the API
if (!empty($layout['settings form']) && function_exists($layout['settings form'])) {
$form['layout_settings'] = $layout['settings form']($display, $layout, $display->layout_settings);
}
$form['layout_settings']['#tree'] = TRUE;
return $form;
}
/**
* Validate the layout settings form.
*/
function panels_edit_display_settings_form_validate($form, &$form_state) {
if ($function = panels_plugin_get_function('layout', $form_state['layout'], 'settings validate')) {
$function($form_state['values']['layout_settings'], $form['layout_settings'], $form_state['display'], $form_state['layout'], $form_state['display']->layout_settings);
}
}
/**
* Store changes from the layout settings form.
*/
function panels_edit_display_settings_form_submit($form, &$form_state) {
$display = &$form_state['display'];
if ($function = panels_plugin_get_function('layout', $form_state['layout'], 'settings submit')) {
$function($form_state['values']['layout_settings'], $display, $form_state['layout'], $display->layout_settings);
}
// Since not all layouts have layout settings, check here in case of notices.
if (isset($form_state['values']['layout_settings'])) {
$display->layout_settings = $form_state['values']['layout_settings'];
}
if (isset($form_state['values']['display_title']['title'])) {
$display->title = $form_state['values']['display_title']['title'];
$display->hide_title = $form_state['values']['display_title']['hide_title'];
}
}

View File

@@ -0,0 +1,333 @@
<?php
/**
* @file
*
* Handle the forms for changing a display's layout.
*/
/**
* Handle calling and processing of the form for editing display layouts.
*
* Helper function for panels_edit_layout().
*
* @see panels_edit_layout() for details on the various behaviors of this function.
*/
function _panels_edit_layout($display, $finish, $destination, $allowed_layouts) {
ctools_include('common', 'panels');
$form_state = array(
'display' => &$display,
'finish' => $finish,
'destination' => $destination,
'allowed_layouts' => $allowed_layouts,
're_render' => FALSE,
'no_redirect' => TRUE,
);
$change_form_state = $form_state;
$change_form = FALSE;
// Examine $_POST to see which form they're currently using.
if (empty($_POST) || empty($_POST['form_id']) || $_POST['form_id'] != 'panels_change_layout') {
$output = drupal_build_form('panels_choose_layout', $form_state);
if (!empty($form_state['executed'])) {
// upon submission go to next form.
$change_form_state['layout'] = $_SESSION['layout'][$display->did] = $form_state['layout'];
$change_form = TRUE;
}
}
else {
$change_form_state['layout'] = $_SESSION['layout'][$display->did];
$change_form = TRUE;
}
if ($change_form) {
$output = drupal_build_form('panels_change_layout', $change_form_state);
if (!empty($change_form_state['executed'])) {
if (isset($change_form_state['back'])) {
unset($_POST);
return _panels_edit_layout($display, $finish, $destination, $allowed_layouts);
}
if (!empty($change_form_state['clicked_button']['#save-display'])) {
drupal_set_message(t('Panel layout has been updated.'));
panels_save_display($display);
}
if ($destination) {
return panels_goto($destination);
}
return $change_form_state['display'];
}
}
return $output;
}
/**
* Form definition for the display layout editor.
*
* @ingroup forms
*/
function panels_choose_layout($form, &$form_state) {
$display = &$form_state['display'];
ctools_include('common', 'panels');
ctools_include('cleanstring');
$layouts = panels_common_get_allowed_layouts($form_state['allowed_layouts']);
$categories = array();
$current = '';
foreach ($layouts as $id => $layout) {
$category = ctools_cleanstring($layout['category']);
// Default category to first in case layout doesn't exist or there isn't one.
if (empty($current)) {
$current = $category;
}
$categories[$category] = $layout['category'];
$options[$category][$id] = panels_print_layout_icon($id, $layout, check_plain($layout['title']));
// Set current category to what is chosen.
if ($id == $display->layout) {
$current = $category;
}
}
ctools_add_js('layout', 'panels');
$form['categories'] = array(
'#title' => t('Category'),
'#type' => 'select',
'#options' => $categories,
'#default_value' => $current,
);
$form['layout'] = array(
'#prefix' => '<div class="panels-choose-layout panels-layouts-checkboxes clearfix">',
'#suffix' => '</div>',
);
// We set up the dependencies manually because these aren't really form
// items. It's possible there's a simpler way to do this, but I could not
// think of one at the time.
$dependencies = array();
foreach ($options as $category => $radios) {
$dependencies['panels-layout-category-' . $category] = array(
'values' => array('edit-categories' => array($category)),
'num' => 1,
'type' => 'hide',
);
$form['layout'][$category] = array(
'#prefix' => '<div id="panels-layout-category-' . $category . '-wrapper"><div id="panels-layout-category-' . $category . '" class="form-checkboxes clearfix"><div class="panels-layouts-category">' . $categories[$category] . '</div>',
'#suffix' => '</div></div>',
);
foreach ($radios as $key => $choice) {
// Set the first available layout as default value.
if (empty($display->layout)) {
$display->layout = $key;
}
// Generate the parents as the autogenerator does, so we will have a
// unique id for each radio button.
$form['layout'][$category][$key] = array(
'#type' => 'radio',
'#title' => $choice,
'#parents' => array('layout'),
'#id' => drupal_clean_css_identifier('edit-layout-' . $key),
'#return_value' => check_plain($key),
'#default_value' => in_array($display->layout, array_keys($layouts)) ? $display->layout : NULL,
);
}
}
ctools_add_js('dependent');
$js['CTools']['dependent'] = $dependencies;
drupal_add_js($js, 'setting');
if (empty($form_state['no buttons'])) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Next'),
);
}
return $form;
}
/**
* Handle form submission of the display layout editor.
*/
function panels_choose_layout_submit($form, &$form_state) {
$form_state['layout'] = $form_state['values']['layout'];
}
/**
* Form definition for the display layout converter.
*
* This form is only triggered if the user attempts to change the layout
* for a display that has already had content assigned to it. It allows
* the user to select where the panes located in to-be-deleted panels should
* be relocated to.
*
* @ingroup forms
*
* @param array $form
* A structured FAPI $form array.
* @param &$form_state
* The Drupal $form_state
*/
function panels_change_layout($form, &$form_state) {
// Provide a temporary display and renderer.
$form_state['layout_display'] = $display = panels_new_display();
if (isset($form_state['cache_key'])) {
$display->cache_key = $form_state['cache_key'];
}
$new_layout = panels_get_layout($form_state['layout']);
$new_layout_regions = panels_get_regions($new_layout, $display);
$old_layout = panels_get_layout($form_state['display']->layout);
$old_layout_regions = panels_get_regions($old_layout, $form_state['display']);
$display->layout = $form_state['layout'];
$renderer = panels_get_renderer_handler('editor', $display);
$renderer->meta_location = 'inline';
ctools_add_css('panels_admin', 'panels');
ctools_add_css('panels_dnd', 'panels');
ctools_add_css('dropdown');
// For every region that had content in the old layout, create a custom pane
// in the new layout that represents that region.
$keys = array_keys($new_layout_regions);
$default_region = reset($keys);
foreach ($old_layout_regions as $region_id => $region_name) {
if (!empty($form_state['display']->panels[$region_id])) {
$pane = panels_new_pane('custom', 'custom', TRUE);
$pane->pid = $region_id;
$pane->configuration['title'] = t('Panes');
$pane->configuration['admin_title'] = $region_name;
// Get a list of pane titles used.
$titles = array();
foreach ($form_state['display']->panels[$region_id] as $pid) {
$content_type = ctools_get_content_type($form_state['display']->content[$pid]->type);
$titles[$pid] = ctools_content_admin_title($content_type, $form_state['display']->content[$pid]->subtype, $form_state['display']->content[$pid]->configuration, $form_state['display']->context);
}
$pane->configuration['body'] = '<ul><li>' . implode('</li><li>', $titles) . '</li></ul>';
// If the region id matches, make it the same; otherwise, put it
// in the default region.
$pane->panel = empty($new_layout_regions[$region_id]) ? $default_region : $region_id;
// Add the pane to the approprate spots.
$display->content[$pane->pid] = $pane;
$display->panels[$pane->panel][] = $pane->pid;
}
}
$form['container'] = array(
'#prefix' => '<div class="change-layout-display clearfix">',
'#suffix' => '</div>',
);
$form['container']['old_layout'] = array(
'#markup' => panels_print_layout_icon($form_state['display']->layout, $old_layout, check_plain($old_layout['title'])),
);
$form['container']['right_arrow'] = array(
'#markup' => theme('image', array('path' => drupal_get_path('module', 'panels') . '/images/go-right.png')),
);
$form['container']['new_layout'] = array(
'#markup' => panels_print_layout_icon($form_state['layout'], $new_layout, check_plain($new_layout['title'])),
);
$edit_form_state = array(
'display' => $display,
'renderer' => $renderer,
'no buttons' => TRUE,
'no preview' => TRUE,
'no display settings' => TRUE,
'display_title' => '',
);
ctools_include('display-edit', 'panels');
$form = panels_edit_display_form($form, $edit_form_state);
if (empty($form_state['no buttons'])) {
$form['back'] = array(
'#type' => 'submit',
'#value' => t('Back'),
'#submit' => array('panels_choose_layout_back'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $form_state['finish'],
'#submit' => array('panels_change_layout_submit'),
'#save-display' => TRUE,
);
}
return $form;
}
/**
* Handle submission of the change layout form.
*
* This submit handler will move panes around and save the display.
*/
function panels_change_layout_submit($form, &$form_state) {
$display = $form_state['display'];
$layout_display = $form_state['layout_display'];
$switch = array();
// Calculate the pids submitted by the display and make a list of
// translation to the regions. Remember the 'pid' of the pane
// is the region id in the old layout.
if (!empty($form_state['values']['panel']['pane'])) {
foreach ($form_state['values']['panel']['pane'] as $region_id => $panes) {
if ($panes) {
$pids = explode(',', $panes);
// need to filter the array, b/c passing it in a hidden field can generate trash
foreach (array_filter($pids) as $pid) {
$switch[$pid] = $region_id;
}
}
}
}
$content = array();
foreach ($switch as $region_id => $new_region_id) {
if (isset($display->panels[$region_id])) {
if (!isset($content[$new_region_id])) {
$content[$new_region_id] = array();
}
$content[$new_region_id] = array_merge($content[$new_region_id], $display->panels[$region_id]);
}
}
// Go through each pane and make sure its region id is correct.
foreach ($content as $region_id => $region) {
foreach ($region as $pid) {
$display->content[$pid]->panel = $region_id;
}
}
$display->panels = $content;
$display->layout = $form_state['layout'];
}
/**
* Handle submission of the change layout form.
*
* This submit handler sets a flag on the form state, which is then used
* by the calling wrapper to restart the process.
*/
function panels_choose_layout_back($form, &$form_state) {
$form_state['back'] = TRUE;
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Legacy state manager for Panels.
*
* Checks all possible ways (using discovery of patterned method names) in which
* Panels may need to operate in legacy mode,
* sets variables as appropriate, and returns an informational
*
*/
class PanelsLegacyState {
var $legacy = NULL;
function t() {
$func = get_t();
$args = func_get_args();
return call_user_func_array($func, $args);
}
function getStatus() {
if (!isset($this->legacy)) {
$this->determineStatus();
}
return $this->legacy;
}
/**
* Run all compatibility checks.
*/
function determineStatus() {
$this->legacy = array();
foreach(get_class_methods($this) as $method) {
if (strtolower(substr($method, 0, 5)) == 'check') {
$this->legacy[$method] = $this->$method();
}
}
$this->legacy = array_filter($this->legacy);
}
// At this time there are no legacy checks.
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains helper functions for the Panels page wizards.
*/
/**
* Add layout form helper for panels page wizards.
*
* This is not a proper form, it is meant to be called by a form to add
* elements to it.
*/
function panels_page_wizard_add_layout(&$form, &$form_state) {
$form_state['allowed_layouts'] = 'panels_page';
$form_state['display'] = $form_state['wizard cache']->display;
// Tell the Panels form not to display buttons.
$form_state['no buttons'] = TRUE;
// Change the #id of the form so the CSS applies properly.
$form['#id'] = 'panels-choose-layout';
$form['layout_prefix'] = array(
'#value' => '<fieldset><legend>' . t('Layout') . '</legend>',
);
ctools_include('common', 'panels');
ctools_include('display-layout', 'panels');
ctools_include('plugins', 'panels');
$form = panels_choose_layout($form, $form_state);
$form['layout_suffix'] = array(
'#value' => '</fieldset>',
);
// $form_state['cache'] = FALSE;
}
/**
* Add content editor form helper for panels page wizards.
*
* This is not a proper form, it is meant to be called by a form to add
* elements to it.
*/
function panels_page_wizard_add_content(&$form, &$form_state) {
ctools_include('ajax');
ctools_include('plugins', 'panels');
ctools_include('common', 'panels');
ctools_include('display-edit', 'panels');
// Panels provides this caching mechanism to make it easy to use the
// wizard to cache the display.
$cache = panels_edit_cache_get('panels_page_wizard:' . $form_state['plugin']['name']);
$form_state['renderer'] = panels_get_renderer_handler('editor', $cache->display);
$form_state['renderer']->cache = &$cache;
$form_state['display'] = &$cache->display;
$form_state['content_types'] = $cache->content_types;
// Tell the Panels form not to display buttons.
$form_state['no buttons'] = TRUE;
$form_state['display_title'] = !empty($cache->display_title);
$form = panels_edit_display_form($form, $form_state);
}
/**
* Add content form submit handler
*
* This is not a proper submit handler, it is meant to be called by a form's
* submit handler to handle submission.
*/
function panels_page_wizard_add_content_submit(&$form, &$form_state) {
// Call the normal panels edit form submit to make sure values are stored
// on the display
panels_edit_display_form_submit($form, $form_state);
$cache = &$form_state['wizard cache'];
// Copy the "being edited" cached display to the "actual" cached display.
$cache->display = &$form_state['display'];
unset($cache->display_cache);
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* @file
* Bulk export of panels_layouts objects generated by Bulk export module.
*/
/**
* Implements hook_default_panels_renderer_pipeline().
*/
function panels_default_panels_renderer_pipeline() {
$pipelines = array();
$pipeline = new stdClass;
$pipeline->disabled = FALSE; /* Edit this to true to make a default pipeline disabled initially */
$pipeline->api_version = 1;
$pipeline->name = 'standard';
$pipeline->admin_title = t('Standard');
$pipeline->admin_description = t('Renders a panel normally. This is the most common option.');
$pipeline->weight = -100;
$pipeline->settings = array(
'renderers' => array(
0 => array(
'access' => array(),
'renderer' => 'standard',
'options' => array(),
),
),
);
$pipelines[$pipeline->name] = $pipeline;
return $pipelines;
}

View File

@@ -0,0 +1,485 @@
<?php
/**
* @file
*
* Contains helper code for plugins and contexts.
*/
/**
* Determine if a pane is visible.
*
* @param $pane
* The pane object to test for access.
* @param $display
* The display object containing the pane object to be tested.
*/
function panels_pane_access($pane, $display) {
ctools_include('context');
return ctools_access($pane->access, $display->context);
}
/**
* Get a list of panels available in the layout.
*/
function panels_get_regions($layout, $display) {
if ($function = ctools_plugin_get_function($layout, 'regions function')) {
return $function($display, $display->layout_settings, $layout);
}
if (!empty($layout['regions'])) {
return $layout['regions'];
}
return array();
}
/**
* Get cached content for a given display and possibly pane.
*
* @return
* The cached content, or FALSE to indicate no cached content exists.
*/
function panels_get_cached_content($display, $args, $context, $pane = NULL) {
// Never use cache on a POST
if (!empty($_POST)) {
return FALSE;
}
$method = $pane ? $pane->cache['method'] : $display->cache['method'];
$function = panels_plugin_get_function('cache', $method, 'cache get');
if (!$function) {
return FALSE;
}
$conf = $pane ? $pane->cache['settings'] : $display->cache['settings'];
$cache = $function($conf, $display, $args, $context, $pane);
if (empty($cache)) {
return FALSE;
}
// restore it.
$cache->restore();
return $cache;
}
/**
* Store cached content for a given display and possibly pane.
*/
function panels_set_cached_content($cache, $display, $args, $context, $pane = NULL) {
// Never use cache on a POST
if (!empty($_POST)) {
return FALSE;
}
$method = $pane ? $pane->cache['method'] : $display->cache['method'];
$function = panels_plugin_get_function('cache', $method, 'cache set');
if (!$function) {
return FALSE;
}
$conf = $pane ? $pane->cache['settings'] : $display->cache['settings'];
// snapshot it.
$cache->cache();
return $function($conf, $cache, $display, $args, $context, $pane);
}
/**
* Clear all cached content for a display.
*/
function panels_clear_cached_content($display) {
// Figure out every method we might be using to cache content in this display:
$methods = array();
if (!empty($display->cache['method'])) {
$methods[$display->cache['method']] = TRUE;
}
foreach ($display->content as $pane) {
if (!empty($pane->cache['method'])) {
$methods[$pane->cache['method']] = TRUE;
}
}
foreach (array_keys($methods) as $method) {
$function = panels_plugin_get_function('cache', $method, 'cache clear');
if ($function) {
$function($display);
}
}
}
/**
* An object to hold caching information while it is happening.
*/
class panels_cache_object {
var $content = '';
var $head = NULL;
var $css = NULL;
var $js = NULL;
var $tokens = NULL;
var $ready = FALSE;
/**
* When constructed, take a snapshot of our existing out of band data.
*/
function panels_cache_object() {
$this->head = drupal_add_html_head();
$this->css = drupal_add_css();
$this->tokens = ctools_set_page_token();
$this->js = drupal_add_js();
}
/**
* Add content to the cache. This assumes a pure stream;
* use set_content() if it's something else.
*/
function add_content($content) {
$this->content .= $content;
}
function set_content($content) {
$this->content = $content;
}
/**
* Set the object for storing. This overwrites.
*/
function cache() {
if ($this->ready) {
return;
}
$this->ready = TRUE;
// Simple replacement for head
$this->head = str_replace($this->head, '', drupal_add_html_head());
// Slightly less simple for CSS:
$css = drupal_add_css();
$start = $this->css;
$this->css = array();
foreach ($css as $name => $data) {
if (!isset($start[$name])) {
$this->css[$name] = $data;
}
}
$js = drupal_add_js();
$start = $this->js;
$this->js = array();
// If there are any differences between the old and the new javascript then
// store them to be added later.
if ($diff = array_diff_assoc($js, $start)) {
$this->js = $diff;
}
// Special case the settings key and get the difference of the data.
if ($settings_diff = array_diff_assoc($js['settings']['data'], $start['settings']['data'])) {
$this->js['settings'] = $settings_diff;
}
// And for tokens:
$tokens = ctools_set_page_token();
foreach ($this->tokens as $token => $argument) {
if (isset($tokens[$token])) {
unset($tokens[$token]);
}
}
$this->tokens = $tokens;
}
/**
* Restore out of band data saved to cache.
*/
function restore() {
if (!empty($this->head)) {
drupal_add_html_head($this->head);
}
if (!empty($this->css)) {
foreach ($this->css as $args) {
drupal_add_css($args['data'], $args);
}
}
if (!empty($this->js)) {
foreach ($this->js as $key => $args) {
if ($key !== 'settings') {
drupal_add_js($args['data'], $args);
}
else {
foreach ($args as $setting) {
drupal_add_js($setting, 'setting');
}
}
}
}
if (!empty($this->tokens)) {
foreach ($this->tokens as $token => $key) {
list($type, $argument) = $key;
ctools_set_page_token($token, $type, $argument);
}
}
}
}
/**
* Get the title of a pane.
*
* @deprecated @todo this function should be removed.
*
* @param $pane
* The $pane object.
*/
function panels_get_pane_title(&$pane, $context = array(), $incoming_content = NULL) {
ctools_include('content');
return ctools_content_admin_title($pane->type, $pane->subtype, $pane->configuration, $context);
}
/**
* Fetch metadata on a specific layout plugin.
*
* @param $layout
* Name of a panel layout. If the layout name contains a ':' this
* indicates that we need to separate the sublayout out and
* load it individually.
*
* @return
* An array with information about the requested panel layout.
*/
function panels_get_layout($layout) {
ctools_include('plugins');
return ctools_get_plugins('panels', 'layouts', $layout);
}
/**
* Fetch metadata for all layout plugins.
*
* @return
* An array of arrays with information about all available panel layouts.
*/
function panels_get_layouts() {
ctools_include('plugins');
return ctools_get_plugins('panels', 'layouts');
}
/**
* Fetch metadata for all layout plugins that provide builders.
*
* The layout builders allow reusable layouts be stored in the database and
* exported. Since there are different methods, we are not limiting this
* to just one plugin.
*
* @return
* An array of arrays with information about panel layouts with builders.
*/
function panels_get_layout_builders() {
ctools_include('plugins');
$plugins = ctools_get_plugins('panels', 'layouts');
$builders = array();
foreach ($plugins as $name => $plugin) {
if (!empty($plugin['builder'])) {
$builders[$name] = $plugin;
}
}
return $builders;
}
/**
* Fetch metadata on a specific style plugin.
*
* @param $style
* Name of a panel style.
*
* @return
* An array with information about the requested panel style.
*/
function panels_get_style($style) {
ctools_include('plugins');
return ctools_get_plugins('panels', 'styles', $style);
}
/**
* Fetch metadata for all style plugins.
*
* @return
* An array of arrays with information about all available panel styles.
*/
function panels_get_styles() {
ctools_include('plugins');
return ctools_get_plugins('panels', 'styles');
}
/**
* Fetch metadata on a specific caching plugin.
*
* @param $cache
* Name of a panel cache.
*
* @return
* An array with information about the requested panel cache.
*/
function panels_get_cache($cache) {
ctools_include('plugins');
return ctools_get_plugins('panels', 'cache', $cache);
}
/**
* Fetch metadata for all context plugins.
*
* @return
* An array of arrays with information about all available panel caches.
*/
function panels_get_caches() {
ctools_include('plugins');
return ctools_get_plugins('panels', 'cache');
}
/**
* Fetch metadata on a specific display renderer plugin.
*
* @return
* An array of arrays with information about the requested panels display
* renderer.
*/
function panels_get_display_renderer($renderer) {
ctools_include('plugins');
return ctools_get_plugins('panels', 'display_renderers', $renderer);
}
/**
* Fetch metadata for all display renderer plugins.
*
* @return
* An array of arrays with information about all available panels display
* renderer.
*/
function panels_get_display_renderers() {
ctools_include('plugins');
return ctools_get_plugins('panels', 'display_renderers');
}
/**
* Get and initialize the class to handle rendering a display.
*
* @return
* Either the instantiated renderer or FALSE if one could not be found.
*/
function panels_get_renderer_handler($plugin, &$display) {
if (is_string($plugin)) {
$plugin = panels_get_display_renderer($plugin);
}
$class = ctools_plugin_get_class($plugin, 'renderer');
if ($class) {
$renderer = new $class();
$renderer->init($plugin, $display);
return $renderer;
}
return FALSE;
}
/**
* Choose a renderer for a display based on a render pipeline setting.
*/
function panels_get_renderer($pipeline_name, &$display) {
// Load the pipeline
ctools_include('export');
$pipeline = ctools_export_crud_load('panels_renderer_pipeline', $pipeline_name);
// If we can't, or it has no renderers, default.
if (!$pipeline || empty($pipeline->settings['renderers'])) {
return panels_get_renderer_handler('standard', $display);
}
// Get contexts set on the pipeline:
$contexts = array();
if (!empty($pipeline->settings['contexts'])) {
$contexts = ctools_context_load_contexts($pipeline->settings['context']);
}
// Cycle through our renderers and see.
foreach ($pipeline->settings['renderers'] as $candidate) {
// See if this passes selection criteria.
if (!ctools_access($candidate['access'], $contexts)) {
continue;
}
$renderer = panels_get_renderer_handler($candidate['renderer'], $display);
if (!empty($candidate['options'])) {
$renderer->set_options($candidate['options']);
}
return $renderer;
}
// Fall through. If no renderer is selected, use the standard renderer
return panels_get_renderer_handler('standard', $display);
}
/**
* Sort callback for sorting renderer pipelines.
*
* Sort first by weight, then by title.
*/
function _panels_renderer_pipeline_sort($a, $b) {
if ($a->weight == $b->weight) {
if ($a->admin_title == $b->admin_title) {
return 0;
}
return ($a->admin_title < $b->admin_title) ? -1 : 1;
}
return ($a->weight < $b->weight) ? -1 : 1;
}
/**
* Get a list of available renderer pipelines.
*
* This can be used to form a select or radios widget by enabling
* sorting. Descriptions are left in.
*/
function panels_get_renderer_pipelines($sort = TRUE) {
ctools_include('export');
$pipelines = ctools_export_crud_load_all('panels_renderer_pipeline');
if ($sort) {
uasort($pipelines, '_panels_renderer_pipeline_sort');
}
return $pipelines;
}
/**
* Get a function from a plugin, if it exists.
*
* @param $plugin
* The type of plugin
* @param $which
* Either the loaded plugin object (or the same data in array form)
* or a string with the name of the desired the specific plugin.
* @param $function_name
* The identifier of the function. For example, 'settings form'.
*
* @return
* The actual name of the function to call, or NULL if the function
* does not exist.
*
* @deprecated All calls to this function should be removed.
*/
function panels_plugin_get_function($plugin, $which, $function_name) {
ctools_include('plugins');
if (is_object($which) || is_array($which)) {
return ctools_plugin_get_function($which, $function_name);
}
else {
return ctools_plugin_load_function('panels', $plugin, $which, $function_name);
}
}

View File

@@ -0,0 +1,546 @@
/**
* @file display_editor.js
*
* Contains the javascript for the Panels display editor.
*/
(function ($) {
// randomly lock a pane.
// @debug only
Drupal.settings.Panels = Drupal.settings.Panels || {};
/** Delete pane button **/
Drupal.Panels.bindClickDelete = function(context) {
$('a.pane-delete:not(.pane-delete-processed)', context)
.addClass('pane-delete-processed')
.click(function() {
if (confirm(Drupal.t('Remove this pane?'))) {
var id = '#' + $(this).attr('id').replace('pane-delete-', '');
$(id).remove();
Drupal.Panels.Draggable.savePositions();
}
return false;
});
};
Drupal.Panels.bindPortlet = function() {
var handle = $(this).find('.panel-pane-collapsible > div.pane-title');
var content = $(this).find('.panel-pane-collapsible > div.pane-content');
if (content.length) {
var toggle = $('<span class="toggle toggle-collapsed"></span>');
handle.before(toggle);
toggle.click(function() {
content.slideToggle(20);
toggle.toggleClass('toggle-collapsed');
});
handle.click(function() {
content.slideToggle(20);
toggle.toggleClass('toggle-collapsed');
});
content.hide();
}
};
Drupal.Panels.Draggable = {
// The draggable object
object: null,
// Where objects can be dropped
dropzones: [],
current_dropzone: null,
// positions within dropzones where an object can be plazed
landing_pads: [],
current_pad: null,
// Where the object is
mouseOffset: { x: 0, y: 0 },
windowOffset: { x: 0, y: 0 },
offsetDivHeight: 0,
// original settings to be restored
original: {},
// a placeholder so that if the object is let go but not over a drop zone,
// it can be put back where it belongs
placeholder: {},
hoverclass: 'hoverclass',
helperclass: 'helperclass',
accept: 'div.panel-region',
handle: 'div.grabber',
draggable: 'div.panel-portlet',
main: 'div#panels-dnd-main',
// part of the id to remove to get just the number
draggableId: 'panel-pane-',
// part of the id to remove to get just the number
regionId: 'panel-region-',
// What to add to the front of a the id to get the form id for a panel
formId: 'input#edit-',
maxWidth: 250,
unsetDropZone: function() {
$(this.current_dropzone.obj).removeClass(this.hoverclass);
this.current_dropzone = null;
for (var i in this.landing_pads) {
$(this.landing_pads[i].obj).remove();
}
this.landing_pads = [];
this.current_pad = null;
},
createLandingPad: function(where, append) {
var obj = $('<div class="' + this.helperclass +'" id="' +
$(where).attr('id') + '-dropzone">&nbsp;</div>');
if (append) {
$(where).append(obj);
}
else {
$(where).before(obj);
}
var offset = $(obj).offset();
$(obj).css({
display: 'none'
});
this.landing_pads.push({
centerX: offset.left + ($(obj).innerWidth() / 2),
centerY: offset.top + ($(obj).innerHeight() / 2),
obj: obj
});
return obj;
},
calculateDropZones: function(event, dropzone) {
var draggable = Drupal.Panels.Draggable;
var dropzones = [];
$(this.accept).each(function() {
var offset = $(this).offset();
offset.obj = this;
offset.region = this.id.replace(draggable.regionId, '');
offset.width = $(this).outerWidth();
offset.height = $(this).outerHeight();
dropzones.push(offset);
});
this.dropzones = dropzones;
},
reCalculateDropZones: function() {
for (var i in this.dropzones) {
var offset = $(this.dropzones[i].obj).offset();
offset.width = $(this.dropzones[i].obj).outerWidth();
offset.height = $(this.dropzones[i].obj).outerHeight();
$.extend(this.dropzones[i], offset);
}
},
changeDropZone: function(new_dropzone) {
// Unset our old dropzone.
if (this.current_dropzone) {
this.unsetDropZone();
}
// Set up our new dropzone.
this.current_dropzone = new_dropzone;
$(this.current_dropzone.obj).addClass(this.hoverclass);
// add a landing pad
this.createLandingPad(this.current_dropzone.obj, true);
var that = this;
// Create a landing pad before each existing portlet.
$(this.current_dropzone.obj).find(this.draggable).each(function() {
if (that.object.id != this.id) {
that.createLandingPad(this, false);
}
});
},
findLandingPad: function(x, y) {
var shortest_distance = null;
var nearest_pad = null;
// find the nearest pad.
for (var i in this.landing_pads) {
// This isn't the real distance, this is the square of the
// distance -- no point in spending processing time on
// sqrt.
var dstx = Math.abs(x - this.landing_pads[i].centerX);
var dsty = Math.abs(y - this.landing_pads[i].centerY);
var distance = (dstx * dstx) + (dsty * dsty);
if (shortest_distance == null || distance < shortest_distance) {
shortest_distance = distance;
nearest_pad = this.landing_pads[i];
}
}
if (nearest_pad != this.current_pad) {
if (this.current_pad) {
$(this.current_pad.obj).hide();
}
this.current_pad = nearest_pad;
$(nearest_pad.obj).show();
}
},
findDropZone: function(x, y) {
// Go through our dropzones and see if we're over one.
var new_dropzone = null;
for (var i in this.dropzones) {
// console.log('x:' + x + ' left:' + this.dropzones[i].left + ' right: ' + this.dropzones[i].left + this.dropzones[i].width);
if (this.dropzones[i].left < x &&
x < this.dropzones[i].left + this.dropzones[i].width &&
this.dropzones[i].top < y &&
y < this.dropzones[i].top + this.dropzones[i].height) {
new_dropzone = this.dropzones[i];
break;
}
}
// If we're over one, see if it's different.
if (new_dropzone && (!this.regionLock || this.regionLockRegions[new_dropzone.region])) {
var changed = false;
if (!this.current_dropzone || new_dropzone.obj.id != this.current_dropzone.obj.id) {
this.changeDropZone(new_dropzone);
changed = true;
}
this.findLandingPad(x, y);
if (changed) {
// recalculate the size of our drop zones due to the fact that we're drawing landing pads.
this.reCalculateDropZones();
}
}
// If we're not over one, be sure to unhilite one if we were just
// over it.
else if (this.current_dropzone) {
this.unsetDropZone();
}
},
/** save button clicked, or pane deleted **/
savePositions: function() {
var draggable = Drupal.Panels.Draggable;
$(draggable.accept).each(function() {
var val = '';
$(this).find(draggable.draggable).each(function() {
if (val) {
val += ',';
}
val += this.id.replace(draggable.draggableId, '');
});
var region = this.id.replace(draggable.regionId, '');
$('input[name="panel[pane][' + region + ']"]').val(val);
});
return false;
}
};
Drupal.Panels.DraggableHandler = function() {
$(this).addClass('panel-draggable');
var draggable = Drupal.Panels.Draggable;
var scrollBuffer = 10;
var scrollDistance = 10;
var scrollTimer = 30;
getMouseOffset = function(docPos, mousePos, windowPos) {
return { x: mousePos.x - docPos.x + windowPos.x, y: mousePos.y - docPos.y + windowPos.y};
};
getMousePos = function(ev) {
ev = ev || window.event;
if (ev.pageX || ev.pageY) {
return { x:ev.pageX, y:ev.pageY };
}
return {
x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
y:ev.clientY + document.body.scrollTop - document.body.clientTop
};
};
getPosition = function(e) {
/*
if (document.defaultView && document.defaultView.getComputedStyle) {
var css = document.defaultView.getComputedStyle(e, null);
return {
x: parseInt(css.getPropertyValue('left')),
y: parseInt(css.getPropertyValue('top'))
};
}
*/
var left = 0;
var top = 0;
while (e.offsetParent) {
left += e.offsetLeft;
top += e.offsetTop;
e = e.offsetParent;
}
left += e.offsetLeft;
top += e.offsetTop;
return { x:left, y:top };
};
mouseUp = function(e) {
clearTimeout(draggable.timeoutId);
draggable.dropzones = [];
if (draggable.current_pad) {
// Drop the object where we're hovering
$(draggable.object).insertAfter($(draggable.current_pad.obj));
Drupal.Panels.changed($(draggable.object));
}
else {
// or put it back where it came from
$(draggable.object).insertAfter(draggable.placeholder);
}
// remove the placeholder
draggable.placeholder.remove();
// restore original settings.
$(draggable.object).css(draggable.original);
if (draggable.current_dropzone) {
draggable.unsetDropZone();
}
$(document).unbind('mouseup').unbind('mousemove');
draggable.savePositions();
};
mouseMove = function(e) {
draggable.mousePos = getMousePos(e);
draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y);
var windowMoved = parseInt(draggable.offsetDivHeight - $(draggable.main).innerHeight());
draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + windowMoved + 'px';
draggable.object.style.left = draggable.mousePos.x - draggable.mouseOffset.x + 'px';
$(draggable.object).toggleClass('moving');
};
mouseDown = function(e) {
// If we mouse-downed over something clickable, don't drag!
if (e.target.nodeName == 'A' || e.target.nodeName == 'INPUT' || e.target.parentNode.nodeName == 'A' || e.target.nodeName.nodeName == 'INPUT') {
return;
}
draggable.object = $(this).parent(draggable.draggable).get(0);
draggable.paneId = draggable.object.id.replace(draggable.draggableId, '');
// create a placeholder so we can put this object back if dropped in an invalid location.
draggable.placeholder = $('<div class="draggable-placeholder-object" style="display:none"></div>"');
$(draggable.object).after(draggable.placeholder);
// Store original CSS so we can put it back.
draggable.original = {
position: $(draggable.object).css('position'),
width: 'auto',
left: $(draggable.object).css('left'),
top: $(draggable.object).css('top'),
'z-index': $(draggable.object).css('z-index'),
'margin-bottom': $(draggable.object).css('margin-bottom'),
'margin-top': $(draggable.object).css('margin-top'),
'margin-left': $(draggable.object).css('margin-left'),
'margin-right': $(draggable.object).css('margin-right'),
'padding-bottom': $(draggable.object).css('padding-bottom'),
'padding-top': $(draggable.object).css('padding-top'),
'padding-left': $(draggable.object).css('padding-left'),
'padding-right': $(draggable.object).css('padding-right')
};
draggable.mousePos = getMousePos(e);
var originalPos = $(draggable.object).offset();
var width = Math.min($(draggable.object).innerWidth(), draggable.maxWidth);
draggable.offsetDivHeight = $(draggable.main).innerHeight();
draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y);
// Make copies of these because in FF3, they actually change when we
// move the item, whereas they did not in FF2.
if (e.layerX || e.layerY) {
var layerX = e.layerX;
var layerY = e.layerY;
}
else if (e.originalEvent && e.originalEvent.layerX) {
var layerX = e.originalEvent.layerX;
var layerY = e.originalEvent.layerY;
}
// Make the draggable relative, get it out of the way and make it
// invisible.
$(draggable.object).css({
position: 'relative',
'z-index': 100,
width: width + 'px',
'margin-bottom': (-1 * parseInt($(draggable.object).outerHeight())) + 'px',
'margin-top': 0,
'margin-left': 0,
'margin-right': (-1 * parseInt($(draggable.object).outerWidth())) + 'px',
'padding-bottom': 0,
'padding-top': 0,
'padding-left': 0,
'padding-right': 0,
'left': 0,
'top': 0
})
.insertAfter($(draggable.main));
var newPos = $(draggable.object).offset();
var windowOffset = { left: originalPos.left - newPos.left, top: originalPos.top - newPos.top }
// if they grabbed outside the area where we make the draggable smaller, move it
// closer to the cursor.
if (layerX != 'undefined' && layerX > width) {
windowOffset.left += layerX - 10;
}
else if (layerX != 'undefined' && e.offsetX > width) {
windowOffset.left += e.offsetX - 10;
}
// This is stored so we can move with it.
draggable.mouseOffset = { x: draggable.mousePos.x - windowOffset.left, y: draggable.mousePos.y - windowOffset.top};
draggable.offsetDivHeight = $(draggable.main).innerHeight();
draggable.object.style.top = windowOffset.top + 'px';
draggable.object.style.left = windowOffset.left + 'px';
$(document).unbind('mouseup').unbind('mousemove').mouseup(mouseUp).mousemove(mouseMove);
draggable.calculateDropZones(draggable.mousePos, e);
draggable.timeoutId = setTimeout('timer()', scrollTimer);
// If locking to a particular set of regions, set that:
if (Drupal.settings.Panels && Drupal.settings.Panels.RegionLock && Drupal.settings.Panels.RegionLock[draggable.paneId]) {
draggable.regionLock = true;
draggable.regionLockRegions = Drupal.settings.Panels.RegionLock[draggable.paneId];
}
else {
draggable.regionLock = false;
draggable.regionLockRegions = null;
}
return false;
};
timer = function() {
if (!draggable.timeCount) {
draggable.timeCount = 0;
}
draggable.timeCount = draggable.timeCount + 1;
var left = $(window).scrollLeft();
var right = left + $(window).width();
var top = $(window).scrollTop();
var bottom = top + $(window).height();
if (draggable.mousePos.x < left + scrollBuffer && left > 0) {
window.scrollTo(left - scrollDistance, top);
draggable.mousePos.x -= scrollDistance;
draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
}
else if (draggable.mousePos.x > right - scrollBuffer) {
window.scrollTo(left + scrollDistance, top);
draggable.mousePos.x += scrollDistance;
draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
}
else if (draggable.mousePos.y < top + scrollBuffer && top > 0) {
window.scrollTo(left, top - scrollDistance);
draggable.mousePos.y -= scrollDistance;
draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
}
else if (draggable.mousePos.y > bottom - scrollBuffer) {
window.scrollTo(left, top + scrollDistance);
draggable.mousePos.y += scrollDistance;
draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
}
draggable.timeoutId = setTimeout('timer()', scrollTimer);
}
$(this).mousedown(mouseDown);
};
$.fn.extend({
panelsDraggable: Drupal.Panels.DraggableHandler
});
/**
* Implement Drupal behavior for autoattach
*/
Drupal.behaviors.PanelsDisplayEditor = {
attach: function(context) {
// Show javascript only items.
$('span#panels-js-only').css('display', 'inline');
$('#panels-dnd-main div.panel-pane:not(.panel-portlet)')
.addClass('panel-portlet')
.each(Drupal.Panels.bindPortlet);
// The above doesn't work if context IS the pane, so do this to catch that.
if ($(context).hasClass('panel-pane') && !$(context).hasClass('panel-portlet')) {
$(context)
.addClass('panel-portlet')
.each(Drupal.Panels.bindPortlet);
}
// Make draggables and make sure their positions are saved.
$(context).find('div.grabber:not(.panel-draggable)').panelsDraggable();
Drupal.Panels.Draggable.savePositions();
// Bind buttons.
$('input#panels-hide-all', context).click(Drupal.Panels.clickHideAll);
$('input#panels-show-all', context).click(Drupal.Panels.clickShowAll);
Drupal.Panels.bindClickDelete(context);
$('#panels-live-preview-button:not(.panels-preview-processed)')
.addClass('panels-preview-processed')
.click(function () {
if (!$('#panels-preview').size()) {
$('#panels-dnd-main').parents('form').after('<div id="panels-preview" class="clearfix"></div>');
}
var html = '';
html += ' <div id="modal-throbber">';
html += ' <div class="modal-throbber-wrapper">';
html += Drupal.settings.CToolsModal.throbber;
html += ' </div>';
html += ' </div>';
$('#panels-preview').html(html);
});
var setTitleClass = function () {
if ($('#edit-display-title-hide-title').val() == 2) {
$('#panels-dnd-main').removeClass('panels-set-title-hide');
}
else {
$('#panels-dnd-main').addClass('panels-set-title-hide');
}
}
// The panes have an option to set the display title, but only if
// a select is set to the proper value. This sets a class on the
// main edit div so that the option to set the display title
// is hidden if that is not selected, and visible if it is.
$('#edit-display-title-hide-title:not(.panels-title-processed)')
.addClass('panels-title-processed')
.change(setTitleClass);
setTitleClass();
}
}
$(function() {
/**
* AJAX responder command to render the preview.
*/
Drupal.ajax.prototype.commands.panel_preview = function(ajax, command, status) {
$('#panels-preview').html(command.output);
}
});
})(jQuery);

View File

@@ -0,0 +1,18 @@
/**
* @file layout.js
*
* Contains javascript to make layout modification a little nicer.
*/
(function ($) {
Drupal.Panels.Layout = {};
Drupal.Panels.Layout.autoAttach = function() {
$('div.form-item div.layout-icon').click(function() {
$widget = $('input', $(this).parent());
// Toggle if a checkbox, turn on if a radio.
$widget.attr('checked', !$widget.attr('checked') || $widget.is('input[type=radio]'));
});
};
$(Drupal.Panels.Layout.autoAttach);
})(jQuery);

View File

@@ -0,0 +1,28 @@
/**
* @file
* Implement basic methods required by all of panels.
*/
(function ($) {
Drupal.Panels = Drupal.Panels || {};
Drupal.Panels.changed = function(item) {
if (!item.is('.changed')) {
item.addClass('changed');
item.find('div.grabber span.text').append(' <span class="star">*</span> ');
}
};
Drupal.Panels.restripeTable = function(table) {
// :even and :odd are reversed because jquery counts from 0 and
// we count from 1, so we're out of sync.
$('tbody tr:not(:hidden)', $(table))
.removeClass('even')
.removeClass('odd')
.filter(':even')
.addClass('odd')
.end()
.filter(':odd')
.addClass('even');
};
})(jQuery);

View File

@@ -0,0 +1,28 @@
(function ($) {
Drupal.Panels = Drupal.Panels || {};
Drupal.Panels.autoAttach = function() {
if ($.browser.msie) {
// If IE, attach a hover event so we can see our admin links.
$("div.panel-pane").hover(
function() {
$('div.panel-hide', this).addClass("panel-hide-hover"); return true;
},
function() {
$('div.panel-hide', this).removeClass("panel-hide-hover"); return true;
}
);
$("div.admin-links").hover(
function() {
$(this).addClass("admin-links-hover"); return true;
},
function(){
$(this).removeClass("admin-links-hover"); return true;
}
);
}
};
$(Drupal.Panels.autoAttach);
})(jQuery);

View File

@@ -0,0 +1,18 @@
name = Panels
description = Core Panels display functions; provides no external UI, at least one other Panels module should be enabled.
core = 7.x
package = "Panels"
configure = admin/structure/panels
dependencies[] = ctools
files[] = panels.module
files[] = includes/common.inc
files[] = includes/legacy.inc
files[] = includes/plugins.inc
files[] = plugins/views/panels_views_plugin_row_fields.inc
; Information added by drupal.org packaging script on 2013-03-02
version = "7.x-3.3+39-dev"
core = "7.x"
project = "panels"
datestamp = "1362187383"

View File

@@ -0,0 +1,377 @@
<?php
/**
* Test requirements for installation and running.
*/
function panels_requirements($phase) {
$function = "panels_requirements_$phase";
return function_exists($function) ? $function() : array();
}
/**
* Check install-time requirements.
*/
function panels_requirements_install() {
$requirements = array();
$t = get_t();
// Assume that if the user is running an installation profile that both
// Panels and CTools are the same release.
if (!(defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install')) {
// apparently the install process doesn't include .module files,
// so we need to force the issue in order for our versioning
// check to work.
if (!defined('PANELS_REQUIRED_CTOOLS_API')) {
include_once drupal_get_path('module', 'panels') . '/panels.module';
}
// In theory we should check module_exists, but Drupal's gating should
// actually prevent us from getting here otherwise.
if (!defined('CTOOLS_API_VERSION')) {
include_once drupal_get_path('module', 'ctools') . '/ctools.module';
}
if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
$requirements['panels_ctools'] = array(
'title' => $t('CTools API Version'),
'value' => CTOOLS_API_VERSION,
'severity' => REQUIREMENT_ERROR,
'description' => t('The CTools API version is too old for Panels. Panels needs at least %version.', array('%version' => PANELS_REQUIRED_CTOOLS_API))
);
}
}
return $requirements;
}
/**
* Implementation of hook_schema().
*/
function panels_schema() {
// This should always point to our 'current' schema. This makes it relatively easy
// to keep a record of schema as we make changes to it.
return panels_schema_4();
}
function panels_schema_4() {
$schema = panels_schema_3();
$schema['panels_pane']['fields']['locks'] = array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
);
return $schema;
}
/**
* Schema from the D6 version.
*/
function panels_schema_3() {
// Schema 3 is now locked. If you need to make changes, please create
// schema 4 and add them.
$schema = array();
$schema['panels_display'] = array(
'export' => array(
'object' => 'panels_display',
'bulk export' => FALSE,
'export callback' => 'panels_export_display',
'can disable' => FALSE,
'identifier' => 'display',
),
'fields' => array(
'did' => array(
'type' => 'serial',
'not null' => TRUE,
'no export' => TRUE,
),
'layout' => array(
'type' => 'varchar',
'length' => '255',
'default' => '',
),
'layout_settings' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'panel_settings' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'cache' => array(
'type' => 'text',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'title' => array(
'type' => 'varchar',
'length' => '255',
'default' => '',
),
'hide_title' => array(
'type' => 'int',
'size' => 'tiny',
'default' => 0,
'no export' => TRUE,
),
'title_pane' => array(
'type' => 'int',
'default' => 0,
'no export' => TRUE,
),
),
'primary key' => array('did'),
);
$schema['panels_pane'] = array(
'export' => array(
'can disable' => FALSE,
'identifier' => 'pane',
'bulk export' => FALSE,
),
'fields' => array(
'pid' => array(
'type' => 'serial',
'not null' => TRUE,
),
'did' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'no export' => TRUE,
),
'panel' => array(
'type' => 'varchar',
'length' => '32',
'default' => '',
),
'type' => array(
'type' => 'varchar',
'length' => '32',
'default' => '',
),
'subtype' => array(
'type' => 'varchar',
'length' => '64',
'default' => '',
),
'shown' => array(
'type' => 'int',
'size' => 'tiny',
'default' => 1,
),
'access' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'configuration' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'cache' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'style' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'css' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'extras' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'initial' => array(),
),
'position' => array(
'type' => 'int',
'size' => 'small',
'default' => 0,
),
),
'primary key' => array('pid'),
'indexes' => array(
'did_idx' => array('did')
),
);
$schema['panels_renderer_pipeline'] = array(
'description' => 'Contains renderer pipelines for Panels. Each pipeline contains one or more renderers and access rules to select which renderer gets used.',
'export' => array(
'identifier' => 'pipeline',
'bulk export' => TRUE,
'primary key' => 'rpid',
'api' => array(
'owner' => 'panels',
'api' => 'pipelines',
'minimum_version' => 1,
'current_version' => 1,
),
),
'fields' => array(
'rpid' => array(
'type' => 'serial',
'description' => 'A database primary key to ensure uniqueness.',
'not null' => TRUE,
'no export' => TRUE,
),
'name' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Unique ID for this content. Used to identify it programmatically.',
),
'admin_title' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative title for this pipeline.',
),
'admin_description' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Administrative description for this pipeline.',
'object default' => '',
),
'weight' => array(
'type' => 'int',
'size' => 'small',
'default' => 0,
),
'settings' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Serialized settings for the actual pipeline. The contents of this field are up to the plugin that uses it.',
'serialize' => TRUE,
'object default' => array(),
),
),
'primary key' => array('rpid'),
);
$schema['panels_layout'] = array(
'description' => 'Contains exportable customized layouts for this site.',
'export' => array(
'identifier' => 'layout',
'bulk export' => TRUE,
'primary key' => 'lid',
'api' => array(
'owner' => 'panels',
'api' => 'layouts',
'minimum_version' => 1,
'current_version' => 1,
),
),
'fields' => array(
'lid' => array(
'type' => 'serial',
'description' => 'A database primary key to ensure uniqueness.',
'not null' => TRUE,
'no export' => TRUE,
),
'name' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Unique ID for this content. Used to identify it programmatically.',
),
'admin_title' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative title for this layout.',
),
'admin_description' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Administrative description for this layout.',
'object default' => '',
),
'category' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative category for this layout.',
),
'plugin' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'The layout plugin that owns this layout.',
),
'settings' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Serialized settings for the actual layout. The contents of this field are up to the plugin that uses it.',
'serialize' => TRUE,
'object default' => array(),
),
),
'primary key' => array('lid'),
);
return $schema;
}
/**
* Change panels_display.layout to match the size of panels_layout.name.
*/
function panels_update_7300() {
// Load the schema.
$schema = panels_schema_3();
$table = 'panels_display';
$field = 'layout';
$spec = $schema[$table]['fields'][$field];
// Re-define the column.
db_change_field($table, $field, $field, $spec);
return t('Changed the panels_display.layout field to the correct size.');
}
/**
* Add lock field to panels_pane table.
*/
function panels_update_7301() {
// Load the schema.
// Due to a previous failure, the field may already exist:
$schema = panels_schema_4();
$table = 'panels_pane';
$field = 'locks';
if (!db_field_exists($table, $field)) {
$spec = $schema[$table]['fields'][$field];
// Core does not properly respect 'initial' and 'serialize'.
unset($spec['initial']);
// Re-define the column.
db_add_field($table, $field, $spec);
return t('Added panels_pane.lock field.');
}
return t('panels_pane.lock field already existed, update skipped.');
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
div.panels-ipe-handlebar-wrapper ul {
float: right;
text-align: left;
}
div.panels-ipe-handlebar-wrapper li {
margin: 0 0 0 .5em;
float: right;
}
div.panels-ipe-draghandle span.panels-ipe-draghandle-icon {
float: left;
}
div.panels-ipe-placeholder {
text-align: right;
}
div.panels-ipe-newblock {
left: 30px;
right: auto;
}
div.panels-ipe-handlebar-wrapper li a span,
div.panels-ipe-newblock a span {
text-align: right;
}
div.panels-ipe-newblock a.style {
margin-left: .5em;
margin-right: auto;
}
.panels-ipe-editing .panels-ipe-region {
float: right;
}
/** ============================================================================
* Controller form markup
*/
.ipe-throbber {
right: 49%;
right: auto;
}
div.panels-ipe-control .form-submit {
padding: 0 34px 2px 0.8em;
}
input#panels-ipe-save,
input#panels-ipe-cancel {
background-position: 86% 0;
}
div.panels-ipe-pseudobutton-container a.panels-ipe-startedit {
padding-right: 34px;
padding-left: 10px;
background-position: 93% 9px;
}
div.panels-ipe-pseudobutton-container a.panels-ipe-change-layout {
padding-right: 34px;
padding-left: 10px;
background-position: 93% 9px;
}

View File

@@ -0,0 +1,559 @@
body.panels-ipe {
margin-bottom: 60px !important;
}
/* Hide the IPE toolbar on print output. */
@media print {
#panels-ipe-control-container {
display: none !important;
}
body.panels-ipe {
margin-top: 0 !important;
}
}
/* Hide the control container when the overlay is open. */
html.overlay-open #panels-ipe-control-container {
display: none !important;
}
html.overlay-open body.panels-ipe {
margin-top: 0 !important;
}
div.panels-ipe-handlebar-wrapper {
border-bottom: 1px solid #CCC;
}
.panels-ipe-editing div.panels-ipe-portlet-wrapper {
margin-top: 1em;
border: 1px solid #CCC;
width: 100%;
}
/* Hide empty panes when not editing them. */
.panels-ipe-empty-pane {
display: none;
}
.panels-ipe-editing .panels-ipe-empty-pane {
display: block;
}
.panels-ipe-editing div.panels-ipe-portlet-wrapper:hover {
border: 1px dashed #CCC;
}
.panels-ipe-editing .panels-ipe-sort-container {
min-height: 40px;
}
.panels-ipe-editing .panels-ipe-sort-container .ui-sortable-helper {
background: white;
}
.panels-ipe-editing div.panel-pane div.admin-links {
display: none !important;
}
.panels-ipe-editing .panels-ipe-sort-container .ui-sortable-placeholder {
border: 2px dashed #999;
background-color: #FFFF99;
margin: 1em 0;
-moz-border-radius: 0;
-khtml-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
}
div.panels-ipe-handlebar-wrapper ul {
float: left;
margin: 0;
padding: 0;
text-align: right;
}
div.panels-ipe-handlebar-wrapper li {
background: none;
list-style-type: none;
list-style-image: none;
margin: 0 .5em 0 0;
padding: 0;
float: left;
}
div.panels-ipe-draghandle,
div.panels-ipe-nodraghandle {
background: #E9E9E9;
background-image: linear-gradient(bottom, #D5D5D5 0%, #FCFCFC 100%);
background-image: -o-linear-gradient(bottom, #D5D5D5 0%, #FCFCFC 100%);
background-image: -moz-linear-gradient(bottom, #D5D5D5 0%, #FCFCFC 100%);
background-image: -webkit-linear-gradient(bottom, #D5D5D5 0%, #FCFCFC 100%);
background-image: -ms-linear-gradient(bottom, #D5D5D5 0%, #FCFCFC 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #D5D5D5),
color-stop(1, #FCFCFC)
);
padding: 8px 7px;
}
div.panels-ipe-draghandle span.panels-ipe-draghandle-icon {
display: block;
float: right;
cursor: move;
width: 34px;
height: 34px;
padding: 0 !important; /* override button defaults */
}
span.panels-ipe-draghandle-icon-inner {
display: block;
width: 34px;
height: 34px;
background: url(../images/icon-draggable.png) no-repeat 3px 3px;
}
div.panels-ipe-placeholder {
border: 2px dashed #999;
padding: .5em;
position: relative;
margin-top: .5em;
background-color: #ECFAFF;
color: #999;
font: 15px/1.3em "Open Sans", "Lucida Grande", Tahoma, Verdana, sans-serif;
text-transform: none;
letter-spacing: 0;
text-align: left;
word-spacing: 0;
}
div.panels-ipe-placeholder h3 {
font-weight: normal;
font-size: 15px;
width: 75px; /* In order to prevent the region title from running into the button, set a width. Initital width only--this will be changed by jQuery */
margin: 1.154em 0;
}
/* Hide editor-state-on elements initially */
.panels-ipe-on {
display: none;
}
.panels-ipe-editing .panels-ipe-on {
display: block;
}
/* Show editor-state-off elements initially */
.panels-ipe-off {
display: block;
}
div.panels-ipe-newblock {
-webkit-box-shadow: 0px 0 5px 5px #ECFAFF;
-moz-box-shadow: 0px 0 5px 5px #ECFAFF;
box-shadow: 0px 0 5px 5px #ECFAFF;
position: absolute;
right: 10px;
top: 50%;
margin-top: -18px; /* some initial guesses to help center the add button
panels_ipe.js will evaluate the width and get this pixel-perfect */
margin-left: -30px;
z-index: 99;
}
div.panels-ipe-newblock li {
padding: 0;
}
div.panels-ipe-handlebar-wrapper li a,
div.panels-ipe-dragtitle span,
div.panels-ipe-newblock a,
span.panels-ipe-draghandle-icon {
display: inline-block;
border: 1px solid #ccc;
padding: 0 8px 1px;
font: bold 12px/32px 'Open Sans', 'Lucida Sans', 'Lucida Grande', verdana sans-serif;
text-decoration: none;
height: 33px;
color: #666;
cursor: pointer;
outline: none;
-moz-border-radius: 3px;
-khtml-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
background: #FAFAFA;
background-image: linear-gradient(bottom, #E9EAEC 0%, #FAFAFA 100%);
background-image: -o-linear-gradient(bottom, #E9EAEC 0%, #FAFAFA 100%);
background-image: -moz-linear-gradient(bottom, #E9EAEC 0%, #FAFAFA 100%);
background-image: -webkit-linear-gradient(bottom, #E9EAEC 0%, #FAFAFA 100%);
background-image: -ms-linear-gradient(bottom, #E9EAEC 0%, #FAFAFA 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #E9EAEC),
color-stop(1, #FAFAFA)
);
-webkit-box-shadow: 0px 3px 3px 0px #d2d2d2;
-moz-box-shadow: 0px 3px 3px 0px #d2d2d2;
box-shadow: 0px 3px 3px 0px #d2d2d2;
}
div.panels-ipe-handlebar-wrapper li a span,
div.panels-ipe-newblock a span {
display: block;
height: 32px;
width: 18px;
background-position: center center;
background-repeat: no-repeat;
text-align: left;
text-indent: -9999em;
}
div.panels-ipe-handlebar-wrapper li.edit a span {
background-image: url(../images/icon-settings.png);
}
div.panels-ipe-handlebar-wrapper li.style a span,
div.panels-ipe-newblock a.style span {
background-image: url(../images/icon-style.png);
}
div.panels-ipe-newblock a.style {
margin-right: .5em;
}
div.panels-ipe-newblock a.add span {
background-image: url(../images/icon-add.png);
}
div.panels-ipe-handlebar-wrapper li.delete a span {
background-image: url(../images/icon-delete.png);
}
div.panels-ipe-handlebar-wrapper li a:hover,
div.panels-ipe-dragtitle span:hover,
div.panels-ipe-newblock a:hover,
span.panels-ipe-draghandle-icon:hover {
background: #E6E6E6;
background-image: linear-gradient(bottom, #C5C5C5 0%, #FAFAFA 100%);
background-image: -o-linear-gradient(bottom, #C5C5C5 0%, #FAFAFA 100%);
background-image: -moz-linear-gradient(bottom, #C5C5C5 0%, #FAFAFA 100%);
background-image: -webkit-linear-gradient(bottom, #C5C5C5 0%, #FAFAFA 100%);
background-image: -ms-linear-gradient(bottom, #C5C5C5 0%, #FAFAFA 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #C5C5C5),
color-stop(1, #FAFAFA)
);
}
div.panels-ipe-handlebar-wrapper li a:active,
div.panels-ipe-dragtitle span:active,
div.panels-ipe-newblock a:active,
span.panels-ipe-draghandle-icon:active {
outline: none;
background-image: linear-gradient(bottom, #FFFFFF 0%, #E9EAEC 100%);
background-image: -o-linear-gradient(bottom, #FFFFFF 0%, #E9EAEC 100%);
background-image: -moz-linear-gradient(bottom, #FFFFFF 0%, #E9EAEC 100%);
background-image: -webkit-linear-gradient(bottom, #FFFFFF 0%, #E9EAEC 100%);
background-image: -ms-linear-gradient(bottom, #FFFFFF 0%, #E9EAEC 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #FFFFFF),
color-stop(1, #E9EAEC)
);
-webkit-box-shadow: 0px 0px 0px 0px #fff;
-moz-box-shadow: 0px 0px 0px 0px #fff;
box-shadow: 0px 0px 0px 0px #fff;
}
.panels-ipe-editing .panels-ipe-portlet-content {
margin: 10px 3px;
overflow: hidden;
}
.panels-ipe-editing .panels-ipe-region {
border: transparent dotted 1px;
float: left;
width: 100%;
margin-bottom: 5px;
}
div.panels-ipe-draghandle {
border: none;
}
.ui-sortable-placeholder {
margin: 1em;
border: 1px dotted black;
visibility: visible !important;
height: 50px !important;
}
.ui-sortable-placeholder * {
visibility: hidden;
}
/** ============================================================================
* Controller form markup
*/
div#panels-ipe-control-container {
z-index: 99999;
position: fixed;
bottom: 0;
display: none;
background-color: #000;
padding: 0.5em 0;
width: 100%;
overflow: hidden;
-moz-box-shadow: 0 3px 20px #000;
-webkit-box-shadow: 0 3px 20px #000;
box-shadow: 0 3px 20px #000;
}
.ipe-throbber {
background-color: #232323;
background-image: url("../images/loading-small.gif");
background-position: center center;
background-repeat: no-repeat;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
height: 24px;
opacity: .9;
padding: 4px;
width: 24px;
/* Can't do center:50% middle: 50%, so approximate it for a typical window size. */
left: 49%;
position: fixed;
top: 48.5%;
z-index: 1001;
}
/* Hide the drupal system throbber image */
.ipe-throbber .throbber {
display: none;
}
div.panels-ipe-pseudobutton-container,
div.panels-ipe-control .form-submit {
cursor: pointer;
background: #666666;
background-image: linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: -o-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: -moz-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: -webkit-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: -ms-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #383838),
color-stop(1, #666666)
);
border: 0;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
color: #CCC;
display: inline-block;
font: bold 12px/33px "Open Sans", "Lucida Grande", Tahoma, Verdana, sans-serif;
height: 33px;
margin: 0 10px;
}
div.panels-ipe-control .form-submit {
padding: 0 0.8em 2px 34px;
}
div.panels-ipe-control input.panels-ipe-save, div.panels-ipe-control input.panels-ipe-cancel,
div.panels-ipe-control input.panels-ipe-save:hover, div.panels-ipe-control input.panels-ipe-cancel:hover,
div.panels-ipe-control input.panels-ipe-save:active, div.panels-ipe-control input.panels-ipe-cancel:active {
background-repeat: no-repeat;
}
div.panels-ipe-pseudobutton-container a {
height: 33px;
padding: 0 0.8em;
display: inline-block;
color: #CCC;
text-decoration: none;
}
div.panels-ipe-control input.panels-ipe-save {
background-image: url(../images/icon-save.png);
background-image: url(../images/icon-save.png), linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-save.png), -o-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-save.png), -moz-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-save.png), -webkit-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-save.png), -ms-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-save.png), -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #383838),
color-stop(1, #666666)
);
}
div.panels-ipe-control input.panels-ipe-cancel {
background-image: url(../images/icon-close.png);
background-image: url(../images/icon-close.png), linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-close.png), -o-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-close.png), -moz-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-close.png), -webkit-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-close.png), -ms-linear-gradient(bottom, #383838 0%, #666666 100%);
background-image: url(../images/icon-close.png), -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #383838),
color-stop(1, #666666)
);
}
div.panels-ipe-pseudobutton-container:hover,
div.panels-ipe-control .form-submit:hover {
background: #999999;
background-image: linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: -o-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: -moz-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: -webkit-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: -ms-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #3D3D3D),
color-stop(1, #999999)
);
color: #FFF;
}
div.panels-ipe-pseudobutton-container a:hover {
color: #FFF;
}
div.panels-ipe-control input.panels-ipe-cancel:hover {
background-image: url(../images/icon-close.png), linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-close.png), -o-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-close.png), -moz-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-close.png), -webkit-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-close.png), -ms-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-close.png), -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #3D3D3D),
color-stop(1, #999999)
);
}
div.panels-ipe-control input.panels-ipe-save:hover {
background-image: url(../images/icon-save.png), linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-save.png), -o-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-save.png), -moz-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-save.png), -webkit-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-save.png), -ms-linear-gradient(bottom, #3D3D3D 0%, #999999 100%);
background-image: url(../images/icon-save.png), -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #3D3D3D),
color-stop(1, #999999)
);
}
div.panels-ipe-pseudobutton-container:active,
div.panels-ipe-control .form-submit:active {
background: #333;
background-image: linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: -o-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: -moz-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: -webkit-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: -ms-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #616161),
color-stop(1, #333333)
);
color: #CCC;
}
div.panels-ipe-pseudobutton-container a:active {
color: #CCC;
}
div.panels-ipe-control input.panels-ipe-cancel:active {
background-image: url(../images/icon-close.png), linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-close.png), -o-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-close.png), -moz-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-close.png), -webkit-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-close.png), -ms-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-close.png), -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #616161),
color-stop(1, #333333)
);
}
div.panels-ipe-control input.panels-ipe-save:active {
background-image: url(../images/icon-save.png), linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-save.png), -o-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-save.png), -moz-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-save.png), -webkit-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-save.png), -ms-linear-gradient(bottom, #616161 0%, #333333 100%);
background-image: url(../images/icon-save.png), -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, #616161),
color-stop(1, #333333)
);
}
div.panels-ipe-pseudobutton-container a.panels-ipe-startedit {
padding-left: 34px;
background: url(../images/icon-configure.png) no-repeat 10px 9px;
}
div.panels-ipe-pseudobutton-container a.panels-ipe-change-layout {
padding-left: 34px;
background: url(../images/icon-change-layout.png) no-repeat 10px 9px;
}
div.panels-ipe-button-container {
margin: 0.3em 0.5em;
text-align: center;
}
form#panels-ipe-edit-control-form {
text-align: center;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,44 @@
<?php
/**
* @file
* Bulk export of panels_layouts objects generated by Bulk export module.
*/
/**
* Implements hook_default_panels_renderer_pipeline().
*/
function panels_ipe_default_panels_renderer_pipeline() {
$pipelines = array();
$pipeline = new stdClass;
$pipeline->disabled = FALSE; /* Edit this to true to make a default pipeline disabled initially */
$pipeline->api_version = 1;
$pipeline->name = 'ipe';
$pipeline->admin_title = t('In-Place Editor');
$pipeline->admin_description = t('Allows privileged users to update and rearrange the content while viewing this panel.');
$pipeline->weight = 0;
$pipeline->settings = array(
'renderers' => array(
0 => array(
'access' => array(
'plugins' => array(
0 => array(
'name' => 'perm',
'settings' => array(
'perm' => 'use panels in place editing',
),
'context' => 'logged-in-user',
),
),
'logic' => 'and',
),
'renderer' => 'ipe',
'options' => array(),
),
),
);
$pipelines[$pipeline->name] = $pipeline;
return $pipelines;
}

View File

@@ -0,0 +1,455 @@
// Ensure the $ alias is owned by jQuery.
(function($) {
// randomly lock a pane.
// @debug only
Drupal.settings.Panels = Drupal.settings.Panels || {};
Drupal.settings.Panels.RegionLock = {
10: { 'top': false, 'left': true, 'middle': true }
}
Drupal.PanelsIPE = {
editors: {},
bindClickDelete: function(context) {
$('a.pane-delete:not(.pane-delete-processed)', context)
.addClass('pane-delete-processed')
.click(function() {
if (confirm(Drupal.t('Remove this pane?'))) {
$(this).parents('div.panels-ipe-portlet-wrapper').fadeOut('medium', function() {
var $sortable = $(this).closest('.ui-sortable');
$(this).empty().remove();
$sortable.trigger('sortremove');
});
$(this).parents('div.panels-ipe-display-container').addClass('changed');
}
return false;
});
}
}
Drupal.behaviors.PanelsIPE = {
attach: function(context) {
for (var i in Drupal.settings.PanelsIPECacheKeys) {
var key = Drupal.settings.PanelsIPECacheKeys[i];
$('div#panels-ipe-display-' + key + ':not(.panels-ipe-processed)')
.addClass('panels-ipe-processed')
.each(function() {
// If we're replacing an old IPE, clean it up a little.
if (Drupal.PanelsIPE.editors[key]) {
Drupal.PanelsIPE.editors[key].editing = false;
}
Drupal.PanelsIPE.editors[key] = new DrupalPanelsIPE(key);
Drupal.PanelsIPE.editors[key].showContainer();
});
}
$('.panels-ipe-hide-bar').once('panels-ipe-hide-bar-processed').click(function() {
Drupal.PanelsIPE.editors[key].hideContainer();
});
Drupal.PanelsIPE.bindClickDelete(context);
}
};
/**
* Base object (class) definition for the Panels In-Place Editor.
*
* A new instance of this object is instanciated for every unique IPE on a given
* page.
*
* Note that this form is provisional, and we hope to replace it with a more
* flexible, loosely-coupled model that utilizes separate controllers for the
* discrete IPE elements. This will result in greater IPE flexibility.
*/
function DrupalPanelsIPE(cache_key, cfg) {
cfg = cfg || {};
var ipe = this;
this.key = cache_key;
this.lockPath = null;
this.state = {};
this.container = $('#panels-ipe-control-container');
this.control = $('div#panels-ipe-control-' + cache_key);
this.initButton = $('div.panels-ipe-startedit', this.control);
this.cfg = cfg;
this.changed = false;
this.sortableOptions = $.extend({
opacity: 0.75, // opacity of sortable while sorting
items: 'div.panels-ipe-portlet-wrapper',
handle: 'div.panels-ipe-draghandle',
cancel: '.panels-ipe-nodrag',
dropOnEmpty: true
}, cfg.sortableOptions || {});
this.regions = [];
this.sortables = {};
$(document).bind('CToolsDetachBehaviors', function() {
// If the IPE is off and the container is not visible, then we need
// to reshow the container on modal close.
if (!$('.panels-ipe-form-container', ipe.control).html() && !ipe.container.is(':visible')) {
ipe.showContainer();
ipe.cancelLock();
}
// If the IPE is on and we've hidden the bar for a modal, we need to
// re-display it.
if (ipe.topParent && ipe.topParent.hasClass('panels-ipe-editing') && ipe.container.is(':not(visible)')) {
ipe.showContainer();
}
});
// If a user navigates away from a locked IPE, cancel the lock in the background.
$(window).bind('beforeunload', function() {
if (!ipe.editing) {
return;
}
if (ipe.topParent && ipe.topParent.hasClass('changed')) {
ipe.changed = true;
}
if (ipe.changed) {
return Drupal.t('This will discard all unsaved changes. Are you sure?');
}
});
// If a user navigates away from a locked IPE, cancel the lock in the background.
$(window).bind('unload', function() {
ipe.cancelLock(true);
});
/**
* If something caused us to abort what we were doing, send a background
* cancel lock request to the server so that we do not leave stale locks
* hanging around.
*/
this.cancelLock = function(sync) {
// If there's a lockpath and an ajax available, inform server to clear lock.
// We borrow the ajax options from the customize this page link.
if (ipe.lockPath && Drupal.ajax['panels-ipe-customize-page']) {
var ajaxOptions = {
type: 'POST',
url: ipe.lockPath
}
if (sync) {
ajaxOptions.async = false;
}
// Make sure we don't somehow get another one:
ipe.lockPath = null;
// Send the request. This is synchronous to prevent being cancelled.
$.ajax(ajaxOptions);
}
}
this.activateSortable = function(event, ui) {
if (!Drupal.settings.Panels || !Drupal.settings.Panels.RegionLock) {
// don't bother if there are no region locks in play.
return;
}
var region = event.data.region;
var paneId = ui.item.attr('id').replace('panels-ipe-paneid-', '');
var disabledRegions = false;
// Determined if this pane is locked out of this region.
if (!Drupal.settings.Panels.RegionLock[paneId] || Drupal.settings.Panels.RegionLock[paneId][region]) {
ipe.sortables[region].sortable('enable');
ipe.sortables[region].sortable('refresh');
}
else {
disabledRegions = true;
ipe.sortables[region].sortable('disable');
ipe.sortables[region].sortable('refresh');
}
// If we disabled regions, we need to
if (disabledRegions) {
$(event.srcElement).bind('dragstop', function(event, ui) {
// Go through
});
}
};
// When dragging is stopped, we need to ensure all sortable regions are enabled.
this.enableRegions = function(event, ui) {
for (var i in ipe.regions) {
ipe.sortables[ipe.regions[i]].sortable('enable');
ipe.sortables[ipe.regions[i]].sortable('refresh');
}
}
this.initSorting = function() {
var $region = $(this).parents('.panels-ipe-region');
var region = $region.attr('id').replace('panels-ipe-regionid-', '');
ipe.sortables[region] = $(this).sortable(ipe.sortableOptions);
ipe.regions.push(region);
$(this).bind('sortactivate', {region: region}, ipe.activateSortable);
};
this.initEditing = function(formdata) {
ipe.editing = true;
ipe.topParent = $('div#panels-ipe-display-' + cache_key);
ipe.backup = this.topParent.clone();
// See http://jqueryui.com/demos/sortable/ for details on the configuration
// parameters used here.
ipe.changed = false;
$('div.panels-ipe-sort-container', ipe.topParent).each(ipe.initSorting);
// Since the connectWith option only does a one-way hookup, iterate over
// all sortable regions to connect them with one another.
$('div.panels-ipe-sort-container', ipe.topParent)
.sortable('option', 'connectWith', ['div.panels-ipe-sort-container']);
$('div.panels-ipe-sort-container', ipe.topParent).bind('sortupdate', function() {
ipe.changed = true;
});
$('div.panels-ipe-sort-container', ipe.topParent).bind('sortstop', this.enableRegions);
$('.panels-ipe-form-container', ipe.control).append(formdata);
$('input:submit:not(.ajax-processed)', ipe.control).addClass('ajax-processed').each(function() {
var element_settings = {};
element_settings.url = $(this.form).attr('action');
element_settings.setClick = true;
element_settings.event = 'click';
element_settings.progress = { 'type': 'throbber' };
element_settings.ipe_cache_key = cache_key;
var base = $(this).attr('id');
Drupal.ajax[ipe.base] = new Drupal.ajax(base, this, element_settings);
});
// Perform visual effects in a particular sequence.
// .show() + .hide() cannot have speeds associated with them, otherwise
// it clears out inline styles.
$('.panels-ipe-on').show();
ipe.showForm();
ipe.topParent.addClass('panels-ipe-editing');
};
this.hideContainer = function() {
ipe.container.slideUp('fast');
};
this.showContainer = function() {
ipe.container.slideDown('normal');
};
this.showButtons = function() {
$('.panels-ipe-form-container').hide();
$('.panels-ipe-button-container').show();
ipe.showContainer();
};
this.showForm = function() {
$('.panels-ipe-button-container').hide();
$('.panels-ipe-form-container').show();
ipe.showContainer();
};
this.endEditing = function() {
ipe.editing = false;
ipe.lockPath = null;
$('.panels-ipe-form-container').empty();
// Re-show all the IPE non-editing meta-elements
$('div.panels-ipe-off').show('fast');
ipe.showButtons();
// Re-hide all the IPE meta-elements
$('div.panels-ipe-on').hide();
$('.panels-ipe-editing').removeClass('panels-ipe-editing');
$('div.panels-ipe-sort-container', ipe.topParent).sortable("destroy");
};
this.saveEditing = function() {
$('div.panels-ipe-region', ipe.topParent).each(function() {
var val = '';
var region = $(this).attr('id').split('panels-ipe-regionid-')[1];
$(this).find('div.panels-ipe-portlet-wrapper').each(function() {
var id = $(this).attr('id').split('panels-ipe-paneid-')[1];
if (id) {
if (val) {
val += ',';
}
val += id;
}
});
$('input[name="panel[pane][' + region + ']"]', ipe.control).val(val);
});
}
this.cancelIPE = function() {
ipe.hideContainer();
ipe.topParent.fadeOut('medium', function() {
ipe.topParent.replaceWith(ipe.backup.clone());
ipe.topParent = $('div#panels-ipe-display-' + ipe.key);
// Processing of these things got lost in the cloning, but the classes remained behind.
// @todo this isn't ideal but I can't seem to figure out how to keep an unprocessed backup
// that will later get processed.
$('.ctools-use-modal-processed', ipe.topParent).removeClass('ctools-use-modal-processed');
$('.pane-delete-processed', ipe.topParent).removeClass('pane-delete-processed');
ipe.topParent.fadeIn('medium');
Drupal.attachBehaviors();
});
};
this.cancelEditing = function() {
if (ipe.topParent.hasClass('changed')) {
ipe.changed = true;
}
if (!ipe.changed || confirm(Drupal.t('This will discard all unsaved changes. Are you sure?'))) {
this.cancelIPE();
return true;
}
else {
// Cancel the submission.
return false;
}
};
this.createSortContainers = function() {
$('div.panels-ipe-region', this.topParent).each(function() {
$('div.panels-ipe-portlet-marker', this).parent()
.wrapInner('<div class="panels-ipe-sort-container" />');
// Move our gadgets outside of the sort container so that sortables
// cannot be placed after them.
$('div.panels-ipe-portlet-static', this).each(function() {
$(this).prependTo($(this).parent().parent());
});
// Also remove the last panel separator.
$('div.panel-separator', this).filter(':last').remove();
});
}
this.createSortContainers();
};
$(function() {
Drupal.ajax.prototype.commands.initIPE = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].initEditing(data.data);
Drupal.PanelsIPE.editors[data.key].lockPath = data.lockPath;
}
Drupal.attachBehaviors();
};
Drupal.ajax.prototype.commands.IPEsetLockState = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].lockPath = data.lockPath;
}
};
Drupal.ajax.prototype.commands.addNewPane = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].changed = true;
}
};
Drupal.ajax.prototype.commands.cancelIPE = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].cancelIPE();
Drupal.PanelsIPE.editors[data.key].endEditing();
}
};
Drupal.ajax.prototype.commands.unlockIPE = function(ajax, data, status) {
if (confirm(data.message)) {
var ajaxOptions = ajax.options;
ajaxOptions.url = data.break_path;
$.ajax(ajaxOptions);
}
else {
Drupal.PanelsIPE.editors[data.key].endEditing();
}
};
Drupal.ajax.prototype.commands.endIPE = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].endEditing();
}
};
/**
* Override the eventResponse on ajax.js so we can add a little extra
* behavior.
*/
Drupal.ajax.prototype.ipeReplacedEventResponse = Drupal.ajax.prototype.eventResponse;
Drupal.ajax.prototype.eventResponse = function (element, event) {
if (element.ipeCancelThis) {
element.ipeCancelThis = null;
return false;
}
if ($(this.element).attr('id') == 'panels-ipe-cancel') {
if (!Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].cancelEditing()) {
return false;
}
}
var retval = this.ipeReplacedEventResponse(element, event);
if (this.ajaxing && this.element_settings.ipe_cache_key) {
// Move the throbber so that it appears outside our container.
if (this.progress.element) {
$(this.progress.element).addClass('ipe-throbber').appendTo($('body'));
}
Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].hideContainer();
}
// @TODO $('#panels-ipe-throbber-backdrop').remove();
return retval;
};
/**
* Override the eventResponse on ajax.js so we can add a little extra
* behavior.
*/
Drupal.ajax.prototype.ipeReplacedError = Drupal.ajax.prototype.error;
Drupal.ajax.prototype.error = function (response, uri) {
var retval = this.ipeReplacedError(response, uri);
if (this.element_settings.ipe_cache_key) {
Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].showContainer();
}
};
Drupal.ajax.prototype.ipeReplacedBeforeSerialize = Drupal.ajax.prototype.beforeSerialize;
Drupal.ajax.prototype.beforeSerialize = function (element_settings, options) {
if ($(this.element).hasClass('panels-ipe-save')) {
Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].saveEditing();
};
return this.ipeReplacedBeforeSerialize(element_settings, options);
};
});
/**
* Apply margin to bottom of the page.
*
* Note that directly applying marginBottom does not work in IE. To prevent
* flickering/jumping page content with client-side caching, this is a regular
* Drupal behavior.
*
* @see admin_menu.js via https://drupal.org/project/admin_menu
*/
Drupal.behaviors.panelsIpeMarginBottom = {
attach: function () {
$('body:not(.panels-ipe)').addClass('panels-ipe');
}
};
})(jQuery);

View File

@@ -0,0 +1,14 @@
name = Panels In-Place Editor
description = Provide a UI for managing some Panels directly on the frontend, instead of having to use the backend.
package = "Panels"
dependencies[] = panels
core = 7.x
configure = admin/structure/panels
files[] = panels_ipe.module
; Information added by drupal.org packaging script on 2013-03-02
version = "7.x-3.3+39-dev"
core = "7.x"
project = "panels"
datestamp = "1362187383"

View File

@@ -0,0 +1,266 @@
<?php
/**
* Implementation of hook_ctools_plugin_directory().
*/
function panels_ipe_ctools_plugin_directory($module, $plugin) {
if ($module == 'panels' && $plugin == 'display_renderers') {
return 'plugins/' . $plugin;
}
}
/**
* Implementation of hook_ctools_plugin_api().
*
* Inform CTools about version information for various plugins implemented by
* Panels.
*
* @param string $owner
* The system name of the module owning the API about which information is
* being requested.
* @param string $api
* The name of the API about which information is being requested.
*/
function panels_ipe_ctools_plugin_api($owner, $api) {
if ($owner == 'panels' && $api == 'pipelines') {
return array(
'version' => 1,
'path' => drupal_get_path('module', 'panels_ipe') . '/includes',
);
}
}
/**
* Implementation of hook_theme().
*/
function panels_ipe_theme() {
return array(
'panels_ipe_pane_wrapper' => array(
'variables' => array('output' => NULL, 'pane' => NULL, 'display' => NULL, 'renderer' => NULL),
),
'panels_ipe_region_wrapper' => array(
'variables' => array('output' => NULL, 'region_id' => NULL, 'display' => NULL, 'controls' => NULL, 'renderer' => NULL),
),
'panels_ipe_add_pane_button' => array(
'variables' => array('region_id' => NULL, 'display' => NULL, 'renderer' => NULL),
),
'panels_ipe_placeholder_pane' => array(
'variables' => array('region_id' => NULL, 'region_title' => NULL),
),
'panels_ipe_dnd_form_container' => array(
'variables' => array('link' => NULL, 'cache_key' => NULL, 'display' => NULL),
),
'panels_ipe_toolbar' => array(
'variables' => array('buttons' => NULL),
),
);
}
/**
* Theme the 'placeholder' pane, which is shown on an active IPE when no panes
* live in that region.
*
* @param string $region_id
* @param string $region_title
*/
function theme_panels_ipe_placeholder_pane($vars) {
$region_id = $vars['region_id'];
$region_title = $vars['region_title'];
$output = '<div class="panels-ipe-placeholder-content">';
$output .= "<h3>$region_title</h3>";
$output .= '</div>';
return $output;
}
function template_preprocess_panels_ipe_pane_wrapper(&$vars) {
$pane = $vars['pane'];
$display = $vars['display'];
$renderer = $vars['renderer'];
$content_type = ctools_get_content_type($pane->type);
$subtype = ctools_content_get_subtype($content_type, $pane->subtype);
$vars['links'] = array();
if (ctools_content_editable($content_type, $subtype, $pane->configuration)) {
$vars['links']['edit'] = array(
'title' => isset($content_type['edit text']) ? '<span>' . $content_type['edit text'] . '</span>' : '<span>' . t('Settings') . '</span>',
'href' => $renderer->get_url('edit-pane', $pane->pid),
'html' => TRUE,
'attributes' => array(
'class' => array('ctools-use-modal', 'panels-ipe-hide-bar'),
'title' => isset($content_type['edit text']) ? $content_type['edit text'] : t('Settings'),
// 'id' => "pane-edit-panel-pane-$pane->pid",
),
);
}
// Add option to configure style in IPE
if (user_access('administer panels styles')) {
$vars['links']['style'] = array(
'title' => '<span>' . t('Style') . '</span>',
'href' => $renderer->get_url('style-type', 'pane', $pane->pid),
'html' => TRUE,
'attributes' => array(
'class' => array('ctools-use-modal', 'panels-ipe-hide-bar'),
'title' => t('Style'),
),
);
}
// Deleting is managed entirely in the js; this is just an attachment point
// for it
$vars['links']['delete'] = array(
'title' => '<span>' . t('Delete') . '</span>',
'href' => '#',
'html' => TRUE,
'attributes' => array(
'class' => 'pane-delete',
'id' => "pane-delete-panel-pane-$pane->pid",
'title' => t('Delete'),
),
);
$context = array(
'pane' => $pane,
'display' => $display,
'renderer' => $renderer
);
drupal_alter('panels_ipe_pane_links', $vars['links'], $context);
}
function theme_panels_ipe_pane_wrapper($vars) {
$output = $vars['output'];
$pane = $vars['pane'];
$attributes = array(
'class' => 'panels-ipe-linkbar',
);
$links = theme('links', array('links' => $vars['links'], 'attributes' => $attributes));
if (!empty($pane->locks['type']) && $pane->locks['type'] == 'immovable') {
$links = '<div class="panels-ipe-dragbar panels-ipe-nodraghandle clearfix">' . $links . '</div>';
}
else {
$links = '<div class="panels-ipe-dragbar panels-ipe-draghandle clearfix">' . $links . '<span class="panels-ipe-draghandle-icon"><span class="panels-ipe-draghandle-icon-inner"></span></span></div>';
}
$handlebar = '<div class="panels-ipe-handlebar-wrapper panels-ipe-on">' . $links . '</div>';
return $handlebar . $output;
}
function theme_panels_ipe_region_wrapper($vars) {
return $vars['controls'] . $vars['output'];
}
function template_preprocess_panels_ipe_add_pane_button(&$vars) {
$region_id = $vars['region_id'];
$display = $vars['display'];
$renderer = $vars['renderer'];
$vars['links'] = '';
// Add option to configure style in IPE
if (user_access('administer panels styles')) {
$vars['links']['style'] = array(
'title' => '<span>' . t('Region style') . '</span>',
'href' => $renderer->get_url('style-type', 'region', $region_id),
'html' => TRUE,
'attributes' => array(
'class' => array('ctools-use-modal', 'panels-ipe-hide-bar', 'style'),
'title' => t('Region style'),
),
);
}
// Add option to add items in the IPE
$vars['links']['add-pane'] = array(
'title' => '<span>' . t('Add new pane') . '</span>',
'href' => $renderer->get_url('select-content', $region_id),
'attributes' => array(
'class' => array('ctools-use-modal', 'add', 'panels-ipe-hide-bar'),
'title' => t('Add new pane'),
),
'html' => TRUE,
);
$context = array(
'region_id' => $region_id,
'display' => $display,
'renderer' => $renderer,
);
drupal_alter('panels_ipe_region_links', $vars['links'], $context);
}
function theme_panels_ipe_add_pane_button($vars) {
$attributes = array(
'class' => array('panels-ipe-linkbar', 'inline'),
);
$links = theme('links', array('links' => $vars['links'], 'attributes' => $attributes));
return '<div class="panels-ipe-newblock panels-ipe-on">' . $links . '</div>';
}
/**
* @deprecated
*/
function panels_ipe_get_cache_key($key = NULL) {
return array();
}
/**
* Add a button to the IPE toolbar.
*/
function panels_ipe_toolbar_add_button($cache_key, $id, $button) {
$buttons = &drupal_static('panels_ipe_toolbar_buttons', array());
$buttons[$cache_key][$id] = $button;
}
/**
* Implementation of hook_footer()
*
* Adds the IPE control container.
*
* @param unknown_type $main
*/
function panels_ipe_page_alter(&$page) {
$buttons = &drupal_static('panels_ipe_toolbar_buttons', array());
if (empty($buttons)) {
return;
}
$output = theme('panels_ipe_toolbar', array('buttons' => $buttons));
$page['page_bottom']['panels_ipe'] = array(
'#markup' => $output,
);
}
function theme_panels_ipe_toolbar($vars) {
$buttons = $vars['buttons'];
$output = "<div id='panels-ipe-control-container' class='clearfix'>";
foreach ($buttons as $key => $ipe_buttons) {
$output .= "<div id='panels-ipe-control-$key' class='panels-ipe-control'>";
// Controls in this container will appear when the IPE is not on.
$output .= '<div class="panels-ipe-button-container clearfix">';
foreach ($ipe_buttons as $button) {
$output .= is_string($button) ? $button : drupal_render($button);
}
$output .= '</div>';
// Controls in this container will appear when the IPE is on. It is usually
// filled via AJAX.
$output .= '<div class="panels-ipe-form-container clearfix"></div>';
$output .= '</div>';
}
$output .= "</div>";
return $output;
}

View File

@@ -0,0 +1,5 @@
<?php
$plugin = array(
'renderer' => 'panels_renderer_ipe',
);

View File

@@ -0,0 +1,462 @@
<?php
/**
* Renderer class for all In-Place Editor (IPE) behavior.
*/
class panels_renderer_ipe extends panels_renderer_editor {
// The IPE operates in normal render mode, not admin mode.
var $admin = FALSE;
function render() {
$output = parent::render();
return "<div id='panels-ipe-display-{$this->clean_key}' class='panels-ipe-display-container'>$output</div>";
}
function add_meta() {
ctools_include('display-edit', 'panels');
ctools_include('content');
if (empty($this->display->cache_key)) {
$this->cache = panels_edit_cache_get_default($this->display);
}
// @todo we may need an else to load the cache, but I am not sure we
// actually need to load it if we already have our cache key, and doing
// so is a waste of resources.
ctools_include('cleanstring');
$this->clean_key = ctools_cleanstring($this->display->cache_key);
$button = array(
'#type' => 'link',
'#title' => t('Customize this page'),
'#href' => $this->get_url('save_form'),
'#id' => 'panels-ipe-customize-page',
'#attributes' => array(
'class' => array('panels-ipe-startedit', 'panels-ipe-pseudobutton'),
),
'#ajax' => array(
'progress' => 'throbber',
'ipe_cache_key' => $this->clean_key,
),
'#prefix' => '<div class="panels-ipe-pseudobutton-container">',
'#suffix' => '</div>',
);
panels_ipe_toolbar_add_button($this->clean_key, 'panels-ipe-startedit', $button);
// @todo this actually should be an IPE setting instead.
if (user_access('change layouts in place editing')) {
$button = array(
'#type' => 'link',
'#title' => t('Change layout'),
'#href' => $this->get_url('change_layout'),
'#attributes' => array(
'class' => array('panels-ipe-change-layout', 'panels-ipe-pseudobutton', 'ctools-modal-layout'),
),
'#ajax' => array(
'progress' => 'throbber',
'ipe_cache_key' => $this->clean_key,
),
'#prefix' => '<div class="panels-ipe-pseudobutton-container">',
'#suffix' => '</div>',
);
panels_ipe_toolbar_add_button($this->clean_key, 'panels-ipe-change-layout', $button);
}
ctools_include('ajax');
ctools_include('modal');
ctools_modal_add_js();
ctools_add_css('panels_dnd', 'panels');
ctools_add_css('panels_admin', 'panels');
ctools_add_js('panels_ipe', 'panels_ipe');
ctools_add_css('panels_ipe', 'panels_ipe');
drupal_add_js(array('PanelsIPECacheKeys' => array($this->clean_key)), 'setting');
drupal_add_library('system', 'ui.draggable');
drupal_add_library('system', 'ui.droppable');
drupal_add_library('system', 'ui.sortable');
parent::add_meta();
}
/**
* Override & call the parent, then pass output through to the dnd wrapper
* theme function.
*
* @param $pane
*/
function render_pane(&$pane) {
$output = parent::render_pane($pane);
if (empty($output)) {
return;
}
// If there are region locks, add them.
if (!empty($pane->locks['type']) && $pane->locks['type'] == 'regions') {
static $key = NULL;
$javascript = &drupal_static('drupal_add_js', array());
// drupal_add_js breaks as we add these, but we can't just lump them
// together because panes can be rendered independently. So game the system:
if (empty($key)) {
$settings['Panels']['RegionLock'][$pane->pid] = $pane->locks['regions'];
drupal_add_js($settings, 'setting');
// These are just added via [] so we have to grab the last one
// and reference it.
$keys = array_keys($javascript['settings']['data']);
$key = end($keys);
}
else {
$javascript['settings']['data'][$key]['Panels']['RegionLock'][$pane->pid] = $pane->locks['regions'];
}
}
if (empty($pane->IPE_empty)) {
// Add an inner layer wrapper to the pane content before placing it into
// draggable portlet
$output = "<div class=\"panels-ipe-portlet-content\">$output</div>";
}
else {
$output = "<div class=\"panels-ipe-portlet-content panels-ipe-empty-pane\">$output</div>";
}
// Hand it off to the plugin/theme for placing draggers/buttons
$output = theme('panels_ipe_pane_wrapper', array('output' => $output, 'pane' => $pane, 'display' => $this->display, 'renderer' => $this));
if (!empty($pane->locks['type']) && $pane->locks['type'] == 'immovable') {
return "<div id=\"panels-ipe-paneid-{$pane->pid}\" class=\"panels-ipe-nodrag panels-ipe-portlet-wrapper panels-ipe-portlet-marker\">" . $output . "</div>";
}
return "<div id=\"panels-ipe-paneid-{$pane->pid}\" class=\"panels-ipe-portlet-wrapper panels-ipe-portlet-marker\">" . $output . "</div>";
}
function prepare_panes($panes) {
// Set to admin mode just for this to ensure all panes are represented.
$this->admin = TRUE;
$panes = parent::prepare_panes($panes);
$this->admin = FALSE;
}
function render_pane_content(&$pane) {
if (!empty($pane->shown) && panels_pane_access($pane, $this->display)) {
$content = parent::render_pane_content($pane);
}
// Ensure that empty panes have some content.
if (empty($content) || empty($content->content)) {
if (empty($content)) {
$content = new stdClass();
}
// Get the administrative title.
$content_type = ctools_get_content_type($pane->type);
$title = ctools_content_admin_title($content_type, $pane->subtype, $pane->configuration, $this->display->context);
$content->content = t('Placeholder for empty or inaccessible "@title"', array('@title' => html_entity_decode($title, ENT_QUOTES)));
// Add these to prevent notices.
$content->type = 'panels_ipe';
$content->subtype = 'panels_ipe';
$pane->IPE_empty = TRUE;
}
return $content;
}
/**
* Add an 'empty' pane placeholder above all the normal panes.
*
* @param $region_id
* @param $panes
*/
function render_region($region_id, $panes) {
// Generate this region's 'empty' placeholder pane from the IPE plugin.
$empty_ph = theme('panels_ipe_placeholder_pane', array('region_id' => $region_id, 'region_title' => $this->plugins['layout']['regions'][$region_id]));
// Wrap the placeholder in some guaranteed markup.
$control = '<div class="panels-ipe-placeholder panels-ipe-on panels-ipe-portlet-marker panels-ipe-portlet-static">' . $empty_ph . theme('panels_ipe_add_pane_button', array('region_id' => $region_id, 'display' => $this->display, 'renderer' => $this)) . "</div>";
$output = parent::render_region($region_id, $panes);
$output = theme('panels_ipe_region_wrapper', array('output' => $output, 'region_id' => $region_id, 'display' => $this->display, 'controls' => $control, 'renderer' => $this));
$classes = 'panels-ipe-region';
return "<div id='panels-ipe-regionid-$region_id' class='panels-ipe-region'>$output</div>";
}
/**
* This is a generic lock test.
*/
function ipe_test_lock($url, $break) {
if (!empty($this->cache->locked)) {
if ($break != 'break') {
$account = user_load($this->cache->locked->uid);
$name = format_username($account);
$lock_age = format_interval(time() - $this->cache->locked->updated);
$message = t("This panel is being edited by user !user, and is therefore locked from editing by others. This lock is !age old.\n\nClick OK to break this lock and discard any changes made by !user.", array('!user' => $name, '!age' => $lock_age));
$this->commands[] = array(
'command' => 'unlockIPE',
'message' => $message,
'break_path' => url($this->get_url($url, 'break')),
'key' => $this->clean_key,
);
return TRUE;
}
// Break the lock.
panels_edit_cache_break_lock($this->cache);
}
}
/**
* AJAX callback to unlock the IPE.
*
* This is called whenever something server side determines that editing
* has stopped and cleans up no longer needed locks.
*
* It has no visible return value as this is considered a background task
* and the client side has already given all indications that things are
* now in a 'normal' state.
*/
function ajax_unlock_ipe() {
panels_edit_cache_clear($this->cache);
$this->commands[] = array();
}
/**
* AJAX entry point to create the controller form for an IPE.
*/
function ajax_save_form($break = NULL) {
if ($this->ipe_test_lock('save-form', $break)) {
return;
}
// Reset the $_POST['ajax_html_ids'] values to preserve
// proper IDs on form elements when they are rebuilt
// by the Panels IPE without refreshing the page
$_POST['ajax_html_ids'] = array();
$form_state = array(
'renderer' => $this,
'display' => &$this->display,
'content_types' => $this->cache->content_types,
'rerender' => FALSE,
'no_redirect' => TRUE,
// Panels needs this to make sure that the layout gets callbacks
'layout' => $this->plugins['layout'],
);
$output = drupal_build_form('panels_ipe_edit_control_form', $form_state);
if (empty($form_state['executed'])) {
// At this point, we want to save the cache to ensure that we have a lock.
$this->cache->ipe_locked = TRUE;
panels_edit_cache_set($this->cache);
$this->commands[] = array(
'command' => 'initIPE',
'key' => $this->clean_key,
'data' => drupal_render($output),
'lockPath' => url($this->get_url('unlock_ipe')),
);
return;
}
// Check to see if we have a lock that was broken. If so we need to
// inform the user and abort.
if (empty($this->cache->ipe_locked)) {
$this->commands[] = ajax_command_alert(t('A lock you had has been externally broken, and all your changes have been reverted.'));
$this->commands[] = array(
'command' => 'cancelIPE',
'key' => $this->clean_key,
);
return;
}
// Otherwise it was submitted.
if (!empty($form_state['clicked_button']['#save-display'])) {
// Saved. Save the cache.
panels_edit_cache_save($this->cache);
// A rerender should fix IDs on added panes as well as ensure style changes are
// rendered.
$this->meta_location = 'inline';
$this->commands[] = ajax_command_replace("#panels-ipe-display-{$this->clean_key}", panels_render_display($this->display, $this));
}
else {
// Cancelled. Clear the cache.
panels_edit_cache_clear($this->cache);
}
$this->commands[] = array(
'command' => 'endIPE',
'key' => $this->clean_key,
);
}
/**
* AJAX entry point to create the controller form for an IPE.
*/
function ajax_change_layout($break = NULL) {
if ($this->ipe_test_lock('change_layout', $break)) {
return;
}
// At this point, we want to save the cache to ensure that we have a lock.
$this->cache->ipe_locked = TRUE;
panels_edit_cache_set($this->cache);
ctools_include('plugins', 'panels');
ctools_include('common', 'panels');
// @todo figure out a solution for this, it's critical
if (isset($this->display->allowed_layouts)) {
$layouts = $this->display->allowed_layouts;
}
else {
$layouts = panels_common_get_allowed_layouts('panels_page');
}
// Filter out builders
$layouts = array_filter($layouts, '_panels_builder_filter');
// Define the current layout
$current_layout = $this->plugins['layout']['name'];
$output = panels_common_print_layout_links($layouts, $this->get_url('set_layout'), array('attributes' => array('class' => array('use-ajax'))), $current_layout);
$this->commands[] = ctools_modal_command_display(t('Change layout'), $output);
$this->commands[] = array(
'command' => 'IPEsetLockState',
'key' => $this->clean_key,
'lockPath' => url($this->get_url('unlock_ipe')),
);
}
function ajax_set_layout($layout) {
ctools_include('context');
ctools_include('display-layout', 'panels');
$form_state = array(
'layout' => $layout,
'display' => $this->display,
'finish' => t('Save'),
'no_redirect' => TRUE,
);
// Reset the $_POST['ajax_html_ids'] values to preserve
// proper IDs on form elements when they are rebuilt
// by the Panels IPE without refreshing the page
$_POST['ajax_html_ids'] = array();
$output = drupal_build_form('panels_change_layout', $form_state);
$output = drupal_render($output);
if (!empty($form_state['executed'])) {
if (isset($form_state['back'])) {
return $this->ajax_change_layout();
}
if (!empty($form_state['clicked_button']['#save-display'])) {
// Saved. Save the cache.
panels_edit_cache_save($this->cache);
$this->display->skip_cache;
// Since the layout changed, we have to update these things in the
// renderer in order to get the right settings.
$layout = panels_get_layout($this->display->layout);
$this->plugins['layout'] = $layout;
if (!isset($layout['regions'])) {
$this->plugins['layout']['regions'] = panels_get_regions($layout, $this->display);
}
$this->meta_location = 'inline';
$this->commands[] = ajax_command_replace("#panels-ipe-display-{$this->clean_key}", panels_render_display($this->display, $this));
$this->commands[] = ctools_modal_command_dismiss();
return;
}
}
$this->commands[] = ctools_modal_command_display(t('Change layout'), $output);
}
/**
* Create a command array to redraw a pane.
*/
function command_update_pane($pid) {
if (is_object($pid)) {
$pane = $pid;
}
else {
$pane = $this->display->content[$pid];
}
$this->commands[] = ajax_command_replace("#panels-ipe-paneid-$pane->pid", $this->render_pane($pane));
$this->commands[] = ajax_command_changed("#panels-ipe-display-{$this->clean_key}");
}
/**
* Create a command array to add a new pane.
*/
function command_add_pane($pid) {
if (is_object($pid)) {
$pane = $pid;
}
else {
$pane = $this->display->content[$pid];
}
$this->commands[] = ajax_command_prepend("#panels-ipe-regionid-{$pane->panel} div.panels-ipe-sort-container", $this->render_pane($pane));
$this->commands[] = ajax_command_changed("#panels-ipe-display-{$this->clean_key}");
$this->commands[] = array(
'command' => 'addNewPane',
'key' => $this->clean_key,
);
}
}
/**
* FAPI callback to create the Save/Cancel form for the IPE.
*/
function panels_ipe_edit_control_form($form, &$form_state) {
$display = &$form_state['display'];
// @todo -- this should be unnecessary as we ensure cache_key is set in add_meta()
// $display->cache_key = isset($display->cache_key) ? $display->cache_key : $display->did;
// Annoyingly, theme doesn't have access to form_state so we have to do this.
$form['#display'] = $display;
$layout = panels_get_layout($display->layout);
$layout_panels = panels_get_regions($layout, $display);
$form['panel'] = array('#tree' => TRUE);
$form['panel']['pane'] = array('#tree' => TRUE);
foreach ($layout_panels as $panel_id => $title) {
// Make sure we at least have an empty array for all possible locations.
if (!isset($display->panels[$panel_id])) {
$display->panels[$panel_id] = array();
}
$form['panel']['pane'][$panel_id] = array(
// Use 'hidden' instead of 'value' so the js can access it.
'#type' => 'hidden',
'#default_value' => implode(',', (array) $display->panels[$panel_id]),
);
}
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#id' => 'panels-ipe-save',
'#attributes' => array('class' => array('panels-ipe-save')),
'#submit' => array('panels_edit_display_form_submit'),
'#save-display' => TRUE,
);
$form['buttons']['cancel'] = array(
'#type' => 'submit',
'#id' => 'panels-ipe-cancel',
'#attributes' => array('class' => array('panels-ipe-cancel')),
'#value' => t('Cancel'),
);
return $form;
}

View File

@@ -0,0 +1,12 @@
name = Mini panels
description = Create mini panels that can be used as blocks by Drupal and panes by other panel modules.
package = "Panels"
dependencies[] = panels
core = 7.x
files[] = plugins/export_ui/panels_mini_ui.class.php
; Information added by drupal.org packaging script on 2013-03-02
version = "7.x-3.3+39-dev"
core = "7.x"
project = "panels"
datestamp = "1362187383"

View File

@@ -0,0 +1,124 @@
<?php
/**
* Implementation of hook_schema().
*/
function panels_mini_schema() {
// This should always point to our 'current' schema. This makes it relatively easy
// to keep a record of schema as we make changes to it.
return panels_mini_schema_1();
}
/**
* Schema version 1 for Panels in D6.
*/
function panels_mini_schema_1() {
$schema = array();
$schema['panels_mini'] = array(
'export' => array(
'identifier' => 'mini',
'load callback' => 'panels_mini_load',
'load all callback' => 'panels_mini_load_all',
'save callback' => 'panels_mini_save',
'delete callback' => 'panels_mini_delete',
'export callback' => 'panels_mini_export',
'api' => array(
'owner' => 'panels_mini',
'api' => 'panels_default',
'minimum_version' => 1,
'current_version' => 1,
),
),
'fields' => array(
'pid' => array(
'type' => 'serial',
'not null' => TRUE,
'no export' => TRUE,
'description' => 'The primary key for uniqueness.',
),
'name' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'The unique name of the mini panel.',
),
'category' => array(
'type' => 'varchar',
'length' => '64',
'description' => 'The category this mini panel appears in on the add content pane.',
),
'did' => array(
'type' => 'int',
'no export' => TRUE,
'description' => 'The display ID of the panel.',
),
'admin_title' => array(
'type' => 'varchar',
'length' => '128',
'description' => 'The administrative title of the mini panel.',
),
'admin_description' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Administrative title of this mini panel.',
'object default' => '',
),
'requiredcontexts' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'description' => 'An array of required contexts.',
),
'contexts' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'description' => 'An array of contexts embedded into the panel.',
),
'relationships' => array(
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
'object default' => array(),
'description' => 'An array of relationships embedded into the panel.',
),
),
'primary key' => array('pid'),
'unique keys' => array(
'name' => array('name'),
),
);
return $schema;
}
/**
* Implementation of hook_uninstall().
*/
function panels_mini_uninstall() {
$panels_exists = db_table_exists('panels_display');
$result = db_query("SELECT * FROM {panels_mini}");
$deltas = array();
foreach ($result as $panel_mini) {
// Delete all associated displays.
if (!function_exists('panels_delete_display')) {
require_once drupal_get_path('module', 'panels') .'/panels.module';
}
if ($panels_exists) {
panels_delete_display($panel_mini->did);
}
$deltas[] = $panel_mini->pid;
}
if ($deltas) {
// Delete all configured blocks.
db_delete('block')
->condition('module', 'panels_mini')
->condition('delta', $deltas)
->execute();
}
}

View File

@@ -0,0 +1,482 @@
<?php
/**
* @file panels_mini.module
*
* This module provides mini panels which are basically panels that can be
* used within blocks or other panels.
*/
/**
* Implementation of hook_permission().
*/
function panels_mini_permission() {
return array(
'create mini panels' => array(
'title' => t('Create mini panels'),
'description' => t('Create new mini panels'),
),
'administer mini panels' => array(
'title' => t('Administer mini panels'),
'description' => t('Edit and delete mini panels'),
),
);
}
/**
* Implementation of hook_menu().
*/
function panels_mini_menu() {
// Safety: go away if CTools is not at an appropriate version.
if (!defined('PANELS_REQUIRED_CTOOLS_API') || !module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
return array();
}
$items['admin/structure/mini-panels/settings'] = array(
'title' => 'Settings',
'page callback' => 'panels_mini_settings',
'access arguments' => array('create mini panels'),
'type' => MENU_LOCAL_TASK,
);
// Also provide settings on the main panel UI
$items['admin/structure/panels/settings/panels-mini'] = array(
'title' => 'Mini panels',
'page callback' => 'panels_mini_settings',
'access arguments' => array('create mini panels'),
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Settings for mini panels.
*/
function panels_mini_settings() {
ctools_include('common', 'panels');
return drupal_get_form('panels_common_settings', 'panels_mini');
}
// ---------------------------------------------------------------------------
// Allow the rest of the system access to mini panels
/**
* Implementation of hook_block_info().
*/
function panels_mini_block_info() {
// Safety: go away if CTools is not at an appropriate version.
if (!defined('PANELS_REQUIRED_CTOOLS_API') || !module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
return array();
}
$blocks = array();
$minis = panels_mini_load_all();
foreach ($minis as $panel_mini) {
if (empty($panel_mini->disabled) && (module_exists('page_manager') || empty($panel_mini->requiredcontexts))) {
$blocks[$panel_mini->name] = array(
'info' => t('Mini panel: "@title"', array('@title' => $panel_mini->admin_title)),
'cache' => DRUPAL_NO_CACHE,
);
}
}
return $blocks;
}
/**
* Implementation of hook_block_view().
*
* @see panels_mini_panels_mini_content_type_render().
*/
function panels_mini_block_view($delta = 0) {
// static recursion protection.
static $viewing = array();
if (!empty($viewing[$delta])) {
return;
}
$viewing[$delta] = TRUE;
$panel_mini = panels_mini_load($delta);
if (empty($panel_mini)) {
// Bail out early if the specified mini panel doesn't exist.
return;
}
ctools_include('context');
$contexts = array();
if (module_exists('page_manager') && $current_page = page_manager_get_current_page()) {
if (!empty($current_page['contexts'])) {
$contexts = ctools_context_match_required_contexts($panel_mini->requiredcontexts, $current_page['contexts']);
}
}
$panel_mini->context = $panel_mini->display->context = ctools_context_load_contexts($panel_mini, FALSE, $contexts);
$panel_mini->display->css_id = panels_mini_get_id($panel_mini->name);
$block = array();
$block['content'] = panels_render_display($panel_mini->display);
$block['subject'] = $panel_mini->display->get_title();
unset($viewing[$delta]);
return $block;
}
/**
* Implementation of hook_block_configure().
*/
function panels_mini_block_configure($delta = 0) {
return array(
'admin_shortcut' => array(
'#markup' => l(t('Manage this mini-panel'), 'admin/structure/mini-panels/list/' . $delta . '/edit')
),
);
}
/**
* Implements hook_block_list_alter().
*
* Remove the block if the required contexts are not available.
*/
function panels_mini_block_list_alter(&$blocks) {
if (module_exists('page_manager')) {
$current_page = page_manager_get_current_page();
}
foreach ($blocks as $key => $block) {
if ($block->module != 'panels_mini') {
// This block was added by a contrib module, leave it in the list.
continue;
}
$panel_mini = panels_mini_load($block->delta);
if (empty($panel_mini)) {
// Bail out early if the specified mini panel doesn't exist.
unset($blocks[$key]);
continue;
}
if (!empty($panel_mini->requiredcontexts)) {
if (!$current_page || empty($current_page['contexts'])) {
unset($blocks[$key]);
continue;
}
else {
$required = array();
foreach ($panel_mini->requiredcontexts as $context) {
$info = ctools_get_context($context['name']);
$required[] = new ctools_context_required($context['identifier'], $info['context name']);
}
if (!ctools_context_match_requirements($current_page['contexts'], $required)) {
unset($blocks[$key]);
continue;
}
}
}
}
}
/**
* Statically store all used IDs to ensure all mini panels get a unique id.
*/
function panels_mini_get_id($name) {
$id_cache = &drupal_static(__FUNCTION__, array());
$id = 'mini-panel-' . $name;
if (!empty($id_cache[$name])) {
$id .= "-" . $id_cache[$name]++;
}
else {
$id_cache[$name] = 1;
}
return $id;
}
// ---------------------------------------------------------------------------
// Database functions.
/**
* Create a new page with defaults appropriately set from schema.
*/
function panels_mini_new($set_defaults = TRUE) {
ctools_include('export');
return ctools_export_new_object('panels_mini', $set_defaults);
}
/**
* Load a single mini panel.
*/
function panels_mini_load($name) {
$cache = &drupal_static('panels_mini_load_all', array());
// We use array_key_exists because failed loads will be NULL and
// isset() will try to load it again.
if (!array_key_exists($name, $cache)) {
ctools_include('export');
$result = ctools_export_load_object('panels_mini', 'names', array($name));
if (isset($result[$name])) {
if (empty($result[$name]->display)) {
$result[$name]->display = panels_load_display($result[$name]->did);
if (!empty($result[$name]->title) && empty($result[$name]->display->title)) {
$result[$name]->display->title = $result[$name]->title;
}
}
$cache[$name] = $result[$name];
if (!empty($result[$name]->title) && empty($result[$name]->admin_title)) {
$cache[$name]->admin_title = $result[$name]->title;
}
}
else {
$cache[$name] = NULL;
}
}
if (isset($cache[$name])) {
return $cache[$name];
}
}
/**
* Load all mini panels.
*/
function panels_mini_load_all($reset = FALSE) {
$cache = &drupal_static('panels_mini_load_all', array());
static $all_loaded = FALSE;
// We check our own private static because individual minis could have
// been loaded prior to load all and we need to know that.
if (!$all_loaded || $reset) {
$all_loaded = TRUE;
if ($reset) {
$cache = array();
}
ctools_include('export');
$minis = ctools_export_load_object('panels_mini');
$dids = array();
foreach ($minis as $mini) {
if (empty($cache[$mini->name])) {
if (!empty($mini->did)) {
$dids[$mini->did] = $mini->name;
}
else {
// Translate old style titles into new titles.
if (!empty($mini->title) && empty($mini->display->title)) {
$mini->display->title = $mini->title;
}
}
// Translate old style titles into new titles.
if (isset($mini->title) && empty($mini->admin_title)) {
$mini->admin_title = $mini->title;
}
$cache[$mini->name] = $mini;
}
}
$displays = panels_load_displays(array_keys($dids));
foreach ($displays as $did => $display) {
if (!empty($cache[$dids[$did]]->title) && empty($display->title)) {
$display->title = $cache[$dids[$did]]->title;
}
$cache[$dids[$did]]->display = $display;
}
}
// Strip out NULL entries that may have been added by panels_mini_load().
return array_filter($cache);
}
/**
* Write a mini panel to the database.
*/
function panels_mini_save(&$mini) {
if (!empty($mini->display)) {
$display = panels_save_display($mini->display);
$mini->did = $display->did;
}
$update = (isset($mini->pid) && $mini->pid != 'new') ? array('pid') : array();
drupal_write_record('panels_mini', $mini, $update);
return $mini;
}
/**
* Remove a mini panel.
*/
function panels_mini_delete($mini) {
db_delete('panels_mini')
->condition('name', $mini->name)
->execute();
if (db_table_exists('block') && $mini->type != t('Overridden')) {
// Also remove from block table as long as there isn't a default that may appear.
db_delete('block')
->condition('delta', $mini->name)
->condition('module', 'panels_mini')
->execute();
}
return panels_delete_display($mini->did);
}
/**
* Export a mini panel.
*/
function panels_mini_export($mini, $indent = '') {
ctools_include('export');
$output = ctools_export_object('panels_mini', $mini, $indent);
// Export the primary display
$display = !empty($mini->display) ? $mini->display : panels_load_display($mini->did);
$output .= panels_export_display($display, $indent);
$output .= $indent . '$mini->display = $display' . ";\n";
return $output;
}
/**
* Remove the block version of mini panels from being available content types.
*/
function panels_mini_ctools_block_info($module, $delta, &$info) {
$info = NULL;
}
/**
* Implementation of hook_ctools_plugin_directory() to let the system know
* we implement task and task_handler plugins.
*/
function panels_mini_ctools_plugin_directory($module, $plugin) {
if ($module == 'ctools' && ($plugin == 'content_types' || $plugin == 'export_ui')) {
return 'plugins/' . $plugin;
}
}
/**
* Get the display cache for the panels_mini plugin.
*/
function _panels_mini_panels_cache_get($key) {
ctools_include('export-ui');
$plugin = ctools_get_export_ui('panels_mini');
$handler = ctools_export_ui_get_handler($plugin);
if (!$handler) {
return;
}
$item = $handler->edit_cache_get($key);
if (!$item) {
$item = ctools_export_crud_load($handler->plugin['schema'], $key);
}
return array($handler, $item);
}
/**
* Get display edit cache for the panels mini export UI
*
* The key is the second half of the key in this form:
* panels_mini:TASK_NAME:HANDLER_NAME;
*/
function panels_mini_panels_cache_get($key) {
ctools_include('common', 'panels');
list($handler, $item) = _panels_mini_panels_cache_get($key);
if (isset($item->mini_panels_display_cache)) {
return $item->mini_panels_display_cache;
}
$cache = new stdClass();
$cache->display = $item->display;
$cache->display->context = ctools_context_load_contexts($item);
$cache->display->cache_key = 'panels_mini:' . $key;
$cache->content_types = panels_common_get_allowed_types('panels_mini', $cache->display->context);
$cache->display_title = TRUE;
// @TODO support locking
$cache->locked = FALSE;
return $cache;
}
/**
* Store a display edit in progress in the page cache.
*/
function panels_mini_panels_cache_set($key, $cache) {
list($handler, $item) = _panels_mini_panels_cache_get($key);
$item->mini_panels_display_cache = $cache;
$handler->edit_cache_set_key($item, $key);
}
/**
* Save all changes made to a display using the panels mini UI cache.
*/
function panels_mini_panels_cache_clear($key, $cache) {
list($handler, $item) = _panels_mini_panels_cache_get($key);
$handler->edit_cache_clear($item);
}
/**
* Save all changes made to a display using the panels mini UI cache.
*/
function panels_mini_panels_cache_save($key, $cache) {
list($handler, $item) = _panels_mini_panels_cache_get($key);
$item->display = $cache->display;
panels_mini_save($item);
$handler->edit_cache_clear($item);
}
/**
* Break the lock on a panels mini page.
*/
function panels_mini_panels_cache_break_lock($key, $cache) {
}
/**
* Implementation of hook_panels_dashboard_blocks().
*
* Adds mini panels information to the Panels dashboard.
*/
function panels_mini_panels_dashboard_blocks(&$vars) {
$vars['links']['panels_mini'] = array(
'title' => l(t('Mini panel'), 'admin/structure/mini-panels/add'),
'description' => t('Mini panels are small content areas exposed as blocks, for when you need to have complex block layouts or layouts within layouts.'),
'weight' => -1,
);
// Load all mini panels and their displays.
$panel_minis = panels_mini_load_all();
$count = 0;
$rows = array();
foreach ($panel_minis as $panel_mini) {
$rows[] = array(
check_plain($panel_mini->admin_title),
array(
'data' => l(t('Edit'), "admin/structure/mini-panels/list/$panel_mini->name/edit"),
'class' => 'links',
),
);
// Only show 10.
if (++$count >= 10) {
break;
}
}
if ($rows) {
$content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
}
else {
$content = '<p>' . t('There are no mini panels.') . '</p>';
}
$vars['blocks']['panels_mini'] = array(
'weight' => -100,
'title' => t('Manage mini panels'),
'link' => l(t('Go to list'), 'admin/structure/mini-panels'),
'content' => $content,
'class' => 'dashboard-mini-panels',
'section' => 'left',
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -0,0 +1,176 @@
<?php
/**
* @file
* Contains the content type plugin for a mini panel. While this does not
* need to be broken out into a .inc file, it's convenient that we do so
* that we don't load code unneccessarily. Plus it demonstrates plugins
* in modules other than Panels itself.
*
*/
/**
* Specially named hook. for .inc file. This looks a little silly due to the
* redundancy, but that's really just because the content type shares a
* name with the module.
*/
function panels_mini_panels_mini_ctools_content_types() {
return array(
'title' => t('Mini panels'),
'content type' => 'panels_mini_panels_mini_content_type_content_type',
);
}
/**
* Return each available mini panel available as a subtype.
*/
function panels_mini_panels_mini_content_type_content_type($subtype_id, $plugin) {
$mini = panels_mini_load($subtype_id);
return _panels_mini_panels_mini_content_type_content_type($mini);
}
/**
* Return each available mini panel available as a subtype.
*/
function panels_mini_panels_mini_content_type_content_types($plugin) {
$types = array();
foreach (panels_mini_load_all() as $mini) {
$type = _panels_mini_panels_mini_content_type_content_type($mini);
if ($type) {
$types[$mini->name] = $type;
}
}
return $types;
}
/**
* Return an info array describing a single mini panel.
*/
function _panels_mini_panels_mini_content_type_content_type($mini) {
if (empty($mini)) {
// The mini panel is deleted or missing.
return;
}
if (!empty($mini->disabled)) {
return;
}
$title = filter_xss_admin($mini->admin_title);
$type = array(
'title' => $title,
// For now mini panels will just use the contrib block icon.
'icon' => 'icon_mini_panel.png',
'description' => $title,
'category' => !empty($mini->category) ? $mini->category : t('Mini panel'),
);
if (!empty($mini->requiredcontexts)) {
$type['required context'] = array();
foreach ($mini->requiredcontexts as $context) {
$info = ctools_get_context($context['name']);
// TODO: allow an optional setting
$type['required context'][] = new ctools_context_required($context['identifier'], $info['context name']);
}
}
return $type;
}
/**
* Render a mini panel called from a panels display.
*/
function panels_mini_panels_mini_content_type_render($subtype, $conf, $panel_args, &$contexts) {
static $viewing = array();
$mini = panels_mini_load($subtype);
if (!$mini) {
return FALSE;
}
if (!empty($viewing[$mini->name])) {
return FALSE;
}
// Load up any contexts we might be using.
$context = ctools_context_match_required_contexts($mini->requiredcontexts, $contexts);
$mini->context = $mini->display->context = ctools_context_load_contexts($mini, FALSE, $context);
if (empty($mini) || !empty($mini->disabled)) {
return;
}
$viewing[$mini->name] = TRUE;
$mini->display->args = $panel_args;
$mini->display->css_id = panels_mini_get_id($subtype);
$mini->display->owner = $mini;
// unique ID of this mini.
$mini->display->owner->id = $mini->name;
$block = new stdClass();
$block->module = 'panels_mini';
$block->delta = $subtype;
$block->content = panels_render_display($mini->display);
$block->title = $mini->display->get_title();
if (user_access('administer mini panels')) {
$block->admin_links = array(
array(
'title' => t('Configure mini panel'),
'href' => "admin/structure/mini-panels/list/$subtype/edit/content",
'query' => drupal_get_destination(),
),
);
}
unset($viewing[$mini->name]);
return $block;
}
/**
* Edit form for the mini panel content type.
*/
function panels_mini_panels_mini_content_type_edit_form($form, &$form_state) {
// Empty form to ensure we have the override title + context gadgets.
return $form;
}
/**
* Provide the administrative title of a mini panel.
*/
function panels_mini_panels_mini_content_type_admin_title($subtype, $conf) {
$mini = panels_mini_load($subtype);
if (!$mini) {
return t('Deleted/missing mini panel @name', array('@name' => $subtype));
}
$title = filter_xss_admin($mini->admin_title);
if (empty($title)) {
$title = t('Untitled mini panel');
}
return $title;
}
/**
* Callback to provide administrative info. Provide links to edit the mini
* panel.
*/
function panels_mini_panels_mini_content_type_admin_info($subtype, $conf) {
$mini = panels_mini_load($subtype);
if (!$mini) {
return FALSE;
}
$block = new stdClass();
$block->title = $mini->admin_title;
$admin_pages = array(
t('Settings') => 'basic',
t('Context') => 'context',
t('Layout') => 'layout',
t('Content') => 'content',
);
$links = array();
foreach ($admin_pages as $title => $tail) {
$links[] = l($title, 'admin/structure/mini-panels/list/' . $subtype . '/edit/' . $tail, array('query' => drupal_get_destination()));
}
$block->content = theme('item_list', array('items' => $links));
return $block;
}

View File

@@ -0,0 +1,41 @@
<?php
$plugin = array(
'schema' => 'panels_mini',
'access' => 'administer mini panels',
'create access' => 'create mini panels',
'menu' => array(
'menu item' => 'mini-panels',
'menu title' => 'Mini panels',
'menu description' => 'Add, edit or delete mini panels, which can be used as blocks or content panes in other panels.',
),
'title singular' => t('mini panel'),
'title singular proper' => t('Mini panel'),
'title plural' => t('mini panels'),
'title plural proper' => t('Mini panels'),
'handler' => array(
'class' => 'panels_mini_ui',
'parent' => 'ctools_export_ui',
),
'use wizard' => TRUE,
'form info' => array(
'order' => array(
'basic' => t('Settings'),
'context' => t('Context'),
'layout' => t('Layout'),
'content' => t('Content'),
),
// We have to add this form specially because it's invisible.
'forms' => array(
'move' => array(
'form id' => 'ctools_export_ui_edit_item_wizard_form',
),
),
),
);

View File

@@ -0,0 +1,298 @@
<?php
class panels_mini_ui extends ctools_export_ui {
function init($plugin) {
parent::init($plugin);
ctools_include('context');
}
function list_form(&$form, &$form_state) {
ctools_include('plugins', 'panels');
$this->layouts = panels_get_layouts();
parent::list_form($form, $form_state);
$categories = $layouts = array('all' => t('- All -'));
foreach ($this->items as $item) {
$categories[$item->category] = $item->category ? $item->category : t('Mini panels');
}
$form['top row']['category'] = array(
'#type' => 'select',
'#title' => t('Category'),
'#options' => $categories,
'#default_value' => 'all',
'#weight' => -10,
);
foreach ($this->layouts as $name => $plugin) {
$layouts[$name] = $plugin['title'];
}
$form['top row']['layout'] = array(
'#type' => 'select',
'#title' => t('Layout'),
'#options' => $layouts,
'#default_value' => 'all',
'#weight' => -9,
);
}
function list_filter($form_state, $item) {
if ($form_state['values']['category'] != 'all' && $form_state['values']['category'] != $item->category) {
return TRUE;
}
if ($form_state['values']['layout'] != 'all' && $form_state['values']['layout'] != $item->display->layout) {
return TRUE;
}
return parent::list_filter($form_state, $item);
}
function list_sort_options() {
return array(
'disabled' => t('Enabled, title'),
'title' => t('Title'),
'name' => t('Name'),
'category' => t('Category'),
'storage' => t('Storage'),
'layout' => t('Layout'),
);
}
function list_build_row($item, &$form_state, $operations) {
// Set up sorting
switch ($form_state['values']['order']) {
case 'disabled':
$this->sorts[$item->name] = empty($item->disabled) . $item->admin_title;
break;
case 'title':
$this->sorts[$item->name] = $item->admin_title;
break;
case 'name':
$this->sorts[$item->name] = $item->name;
break;
case 'category':
$this->sorts[$item->name] = ($item->category ? $item->category : t('Mini panels')) . $item->admin_title;
break;
case 'layout':
$this->sorts[$item->name] = $item->display->layout . $item->admin_title;
break;
case 'storage':
$this->sorts[$item->name] = $item->type . $item->admin_title;
break;
}
$layout = !empty($this->layouts[$item->display->layout]) ? $this->layouts[$item->display->layout]['title'] : t('Missing layout');
$category = $item->category ? check_plain($item->category) : t('Mini panels');
$ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
$this->rows[$item->name] = array(
'data' => array(
array('data' => check_plain($item->admin_title), 'class' => array('ctools-export-ui-title')),
array('data' => check_plain($item->name), 'class' => array('ctools-export-ui-name')),
array('data' => $category, 'class' => array('ctools-export-ui-category')),
array('data' => $layout, 'class' => array('ctools-export-ui-layout')),
array('data' => $item->type, 'class' => array('ctools-export-ui-storage')),
array('data' => $ops, 'class' => array('ctools-export-ui-operations')),
),
'title' => !empty($item->admin_description) ? check_plain($item->admin_description) : '',
'class' => array(!empty($item->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'),
);
}
function list_table_header() {
return array(
array('data' => t('Title'), 'class' => array('ctools-export-ui-title')),
array('data' => t('Name'), 'class' => array('ctools-export-ui-name')),
array('data' => t('Category'), 'class' => array('ctools-export-ui-category')),
array('data' => t('Layout'), 'class' => array('ctools-export-ui-layout')),
array('data' => t('Storage'), 'class' => array('ctools-export-ui-storage')),
array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations')),
);
}
function edit_form(&$form, &$form_state) {
// Get the basic edit form
parent::edit_form($form, $form_state);
// Set the admin title machine name length.
// We need to do this because the system block name length is
// limited to 32 chars.
$form['info']['name']['#maxlength'] = 32;
$form['info']['name']['#size'] = 34;
$form['info']['name']['#description'] .= ' ' . t('The machine name length is limited to 32 characters, due to a limitation in the core block system.');
$form['category'] = array(
'#type' => 'textfield',
'#size' => 24,
'#default_value' => $form_state['item']->category,
'#title' => t('Category'),
'#description' => t("The category that this mini-panel will be grouped into on the Add Content form. Only upper and lower-case alphanumeric characters are allowed. If left blank, defaults to 'Mini panels'."),
);
$form['title']['#title'] = t('Title');
$form['title']['#description'] = t('The title for this mini panel. It can be overridden in the block configuration.');
}
/**
* Validate submission of the mini panel edit form.
*/
function edit_form_basic_validate($form, &$form_state) {
parent::edit_form_validate($form, $form_state);
if (preg_match("/[^A-Za-z0-9 ]/", $form_state['values']['category'])) {
form_error($form['category'], t('Categories may contain only alphanumerics or spaces.'));
}
}
function edit_form_submit(&$form, &$form_state) {
parent::edit_form_submit($form, $form_state);
$form_state['item']->category = $form_state['values']['category'];
}
function edit_form_context(&$form, &$form_state) {
ctools_include('context-admin');
ctools_context_admin_includes();
ctools_add_css('ruleset');
$form['right'] = array(
'#prefix' => '<div class="ctools-right-container">',
'#suffix' => '</div>',
);
$form['left'] = array(
'#prefix' => '<div class="ctools-left-container clearfix">',
'#suffix' => '</div>',
);
// Set this up and we can use CTools' Export UI's built in wizard caching,
// which already has callbacks for the context cache under this name.
$module = 'export_ui::' . $this->plugin['name'];
$name = $this->edit_cache_get_key($form_state['item'], $form_state['form type']);
ctools_context_add_context_form($module, $form, $form_state, $form['right']['contexts_table'], $form_state['item'], $name);
ctools_context_add_required_context_form($module, $form, $form_state, $form['left']['required_contexts_table'], $form_state['item'], $name);
ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $form_state['item'], $name);
}
function edit_form_context_submit(&$form, &$form_state) {
// Prevent this from going to edit_form_submit();
}
function edit_form_layout(&$form, &$form_state) {
ctools_include('common', 'panels');
ctools_include('display-layout', 'panels');
ctools_include('plugins', 'panels');
// @todo -- figure out where/how to deal with this.
$form_state['allowed_layouts'] = 'panels_mini';
if ($form_state['op'] == 'add' && empty($form_state['item']->display)) {
$form_state['item']->display = panels_new_display();
}
$form_state['display'] = &$form_state['item']->display;
// Tell the Panels form not to display buttons.
$form_state['no buttons'] = TRUE;
// Change the #id of the form so the CSS applies properly.
$form['#id'] = 'panels-choose-layout';
$form = panels_choose_layout($form, $form_state);
if ($form_state['op'] == 'edit') {
$form['buttons']['next']['#value'] = t('Change');
}
}
/**
* Validate that a layout was chosen.
*/
function edit_form_layout_validate(&$form, &$form_state) {
$display = &$form_state['display'];
if (empty($form_state['values']['layout'])) {
form_error($form['layout'], t('You must select a layout.'));
}
if ($form_state['op'] == 'edit') {
if ($form_state['values']['layout'] == $display->layout) {
form_error($form['layout'], t('You must select a different layout if you wish to change layouts.'));
}
}
}
/**
* A layout has been selected, set it up.
*/
function edit_form_layout_submit(&$form, &$form_state) {
$display = &$form_state['display'];
if ($form_state['op'] == 'edit') {
if ($form_state['values']['layout'] != $display->layout) {
$form_state['item']->temp_layout = $form_state['values']['layout'];
$form_state['clicked_button']['#next'] = 'move';
}
}
else {
$form_state['item']->display->layout = $form_state['values']['layout'];
}
}
/**
* When a layout is changed, the user is given the opportunity to move content.
*/
function edit_form_move(&$form, &$form_state) {
$form_state['display'] = &$form_state['item']->display;
$form_state['layout'] = $form_state['item']->temp_layout;
ctools_include('common', 'panels');
ctools_include('display-layout', 'panels');
ctools_include('plugins', 'panels');
// Tell the Panels form not to display buttons.
$form_state['no buttons'] = TRUE;
// Change the #id of the form so the CSS applies properly.
$form = panels_change_layout($form, $form_state);
// This form is outside the normal wizard list, so we need to specify the
// previous/next forms.
$form['buttons']['previous']['#next'] = 'layout';
$form['buttons']['next']['#next'] = 'content';
}
function edit_form_move_submit(&$form, &$form_state) {
panels_change_layout_submit($form, $form_state);
}
function edit_form_content(&$form, &$form_state) {
ctools_include('ajax');
ctools_include('plugins', 'panels');
ctools_include('display-edit', 'panels');
ctools_include('context');
// If we are cloning an item, we MUST have this cached for this to work,
// so make sure:
if ($form_state['form type'] == 'clone' && empty($form_state['item']->export_ui_item_is_cached)) {
$this->edit_cache_set($form_state['item'], 'clone');
}
$cache = panels_edit_cache_get('panels_mini:' . $this->edit_cache_get_key($form_state['item'], $form_state['form type']));
$form_state['renderer'] = panels_get_renderer_handler('editor', $cache->display);
$form_state['renderer']->cache = &$cache;
$form_state['display'] = &$cache->display;
$form_state['content_types'] = $cache->content_types;
// Tell the Panels form not to display buttons.
$form_state['no buttons'] = TRUE;
$form_state['display_title'] = !empty($cache->display_title);
$form = panels_edit_display_form($form, $form_state);
}
function edit_form_content_submit(&$form, &$form_state) {
panels_edit_display_form_submit($form, $form_state);
$form_state['item']->display = $form_state['display'];
}
}

View File

@@ -0,0 +1,14 @@
name = Panel nodes
description = Create nodes that are divided into areas with selectable content.
package = "Panels"
dependencies[] = panels
configure = admin/structure/panels
core = 7.x
files[] = panels_node.module
; Information added by drupal.org packaging script on 2013-03-02
version = "7.x-3.3+39-dev"
core = "7.x"
project = "panels"
datestamp = "1362187383"

View File

@@ -0,0 +1,71 @@
<?php
/**
* Implementation of hook_schema().
*/
function panels_node_schema() {
// This should always point to our 'current' schema. This makes it relatively easy
// to keep a record of schema as we make changes to it.
return panels_node_schema_1();
}
/**
* Schema version 1 for Panels in D6.
*/
function panels_node_schema_1() {
$schema = array();
$schema['panels_node'] = array(
'fields' => array(
'nid' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'css_id' => array(
'type' => 'varchar',
'length' => '255',
),
'did' => array(
'type' => 'int',
'not null' => TRUE,
),
'pipeline' => array(
'type' => 'varchar',
'length' => '255',
),
),
'primary key' => array('did'),
);
return $schema;
}
/**
* Implementation of hook_install().
*/
function panels_node_install() {
db_query("UPDATE {system} SET weight = 11 WHERE name = 'panels_node'");
}
/**
* Implementation of hook_uninstall().
*/
function panels_node_uninstall() {
db_query("DELETE FROM {node} WHERE type = 'panel'");
drupal_uninstall_schema('panels_node');
}
/**
* Implementation of hook_update to handle adding a pipeline
*/
function panels_node_update_6001() {
$ret = array();
$field = array(
'type' => 'varchar',
'length' => '255',
);
db_add_field('panels_node', 'pipeline', $field);
return $ret;
}

View File

@@ -0,0 +1,488 @@
<?php
/**
* @file panels_node.module
*
* This module provides the "panel" node type.
* Panel nodes are useful to add additional content to the content area
* on a per-node base.
*/
// ---------------------------------------------------------------------------
// General Drupal hooks
/**
* Implementation of hook_permission().
*/
function panels_node_permission() {
return array(
'create panel-nodes' => array(
'title' => t('Create panel nodes'),
'description' => t('Create new panel nodes.'),
),
'edit any panel-nodes' => array(
'title' => t('Edit any panel-nodes'),
'description' => t('Edit all pre-existing panel nodes regardless of ownership.'),
),
'edit own panel-nodes' => array(
'title' => t('Edit own panel nodes'),
'description' => t('Edit panel nodes owned by this user.'),
),
'administer panel-nodes' => array(
'title' => t('Administer panel nodes'),
'description' => t('Full administrative access to panel nodes including create, update and delete all'),
),
'delete any panel-nodes' => array(
'title' => t('Delete any panel nodes'),
'description' => t('Delete any panel node regardless of ownership'),
),
'delete own panel-nodes' => array(
'title' => t('Delete own panel nodes'),
'description' => t('Delete any panel node owned by this user.'),
),
);
}
/**
* Implementation of hook_menu().
*/
function panels_node_menu() {
// Safety: go away if CTools is not at an appropriate version.
if (!defined('PANELS_REQUIRED_CTOOLS_API') || !module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
return array();
}
$items['admin/structure/panels/settings/panel-node'] = array(
'title' => 'Panel nodes',
'description' => 'Configure which content is available to add to panel node displays.',
'access arguments' => array('administer panel-nodes'),
'page callback' => 'panels_node_settings',
'type' => MENU_LOCAL_TASK,
);
// Avoid some repetition on these:
$base = array(
'access callback' => 'panels_node_edit_node',
'access arguments' => array(1),
'page arguments' => array(1),
'type' => MENU_LOCAL_TASK,
);
$items['node/%node/panel_layout'] = array(
'title' => 'Panel layout',
'page callback' => 'panels_node_edit_layout',
'weight' => 2,
) + $base;
$items['node/%node/panel_content'] = array(
'title' => 'Panel content',
'page callback' => 'panels_node_edit_content',
'weight' => 3,
) + $base;
$items['node/add/panel/choose-layout'] = array(
'title' => 'Choose layout',
'access arguments' => array('create panel-nodes'),
'page callback' => 'panels_node_add',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Access callback to determine if a user has edit access
*/
function panels_node_edit_node($node) {
if (!isset($node->panels_node)) {
return FALSE;
}
return node_access('update', $node);
}
/**
* Override of node add page to force layout selection prior
* to actually editing a node.
*/
function panels_node_add() {
$output = '';
ctools_include('plugins', 'panels');
ctools_include('common', 'panels');
$layouts = panels_common_get_allowed_layouts('panels_node');
return panels_common_print_layout_links($layouts, 'node/add/panel', array('query' => $_GET));
}
// ---------------------------------------------------------------------------
// Node hooks
/**
* Implementation of hook_node_info().
*/
function panels_node_node_info() {
// Safety: go away if CTools is not at an appropriate version.
if (!defined('PANELS_REQUIRED_CTOOLS_API') || !module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
return array();
}
return array(
'panel' => array(
'name' => t('Panel'),
// We use panels_node_hook so that panels_node private
// callbacks do not get confused with panels versions of
// nodeapi callbacks.
'base' => 'panels_node_hook',
'body_label' => t('Teaser'),
'description' => t("A panel layout broken up into rows and columns."),
),
);
}
/**
* Implementation of hook_access().
*/
function panels_node_node_access($node, $op, $account) {
if ($op == 'create' && $node != 'panel') {
return NODE_ACCESS_IGNORE;
}
if (is_object($node) && $node->type != 'panel') {
return NODE_ACCESS_IGNORE;
}
if (user_access('administer panel-nodes', $account)) {
return NODE_ACCESS_ALLOW;
}
if ($op == 'create' && user_access('create panel-nodes', $account)) {
return NODE_ACCESS_ALLOW;
}
if ($op == 'update' && (user_access('edit any panel-nodes', $account) || $node->uid == $account->uid && user_access('edit own panel-nodes', $account))) {
return NODE_ACCESS_ALLOW;
}
if ($op == 'delete' && (user_access('delete any panel-nodes') || $node->uid == $account->uid && user_access('delete own panel-nodes'))) {
return NODE_ACCESS_ALLOW;
}
}
/**
* Implementation of hook_form().
*/
function panels_node_hook_form(&$node, &$form_state) {
ctools_include('plugins', 'panels');
$form['panels_node']['#tree'] = TRUE;
if (empty($node->nid) && arg(0) == 'node' && arg(1) == 'add') {
// Grab our selected layout from the $node, If it doesn't exist, try arg(3)
// and if that doesn't work present them with a list to pick from.
$panel_layout = isset($node->panel_layout) ? $node->panel_layout : arg(3);
if (empty($panel_layout)) {
$opts = $_GET;
unset($opts['q']);
return drupal_goto('node/add/panel/choose-layout', $opts);
}
$layout = panels_get_layout($panel_layout);
if (empty($layout)) {
return drupal_not_found();
}
$form['panels_node']['layout'] = array(
'#type' => 'value',
'#value' => $panel_layout,
);
}
$type = node_type_get_type($node);
$form['title'] = array(
'#type' => 'textfield',
'#title' => check_plain($type->title_label),
'#required' => TRUE,
'#default_value' => $node->title,
);
$css_id = '';
if (!empty($node->panels_node['css_id'])) {
$css_id = $node->panels_node['css_id'];
}
$form['panels_node']['css_id'] = array(
'#type' => 'textfield',
'#title' => t('CSS ID'),
'#size' => 30,
'#description' => t('An ID that can be used by CSS to style the panel.'),
'#default_value' => $css_id,
);
// Support for different rendering pipelines
// Mostly borrowed from panel_context.inc
$pipelines = panels_get_renderer_pipelines();
$options = array();
foreach ($pipelines as $name => $pipeline) {
$options[$name] = check_plain($pipeline->admin_title) . '<div class="description">' . check_plain($pipeline->admin_description) . '</div>';
}
$form['panels_node']['pipeline'] = array(
'#type' => 'radios',
'#options' => $options,
'#title' => t('Renderer'),
'#default_value' => isset($node->panels_node['pipeline']) ? $node->panels_node['pipeline'] : 'standard',
);
return $form;
}
/**
* Implementation of hook_validate().
*/
function panels_node_hook_validate($node, $form, &$form_state) {
if (!$node->nid && empty($node->panels_node['layout'])) {
form_error($form['panels_node']['layout'], t('Please select a layout.'));
}
}
/**
* Implementation of hook_load().
*
* Panels does not use revisions for nodes because that would open us up
* to have completely separate displays, and we'd have to copy them,
* and that's going to be a LOT of data.
*/
function panels_node_hook_load($nodes) {
// We shortcut this because only in some really drastic corruption circumstance will this
// not work.
$result = db_query("SELECT * FROM {panels_node} WHERE nid IN (:nids)", array(':nids' => array_keys($nodes)));
foreach ($result as $record) {
$nodes[$record->nid]->panels_node = (array) $record;
}
}
/**
* Implementation of hook_insert().
*/
function panels_node_hook_insert(&$node) {
// Create a new display and record that.
$display = panels_new_display();
$display->layout = $node->panels_node['layout'];
// Special handling for nodes being imported from an export.module data dump.
if (!empty($node->export_display)) {
// This works by overriding the $display set above
eval($node->export_display);
unset($node->export_display);
}
panels_save_display($display);
$node->panels_node['did'] = $display->did;
db_insert('panels_node')
->fields(array(
'nid' => $node->nid,
'did' => $display->did,
'css_id' => $node->panels_node['css_id'],
'pipeline' => $node->panels_node['pipeline'],
))
->execute();
}
/**
* Implementation of hook_delete().
*/
function panels_node_hook_delete(&$node) {
db_delete('panels_node')->condition('nid', $node->nid)->execute();
if (!empty($node->panels_node['did'])) {
panels_delete_display($node->panels_node['did']);
}
}
/**
* Implementation of hook_update().
*/
function panels_node_hook_update($node) {
db_update('panels_node')
->condition('nid', $node->nid)
->fields(array(
'css_id' => $node->panels_node['css_id'],
'pipeline' => $node->panels_node['pipeline'],
))
->execute();
}
/**
* Implementation of hook_view().
*/
function panels_node_hook_view($node, $view_mode) {
static $rendering = array();
// Prevent loops if someone foolishly puts the node inside itself:
if (!empty($rendering[$node->nid])) {
return $node;
}
$rendering[$node->nid] = TRUE;
ctools_include('plugins', 'panels');
if ($view_mode == 'teaser') {
// Because our teasier is never the same as our content, *always* provide
// the read more flag.
$node->readmore = TRUE;
}
else {
if (!empty($node->panels_node['did'])) {
$display = panels_load_display($node->panels_node['did']);
$display->css_id = $node->panels_node['css_id'];
// TODO: Find a way to make sure this can't node_view.
$display->context = panels_node_get_context($node);
$display->cache_key = 'panels_node:' . $node->nid;
$renderer = panels_get_renderer($node->panels_node['pipeline'], $display);
$node->content['body'] = array(
'#markup' => panels_render_display($display, $renderer),
'#weight' => 0,
);
}
}
unset($rendering[$node->nid]);
return $node;
}
// ---------------------------------------------------------------------------
// Administrative pages
/**
* Settings for panel nodes.
*/
function panels_node_settings() {
ctools_include('common', 'panels');
return drupal_get_form('panels_common_settings', 'panels_node');
}
// ---------------------------------------------------------------------------
// Meat of the Panels API; almost completely passing through to panels.module
/**
* Pass through to the panels layout editor.
*/
function panels_node_edit_layout($node) {
// ctools_include('plugins', 'panels');
ctools_include('context');
$display = panels_load_display($node->panels_node['did']);
$display->context = panels_node_get_context($node);
return panels_edit_layout($display, t('Save'), "node/$node->nid/panel_layout", 'panels_node');
}
/**
* Pass through to the panels content editor.
*/
function panels_node_edit_content($node) {
ctools_include('context');
$display = panels_load_display($node->panels_node['did']);
$display->context = panels_node_get_context($node);
ctools_include('common', 'panels');
$content_types = panels_common_get_allowed_types('panels_node', $display->context);
return panels_edit($display, "node/$node->nid/panel_content", $content_types);
}
/**
* Build the context to use for a panel node.
*/
function panels_node_get_context(&$node) {
ctools_include('context');
$context = ctools_context_create('node', $node);
$context->identifier = t('This node');
$context->keyword = 'node';
return array('panel-node' => $context);
}
/**
* Implementation of hook_export_node_alter()
*
* Integrate with export.module for saving panel_nodes into code.
*/
function panels_node_export_node_alter(&$node, $original_node, $method) {
if ($method == 'export') {
$node_export_omitted = variable_get('node_export_omitted', array());
if (variable_get('node_export_method', '') != 'save-edit' && (array_key_exists('panel', $node_export_omitted) && !$node_export_omitted['panel'])) {
drupal_set_message(t("NOTE: in order to import panel_nodes you must first set the export.module settings to \"Save as a new node then edit\", otherwise it won't work."));
}
$display = panels_load_display($node->panels_node['did']);
$export = panels_export_display($display);
$node->export_display = $export;
}
}
/**
* Implementation of hook_panels_dashboard_blocks().
*
* Adds panel nodes information to the Panels dashboard.
*/
function panels_node_panels_dashboard_blocks(&$vars) {
$vars['links']['panels_node'] = array(
'title' => l(t('Panel node'), 'node/add/panel'),
'description' => t('Panel nodes are node content and appear in your searches, but are more limited than panel pages.'),
'weight' => -1,
);
}
// ---------------------------------------------------------------------------
// Callbacks for panel caching.
/**
* Get display edit cache for a panel node being edited.
*
* The key is the second half of the key in this form:
* panels_node:NID;
*/
function panels_node_panels_cache_get($nid) {
ctools_include('object-cache');
$cache = ctools_object_cache_get('panels_node_display_cache', $nid);
if (empty($cache)) {
$cache = new stdClass();
$node = node_load($nid);
if (empty($node)) {
return;
}
ctools_include('common', 'panels');
$cache->display = panels_load_display($node->panels_node['did']);
$cache->display->css_id = $node->panels_node['css_id'];
$cache->display->context = panels_node_get_context($node);
$cache->display->cache_key = 'panels_node:' . $node->nid;
$cache->content_types = panels_common_get_allowed_types('panels_node', $cache->display->context);
$cache->allwed_layouts = panels_common_get_allowed_layouts('panels_node');
}
return $cache;
}
/**
* Store a display edit in progress in the panels cache.
*/
function panels_node_panels_cache_set($nid, $cache) {
ctools_include('object-cache');
ctools_object_cache_set('panels_node_display_cache', $nid, $cache);
}
/**
* Clear all changes made to a display using the panels cache.
*/
function panels_node_panels_cache_clear($nid, $cache) {
ctools_include('object-cache');
ctools_object_cache_clear('panels_node_display_cache', $nid);
}
/**
* React to a cache save and save the display and clear cache.
*/
function panels_node_panels_cache_save($nid, $cache) {
panels_save_display($cache->display);
ctools_include('object-cache');
ctools_object_cache_clear('panels_node_display_cache', $nid);
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* @file
* Provides a simple time-based caching option for panel panes.
*/
// Plugin definition
$plugin = array(
'title' => t("Simple cache"),
'description' => t('Simple caching is a time-based cache. This is a hard limit, and once cached it will remain that way until the time limit expires.'),
'cache get' => 'panels_simple_cache_get_cache',
'cache set' => 'panels_simple_cache_set_cache',
'cache clear' => 'panels_simple_cache_clear_cache',
'settings form' => 'panels_simple_cache_settings_form',
'settings form submit' => 'panels_simple_cache_settings_form_submit',
'defaults' => array(
'lifetime' => 15,
'granularity' => 'none',
),
);
/**
* Get cached content.
*/
function panels_simple_cache_get_cache($conf, $display, $args, $contexts, $pane = NULL) {
$cid = panels_simple_cache_get_id($conf, $display, $args, $contexts, $pane);
$cache = cache_get($cid, 'cache');
if (!$cache) {
return FALSE;
}
if ((time() - $cache->created) > $conf['lifetime']) {
return FALSE;
}
return $cache->data;
}
/**
* Set cached content.
*/
function panels_simple_cache_set_cache($conf, $content, $display, $args, $contexts, $pane = NULL) {
$cid = panels_simple_cache_get_id($conf, $display, $args, $contexts, $pane);
cache_set($cid, $content);
}
/**
* Clear cached content.
*
* Cache clears are always for an entire display, regardless of arguments.
*/
function panels_simple_cache_clear_cache($display) {
$cid = 'panels_simple_cache';
// This is used in case this is an in-code display, which means did will be something like 'new-1'.
if (isset($display->owner) && isset($display->owner->id)) {
$cid .= ':' . $display->owner->id;
}
$cid .= ':' . $display->did;
cache_clear_all($cid, 'cache', TRUE);
}
/**
* Figure out an id for our cache based upon input and settings.
*/
function panels_simple_cache_get_id($conf, $display, $args, $contexts, $pane) {
$id = 'panels_simple_cache';
// If the panel is stored in the database it'll have a numeric did value.
if (is_numeric($display->did)) {
$id .= ':' . $display->did;
}
// Exported panels won't have a numeric did but may have a usable cache_key.
elseif (!empty($display->cache_key)) {
$id .= ':' . str_replace('panel_context:', '', $display->cache_key);
}
// Alternatively use the css_id.
elseif (!empty($display->css_id)) {
$id .= ':' . $display->css_id;
}
// Failover to just appending the did, which may be the completely unusable
// string 'new'.
else {
$id .= ':' . $display->did;
}
if ($pane) {
$id .= ':' . $pane->pid;
}
if (user_access('view pane admin links')) {
$id .= ':admin';
}
switch ($conf['granularity']) {
case 'args':
foreach ($args as $arg) {
$id .= ':' . $arg;
}
break;
case 'context':
if (!is_array($contexts)) {
$contexts = array($contexts);
}
foreach ($contexts as $context) {
if (isset($context->argument)) {
$id .= ':' . $context->argument;
}
}
}
if (module_exists('locale')) {
global $language;
$id .= ':' . $language->language;
}
if(!empty($pane->configuration['use_pager']) && !empty($_GET['page'])) {
$id .= ':p' . check_plain($_GET['page']);
}
return $id;
}
function panels_simple_cache_settings_form($conf, $display, $pid) {
$options = drupal_map_assoc(array(15, 30, 60, 120, 180, 240, 300, 600, 900, 1200, 1800, 3600, 7200, 14400, 28800, 43200, 86400, 172800, 259200, 345600, 604800), 'format_interval');
$form['lifetime'] = array(
'#title' => t('Lifetime'),
'#type' => 'select',
'#options' => $options,
'#default_value' => $conf['lifetime'],
);
$form['granularity'] = array(
'#title' => t('Granularity'),
'#type' => 'select',
'#options' => array(
'args' => t('Arguments'),
'context' => t('Context'),
'none' => t('None'),
),
'#description' => t('If "arguments" are selected, this content will be cached per individual argument to the entire display; if "contexts" are selected, this content will be cached per unique context in the pane or display; if "neither" there will be only one cache for this pane.'),
'#default_value' => $conf['granularity'],
);
return $form;
}

View File

@@ -0,0 +1,5 @@
<?php
$plugin = array(
'renderer' => 'panels_renderer_editor',
);

View File

@@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains the simple display renderer.
*/
/**
* The simple display renderer renders a display normally, except each pane
* is already rendered content, rather than a pane containing CTools content
* to be rendered. Styles are not supported.
*/
class panels_renderer_simple extends panels_renderer_standard {
function render_regions() {
$this->rendered['regions'] = array();
foreach ($this->display->content as $region_id => $content) {
if (is_array($content)) {
$content = implode('', $content);
}
$this->rendered['regions'][$region_id] = $content;
}
return $this->rendered['regions'];
}
function render_panes() {
// NOP
}
function prepare($external_settings = NULL) {
$this->prep_run = TRUE;
}
}

View File

@@ -0,0 +1,633 @@
<?php
/**
* The standard render pipeline for a Panels display object.
*
* Given a fully-loaded panels_display object, this class will turn its
* combination of layout, panes, and styles into HTML, invoking caching
* appropriately along the way. Interacting with the renderer externally is
* very simple - just pass it the display object and call the render() method:
*
* @code
* // given that $display is a fully loaded Panels display object
* $renderer = panels_get_renderer_handler('standard', $display)
* $html_output = $renderer->render();
* @endcode
*
* Internally, the render pipeline is divided into two phases, prepare and
* render:
* - The prepare phase transforms the skeletal data on the provided
* display object into a structure that is expected by the render phase.
* It is divided into a series of discrete sub-methods and operates
* primarily by passing parameters, all with the intention of making
* subclassing easier.
* - The render phase relies primarily on data stored in the renderer object's
* properties, presumably set in the prepare phase. It iterates through the
* rendering of each pane, pane styling, placement in panel regions, region
* styling, and finally the arrangement of rendered regions in the layout.
* Caching, if in use, is triggered per pane, or on the entire display.
*
* In short: prepare builds conf, render renders conf. Subclasses should respect
* this separation of responsibilities by adhering to these loose guidelines,
* given a loaded display object:
* - If your renderer needs to modify the datastructure representing what is
* to be rendered (panes and their conf, styles, caching, etc.), it should
* use the prepare phase.
* - If your renderer needs to modify the manner in which that renderable
* datastructure data is rendered, it should use the render phase.
*
* In the vast majority of use cases, this standard renderer will be sufficient
* and need not be switched out/subclassed; style and/or layout plugins can
* accommodate nearly every use case. If you think you might need a custom
* renderer, consider the following criteria/examples:
* - Some additional markup needs to be added to EVERY SINGLE panel.
* - Given a full display object, just render one pane.
* - Show a Panels admin interface.
*
* The system is almost functionally identical to the old procedural approach,
* with some exceptions (@see panels_renderer_legacy for details). The approach
* here differs primarily in its friendliness to tweaking in subclasses.
*/
class panels_renderer_standard {
/**
* The fully-loaded Panels display object that is to be rendered. "Fully
* loaded" is defined as:
* 1. Having been produced by panels_load_displays(), whether or this page
* request or at some time in the past and the object was exported.
* 2. Having had some external code attach context data ($display->context),
* in the exact form expected by panes. Context matching is delicate,
* typically relying on exact string matches, so special attention must
* be taken.
*
* @var panels_display
*/
var $display;
/**
* An associative array of loaded plugins. Used primarily as a central
* location for storing plugins that require additional loading beyond
* reading the plugin definition, which is already statically cached by
* ctools_get_plugins(). An example is layout plugins, which can optionally
* have a callback that determines the set of panel regions available at
* runtime.
*
* @var array
*/
var $plugins = array();
/**
* A multilevel array of rendered data. The first level of the array
* indicates the type of rendered data, typically with up to three keys:
* 'layout', 'regions', and 'panes'. The relevant rendered data is stored as
* the value for each of these keys as it is generated:
* - 'panes' are an associative array of rendered output, keyed on pane id.
* - 'regions' are an associative array of rendered output, keyed on region
* name.
* - 'layout' is the whole of the rendered output.
*
* @var array
*/
var $rendered = array();
/**
* A multilevel array of data prepared for rendering. The first level of the
* array indicates the type of prepared data. The standard renderer populates
* and uses two top-level keys, 'panes' and 'regions':
* - 'panes' are an associative array of pane objects to be rendered, keyed
* on pane id and sorted into proper rendering order.
* - 'regions' are an associative array of regions, keyed on region name,
* each of which is itself an indexed array of pane ids in the order in
* which those panes appear in that region.
*
* @var array
*/
var $prepared = array();
/**
* Boolean state variable, indicating whether or not the prepare() method has
* been run.
*
* This state is checked in panels_renderer_standard::render_layout() to
* determine whether the prepare method should be automatically triggered.
*
* @var bool
*/
var $prep_run = FALSE;
/**
* The plugin that defines this handler.
*/
var $plugin = FALSE;
/**
* TRUE if this renderer is rendering in administrative mode
* which will allow layouts to have extra functionality.
*
* @var bool
*/
var $admin = FALSE;
/**
* Where to add standard meta information. There are three possibilities:
* - standard: Put the meta information in the normal location. Default.
* - inline: Put the meta information directly inline. This will
* not work for javascript.
*
* @var string
*/
var $meta_location = 'standard';
/**
* Include rendered HTML prior to the layout.
*
* @var string
*/
var $prefix = '';
/**
* Include rendered HTML after the layout.
*
* @var string
*/
var $suffix = '';
/**
* Receive and store the display object to be rendered.
*
* This is a psuedo-constructor that should typically be called immediately
* after object construction.
*
* @param array $plugin
* The definition of the renderer plugin.
* @param panels_display $display
* The panels display object to be rendered.
*/
function init($plugin, &$display) {
$this->plugin = $plugin;
$layout = panels_get_layout($display->layout);
$this->display = &$display;
$this->plugins['layout'] = $layout;
if (!isset($layout['regions'])) {
$this->plugins['layout']['regions'] = panels_get_regions($layout, $display);
}
if (empty($this->plugins['layout'])) {
watchdog('panels', "Layout: @layout couldn't been found, maybe the theme is disabled.", array('@layout' => $display->layout));
}
}
/**
* Prepare the attached display for rendering.
*
* This is the outermost prepare method. It calls several sub-methods as part
* of the overall preparation process. This compartmentalization is intended
* to ease the task of modifying renderer behavior in child classes.
*
* If you override this method, it is important that you either call this
* method via parent::prepare(), or manually set $this->prep_run = TRUE.
*
* @param mixed $external_settings
* An optional parameter allowing external code to pass in additional
* settings for use in the preparation process. Not used in the default
* renderer, but included for interface consistency.
*/
function prepare($external_settings = NULL) {
$this->prepare_panes($this->display->content);
$this->prepare_regions($this->display->panels, $this->display->panel_settings);
$this->prep_run = TRUE;
}
/**
* Prepare the list of panes to be rendered, accounting for visibility/access
* settings and rendering order.
*
* This method represents the standard approach for determining the list of
* panes to be rendered that is compatible with all parts of the Panels
* architecture. It first applies visibility & access checks, then sorts panes
* into their proper rendering order, and returns the result as an array.
*
* Inheriting classes should override this method if that renderer needs to
* regularly make additions to the set of panes that will be rendered.
*
* @param array $panes
* An associative array of pane data (stdClass objects), keyed on pane id.
* @return array
* An associative array of panes to be rendered, keyed on pane id and sorted
* into proper rendering order.
*/
function prepare_panes($panes) {
ctools_include('content');
// Use local variables as writing to them is very slightly faster
$first = $normal = $last = array();
// Prepare the list of panes to be rendered
foreach ($panes as $pid => $pane) {
if (empty($this->admin)) {
// TODO remove in 7.x and ensure the upgrade path weeds out any stragglers; it's been long enough
$pane->shown = !empty($pane->shown); // guarantee this field exists.
// If this pane is not visible to the user, skip out and do the next one
if (!$pane->shown || !panels_pane_access($pane, $this->display)) {
continue;
}
}
// If the pane's subtype is unique, get it so that
// hook_ctools_content_subtype_alter() and/or
// hook_ctools_block_info() will be called.
if ($pane->type != $pane->subtype) {
$content_type = ctools_content_get_subtype($pane->type, $pane->subtype);
}
else {
$content_type = ctools_get_content_type($pane->type);
}
// If this pane wants to render last, add it to the $last array. We allow
// this because some panes need to be rendered after other panes,
// primarily so they can do things like the leftovers of forms.
if (!empty($content_type['render last'])) {
$last[$pid] = $pane;
}
// If it wants to render first, add it to the $first array. This is used
// by panes that need to do some processing before other panes are
// rendered.
else if (!empty($content_type['render first'])) {
$first[$pid] = $pane;
}
// Otherwise, render it in the normal order.
else {
$normal[$pid] = $pane;
}
}
$this->prepared['panes'] = $first + $normal + $last;
// Allow other modules the alter the prepared panes array.
drupal_alter('panels_panes_prepared', $this->prepared['panes'], $this);
return $this->prepared['panes'];
}
/**
* Prepare the list of regions to be rendered.
*
* This method is primarily about properly initializing the style plugin that
* will be used to render the region. This is crucial as regions cannot be
* rendered without a style plugin (in keeping with Panels' philosophy of
* hardcoding none of its output), but for most regions no style has been
* explicitly set. The logic here is what accommodates that situation:
* - If a region has had its style explicitly set, then we fetch that plugin
* and continue.
* - If the region has no explicit style, but a style was set at the display
* level, then inherit the style from the display.
* - If neither the region nor the dispay have explicitly set styles, then
* fall back to the hardcoded 'default' style, a very minimal style.
*
* The other important task accomplished by this method is ensuring that even
* regions without any panes are still properly prepared for the rendering
* process. This is essential because the way Panels loads display objects
* (@see panels_load_displays) results only in a list of regions that
* contain panes - not necessarily all the regions defined by the layout
* plugin, which can only be determined by asking the plugin at runtime. This
* method consults that retrieved list of regions and prepares all of those,
* ensuring none are inadvertently skipped.
*
* @param array $region_pane_list
* An associative array of pane ids, keyed on the region to which those pids
* are assigned. In the default case, this is $display->panels.
* @param array $settings
* All known region style settings, including both the top-level display's
* settings (if any) and all region-specific settings (if any).
* @return array
* An array of regions prepared for rendering.
*/
function prepare_regions($region_pane_list, $settings) {
// Initialize defaults to be used for regions without their own explicit
// settings. Use display settings if they exist, else hardcoded defaults.
$default = array(
'style' => panels_get_style(!empty($settings['style']) ? $settings['style'] : 'default'),
'style settings' => isset($settings['style_settings']['default']) ? $settings['style_settings']['default'] : array(),
);
$regions = array();
if (empty($settings)) {
// No display/panel region settings exist, init all with the defaults.
foreach ($this->plugins['layout']['regions'] as $region_id => $title) {
// Ensure this region has at least an empty panes array.
$panes = !empty($region_pane_list[$region_id]) ? $region_pane_list[$region_id] : array();
$regions[$region_id] = $default;
$regions[$region_id]['pids'] = $panes;
}
}
else {
// Some settings exist; iterate through each region and set individually.
foreach ($this->plugins['layout']['regions'] as $region_id => $title) {
// Ensure this region has at least an empty panes array.
$panes = !empty($region_pane_list[$region_id]) ? $region_pane_list[$region_id] : array();
if (empty($settings[$region_id]['style']) || $settings[$region_id]['style'] == -1) {
$regions[$region_id] = $default;
}
else {
$regions[$region_id]['style'] = panels_get_style($settings[$region_id]['style']);
$regions[$region_id]['style settings'] = isset($settings['style_settings'][$region_id]) ? $settings['style_settings'][$region_id] : array();
}
$regions[$region_id]['pids'] = $panes;
}
}
$this->prepared['regions'] = $regions;
return $this->prepared['regions'];
}
/**
* Build inner content, then hand off to layout-specified theme function for
* final render step.
*
* This is the outermost method in the Panels render pipeline. It calls the
* inner methods, which return a content array, which is in turn passed to the
* theme function specified in the layout plugin.
*
* @return string
* Themed & rendered HTML output.
*/
function render() {
// Let the display refer back to the renderer.
$this->display->renderer_handler = $this;
// Attach out-of-band data first.
$this->add_meta();
if (empty($this->display->cache['method']) || !empty($this->display->skip_cache)) {
return $this->render_layout();
}
else {
$cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context);
if ($cache === FALSE) {
$cache = new panels_cache_object();
$cache->set_content($this->render_layout());
panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context);
}
return $cache->content;
}
}
/**
* Perform display/layout-level render operations.
*
* This method triggers all the inner pane/region rendering processes, passes
* that to the layout plugin's theme callback, and returns the rendered HTML.
*
* If display-level caching is enabled and that cache is warm, this method
* will not be called.
*
* @return string
* The HTML string representing the entire rendered, themed panel.
*/
function render_layout() {
if (empty($this->prep_run)) {
$this->prepare();
}
$this->render_panes();
$this->render_regions();
if ($this->admin && !empty($this->plugins['layout']['admin theme'])) {
$theme = $this->plugins['layout']['admin theme'];
}
else {
$theme = $this->plugins['layout']['theme'];
}
$this->rendered['layout'] = theme($theme, array('css_id' => check_plain($this->display->css_id), 'content' => $this->rendered['regions'], 'settings' => $this->display->layout_settings, 'display' => $this->display, 'layout' => $this->plugins['layout'], 'renderer' => $this));
return $this->prefix . $this->rendered['layout'] . $this->suffix;
}
/**
* Attach out-of-band page metadata (e.g., CSS and JS).
*
* This must be done before render, because panels-within-panels must have
* their CSS added in the right order: inner content before outer content.
*/
function add_meta() {
if (!empty($this->plugins['layout']['css'])) {
if (file_exists(path_to_theme() . '/' . $this->plugins['layout']['css'])) {
$this->add_css(path_to_theme() . '/' . $this->plugins['layout']['css']);
}
else {
$this->add_css($this->plugins['layout']['path'] . '/' . $this->plugins['layout']['css']);
}
}
if ($this->admin && isset($this->plugins['layout']['admin css'])) {
$this->add_css($this->plugins['layout']['path'] . '/' . $this->plugins['layout']['admin css']);
}
}
/**
* Add CSS information to the renderer.
*
* To facilitate previews over Views, CSS can now be added in a manner
* that does not necessarily mean just using drupal_add_css. Therefore,
* during the panel rendering process, this method can be used to add
* css and make certain that ti gets to the proper location.
*
* The arguments should exactly match drupal_add_css().
*
* @see drupal_add_css
*/
function add_css($filename) {
switch ($this->meta_location) {
case 'standard':
drupal_add_css($filename);
break;
case 'inline':
$url = base_path() . $filename;
$this->prefix .= '<link type="text/css" rel="stylesheet" href="' . file_create_url($url) . '" />'."\n";
break;
}
}
/**
* Render all prepared panes, first by dispatching to their plugin's render
* callback, then handing that output off to the pane's style plugin.
*
* @return array
* The array of rendered panes, keyed on pane pid.
*/
function render_panes() {
ctools_include('content');
// First, render all the panes into little boxes.
$this->rendered['panes'] = array();
foreach ($this->prepared['panes'] as $pid => $pane) {
$content = $this->render_pane($pane);
if ($content) {
$this->rendered['panes'][$pid] = $content;
}
}
return $this->rendered['panes'];
}
/**
* Render a pane using its designated style.
*
* This method also manages 'title pane' functionality, where the title from
* an individual pane can be bubbled up to take over the title for the entire
* display.
*
* @param stdClass $pane
* A Panels pane object, as loaded from the database.
*/
function render_pane(&$pane) {
$content = $this->render_pane_content($pane);
if ($this->display->hide_title == PANELS_TITLE_PANE && !empty($this->display->title_pane) && $this->display->title_pane == $pane->pid) {
// If the user selected to override the title with nothing, and selected
// this as the title pane, assume the user actually wanted the original
// title to bubble up to the top but not actually be used on the pane.
if (empty($content->title) && !empty($content->original_title)) {
$this->display->stored_pane_title = $content->original_title;
}
else {
$this->display->stored_pane_title = !empty($content->title) ? $content->title : '';
}
}
if (!empty($content->content)) {
if (!empty($pane->style['style'])) {
$style = panels_get_style($pane->style['style']);
if (isset($style) && isset($style['render pane'])) {
$output = theme($style['render pane'], array('content' => $content, 'pane' => $pane, 'display' => $this->display, 'style' => $style, 'settings' => $pane->style['settings']));
// This could be null if no theme function existed.
if (isset($output)) {
return $output;
}
}
}
// fallback
return theme('panels_pane', array('content' => $content, 'pane' => $pane, 'display' => $this->display));
}
}
/**
* Render the interior contents of a single pane.
*
* This method retrieves pane content and produces a ready-to-render content
* object. It also manages pane-specific caching.
*
* @param stdClass $pane
* A Panels pane object, as loaded from the database.
* @return stdClass $content
* A renderable object, containing a subject, content, etc. Based on the
* renderable objects used by the block system.
*/
function render_pane_content(&$pane) {
ctools_include('context');
// TODO finally safe to remove this check?
if (!is_array($this->display->context)) {
watchdog('panels', 'renderer::render_pane_content() hit with a non-array for the context', $this->display, WATCHDOG_DEBUG);
$this->display->context = array();
}
$content = FALSE;
$caching = !empty($pane->cache['method']) && empty($this->display->skip_cache);
if ($caching && ($cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context, $pane))) {
$content = $cache->content;
}
else {
if ($caching) {
// This is created before rendering so that calls to drupal_add_js
// and drupal_add_css will be captured.
$cache = new panels_cache_object();
}
$content = ctools_content_render($pane->type, $pane->subtype, $pane->configuration, array(), $this->display->args, $this->display->context);
if (empty($content)) {
return;
}
foreach (module_implements('panels_pane_content_alter') as $module) {
$function = $module . '_panels_pane_content_alter';
$function($content, $pane, $this->display->args, $this->display->context, $this, $this->display);
}
if ($caching && isset($cache)) {
$cache->set_content($content);
panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context, $pane);
$content = $cache->content;
}
}
// Pass long the css_id that is usually available.
if (!empty($pane->css['css_id'])) {
$content->css_id = check_plain($pane->css['css_id']);
}
// Pass long the css_class that is usually available.
if (!empty($pane->css['css_class'])) {
$content->css_class = check_plain($pane->css['css_class']);
}
return $content;
}
/**
* Render all prepared regions, placing already-rendered panes into their
* appropriate positions therein.
*
* @return array
* An array of rendered panel regions, keyed on the region name.
*/
function render_regions() {
$this->rendered['regions'] = array();
// Loop through all panel regions, put all panes that belong to the current
// region in an array, then render the region. Primarily this ensures that
// the panes are arranged in the proper order.
$content = array();
foreach ($this->prepared['regions'] as $region_id => $conf) {
$region_panes = array();
foreach ($conf['pids'] as $pid) {
// Only include panes for region rendering if they had some output.
if (!empty($this->rendered['panes'][$pid])) {
$region_panes[$pid] = $this->rendered['panes'][$pid];
}
}
$this->rendered['regions'][$region_id] = $this->render_region($region_id, $region_panes);
}
return $this->rendered['regions'];
}
/**
* Render a single panel region.
*
* Primarily just a passthrough to the panel region rendering callback
* specified by the style plugin that is attached to the current panel region.
*
* @param $region_id
* The ID of the panel region being rendered
* @param $panes
* An array of panes that are assigned to the panel that's being rendered.
*
* @return string
* The rendered, HTML string output of the passed-in panel region.
*/
function render_region($region_id, $panes) {
$style = $this->prepared['regions'][$region_id]['style'];
$style_settings = $this->prepared['regions'][$region_id]['style settings'];
// Retrieve the pid (can be a panel page id, a mini panel id, etc.), this
// might be used (or even necessary) for some panel display styles.
// TODO: Got to fix this to use panel page name instead of pid, since pid is
// no longer guaranteed. This needs an API to be able to set the final id.
$owner_id = 0;
if (isset($this->display->owner) && is_object($this->display->owner) && isset($this->display->owner->id)) {
$owner_id = $this->display->owner->id;
}
$output = theme($style['render region'], array('display' => $this->display, 'owner_id' => $owner_id, 'panes' => $panes, 'settings' => $style_settings, 'region_id' => $region_id, 'style' => $style));
return $output;
}
}

View File

@@ -0,0 +1,8 @@
<?php
/**
* Create a simple renderer plugin that renders a layout but the content is
* already rendered, not in panes.
*/
$plugin = array(
'renderer' => 'panels_renderer_simple',
);

View File

@@ -0,0 +1,5 @@
<?php
$plugin = array(
'renderer' => 'panels_renderer_standard',
);

View File

@@ -0,0 +1,23 @@
<?php
$plugin = array(
'schema' => 'panels_layout',
'access' => 'administer panels layouts',
'menu' => array(
'menu prefix' => 'admin/structure/panels',
'menu item' => 'layouts',
'menu title' => 'Layouts',
'menu description' => 'Add, edit or delete custom content layouts.',
),
'title singular' => t('layout'),
'title singular proper' => t('Layout'),
'title plural' => t('layouts'),
'title plural proper' => t('Layouts'),
'handler' => array(
'class' => 'panels_layouts_ui',
),
);

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