diff --git a/API.txt b/API.txt
index a6589524f38c81d77986e261db16fdbdb6ade388..ac5488fe88842061771688cd0e3e73885de09a80 100644
--- a/API.txt
+++ b/API.txt
@@ -1,125 +1,4 @@
-The API for expanding the panels module comes in two pieces. First there is the
-layout API, which adds to the list of layouts you need. Second is the content
-types API, which lets modules supply content to the panels. Natively, panels
-module supports the content types of 'block', which just renders the output
-of a block, 'node' which simply renders a node_view, 'custom' which allows the
-user to enter custom content with filtering, and finally 'views' because I
-wrote them both.
-
-Where to put your code:
-=======================
-
-With both types, there are two ways to implement a new type. First, you can
-implement the hook in your module and provide the necessary data. Or you
-can create a .inc file in the right format, and drop it into the proper
-directory in the panels module. Both are very similar, and only requires
-a minor naming adjustment.
-
-When using the .inc file, in place of 'hook' in the various hooks, use
-panels_FILENAME.
-
-Creating a new Layout Type:
-===========================
-
-A layout consists of 4 things:
-
-1) A bit of HTML in a theme function. I use heredoc notation to make it
- extra easy to convert these to .tpl.inc files in case they are to
- be overridden in php template.
-2) A bit of CSS to describe how the layout should be, well, laid out.
-3) An icon that is 50x75 which gives the user a visual indication of
- what the layout looks like.
-4) An implementation of hook_panels_layouts() to tell panels the necessary
- information.
-
-hook_panels_layouts returns an array with the following information:
-
-'module' => The module name providing this. This is necessary because it
- uses drupal_get_path('module', $module) to get the proper
- path for included CSS.
-'title' => The title of the layout presented to the user. Use t().
-'icon' => The filename of the icon to use when listing avialable layouts.
-'theme' => The theme function that contains the HTML, without the theme_
- part.
-'css' => The CSS file.
-'content areas' => an array in the form of 'name' => t('Title') of content
- areas supported by the layout. For example, the simple
- 2 column layout uses array('left' => t('Left side'),
- 'right' => t('Right side')); -- the name is the internal
- identifier. Your theme function will see it as
- $content['name'] (so twocol gets $content['left'] and
- $content['right']).
-
-
-Creating a new Content Type:
-============================
-
-Content types require 1 hook and two callbacks. The hook defines what content
-types are available, the first callback displays the content in a dashboard,
-and the other callback does all of the administrative functions.
-
-hook_panels_content_types returns an array with the following information:
-'callback' => The function to display the content.
-'admin' => The function to administer the content.
-
-The callback function receives one argument: The $configuration array, as
-defined by the administrative callback.
-
-The administrative callback recieves 3 arguments:
-
-$op -- the operation to perform. See below.
-&$arg1 -- The first argument should be a reference and its meaning varies
- based on the op.
-$arg2 -- The second argument is optional, not a reference, and should
- default to NULL.
-
-Administrative operations:
-
-'list': $arg1 is the configuration array.
- This op is called when panels lists what content is in a content
- area. It generally returns something similar to this:
- return 'Views: ' . $view->name . ' (' . $view->description . ')';
-
-'add button': arguments not used here.
- This op is called to display the 'add content type' button; it can also
- display additional information (such as the list of blocks or the
- autocomplete to select a node).
-
- The actual button should look something like this:
- $form['submit'] = array(
- '#type' => 'button',
- '#value' => t('Add view'),
- );
-
- And it's a good idea to do this, but it's not required:
-
- $form['#prefix'] = '
+ that directly contains your content; you can do this by
+ adding an empty
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.
\ No newline at end of file
diff --git a/README.txt b/README.txt
index f23574c2c38d9099c77b74cb113a7bc776430b4c..f055db8f3728d6af6927f99da366f5761d4d7758 100644
--- a/README.txt
+++ b/README.txt
@@ -1,22 +1,7 @@
-The panels module is the ideological son, successor to and almost complete
-replacement for the dashboard module. This module allows you to create pages
-that are divided into areas of the page. Where the dashboard module only gave
-four areas--top, bottom, left and right--this one is a completely flexible
-system that includes a couple of 2 column and 3 column layouts by default, but
-is also highly extensible and other layouts can be plugged in with a little HTML
-and CSS knowledge, with just enough PHP knowledge to be able to edit an include
-file without breaking it.
-
-Perhaps most importantly, unlike the dashboard module it requires no fiddling
-with PHP code to include the things you want; the interface lets you add blocks,
-nodes and custom content just by selecting and clicking.
-
-If you want to override the CSS of a panel, the easiest way is to just copy
-the CSS into your theme directory and tweak; panels will look there before
-including the CSS from the module, and if it exists, will not include the
-module's CSS. If you want to just change a tiny bit but keep the basic
-structure, just add your changes to your style.css instead.
-
-If you're having problems with IE and your panels falling below your sidebars,
-try setting the width of the main panel area (example, .panel-2col-stacked) to
-98%.
+# $Id$
+
+Welcome to Panels 2.
+
+A little documentation should go here, but Panels 2 is a beast - you're best
+off checking the online handbook on Drupal.org, or the developer/API docs,
+which are available at http://doxy.samboyer.org/panels2/
diff --git a/TODO.txt b/TODO.txt
index 6022dd147c22cc50bb27f9b5feb0addf148d5aab..dcfc27d1b2d60c4a85ecd423222af12d4f81779f 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,12 +1,132 @@
-TODO:
-
-This is a brief list of the things that may still need to be done. No guarantee
-of them ever getting done.
-
-content type: phptemplate
-
-access control to pages
-access control to content types
-
-dashboard node extension
-
+# $Id$
+
+PLEASE NOTE THAT THIS FILE CONTAINS VERY OUTDATED INFORMATION, KEPT ONLY FOR HISTORICAL PURPOSES.
+
+For the current plans related to Panels2, you should consult the groups.drupal.org Panels group:
+
+http://groups.drupal.org/panels
+
+Here's a rough sketch of the work plan shortly prior to the 5.x-2.0-beta4 release:
+
+5.x-beta3 ==> beta4 ==> beta5 ==> RC1 ==> RCs... ==> 5.x RELEASE!! ==> 5.x-2.1
+ |
+ 6.x port ==> 6.x RCs... ==> 6.x RELEASE!! ==> 6.x-2.1
+ |
+ 6.x-3.x-ALPHA ==> (FUTURE OF PANELS)
+
+==========================================================
+
+TODO:
+
+X = DONE
+o = TODO
+- = Skipped/postponed
+
+ajax/js
+ o remove panels_hidden, use only javascript
+ o Remove 'panels_set' and use the broken up drupal_get_form functions
+ rather than drupal_get_form for our multi-step stuff.
+ X Add permission checking to all the ajax functions
+ o Break up panels_ajax into separate entry areas [mostly done]
+ o ensure complete degradability
+ X Make sure the icons on the 'change layout' page are clickable
+ X Loading animation for panels-modal
+ X Make the titlebar outside of the overflow in the modal
+ o Change show/hide to use AJAX.
+ X Write my own drag & drop
+
+modules
+ X panels node
+ X mini-panel
+ o panels-profile
+ o panels-dashboard-compat (upgrade all the old dashboard installs)
+
+panels common
+ o update settings to use categories like the 'add content' popup now does.
+ o Clean up and better document this code
+
+panels page
+ X panels page 'default' panels
+ X allow panel-page to use $arg/$node/$node-type/$user substitution like Views
+ X allow panel-page to get a context; allow modules to provide this context.
+ X allow panel-page to do menu item & tab like Views.
+ X provide 'edit' tab for panels pages
+ X allow setting to not display blocks
+ o Clone panels page
+
+mini panels
+ X Allow mini panels to require an argument (and thus be just panel content)
+
+content types
+ - Allow creating new custom blocks.
+ X Get all 'block' settings into blocks.
+ X Remove all calls to the 'admin' callback
+ X Make panels_get_content_type() like using for layout
+ X Translate views to new content format
+ X Translate node to new content format
+ X Translate custom to new content format
+ X Allow content types to be aware of panel context.
+ X Allow content types to include or exclude themselves based upon context.
+ X Figure out a way for blocks to easily have per-block icons
+ X sort blocks in content type list
+ X sort views in content type list
+ X add weight so custom and node will float up
+ X make forms fit a bit better
+ X Allow display to have 'arguments' that can be passed on to Views
+ more fluidly
+ X give views.inc setting to NOT use arguments.
+ X unify title overriding.
+ X node_content type
+ X taxonomy term types
+ o user types
+ o views context sensitive conversions
+
+layouts
+ X Allow each layout to have settings
+ X Create a flexible layout where you can quickly create arbitrary layouts
+ X Fix the div div problem in all layouts
+
+icons
+ X Change configure icon
+ X Change remove pane icon
+ X Find add content icon
+ X Create icons for content types
+
+Flexible layout
+ o Allow fixed width sidebars
+ o Use a slider to control widths
+
+Styles:
+ o Convert DND to a style
+ X Rounded corners style
+
+Arguments:
+ X taxonomy argument
+ X user argument
+
+panels API
+ X Provide method to directly edit/configure an individual content block if viewer has permissions
+ X Provide method to control pane visibility. Make sure this is controllable from the admin so that not all users can get to it.
+ X Change add content to be per panel
+ X Make Cancel actually cancel
+ X Wrap calls to existing callbacks
+ X Make a function to simply get the IDs of all content-types
+ X Allow panels_edit_display to restrict available content to a preprepared list
+ X Use cache instead of putting everything in $_SESSION
+ X create panels_render_display() (or panels_display_render)
+ X Allow API to accept context
+ X Allow API to accept content
+ X Allow API to accept arguments
+
+General:
+ o CSS class/id name cleanup and unification.
+ o Make sure core blocks that require context are labelled as such.
+
+Documentation
+ o Creating new panel modules that use the API
+ o Creating new content types
+ o Creating new layouts
+ o Creating new styles
+ o Creating new arguments
+ o Themeing panels
+ o Doing interesting things with Panels
diff --git a/arguments/nid.inc b/arguments/nid.inc
new file mode 100644
index 0000000000000000000000000000000000000000..db9522b46ef3133a1be141d5d99729082b827d4b
--- /dev/null
+++ b/arguments/nid.inc
@@ -0,0 +1,162 @@
+ t("Node ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'node',
+ 'description' => t('Restricts the argument to a node id.'),
+ 'context' => 'panels_nid_context',
+ 'settings form' => 'panels_nid_settings_form',
+ 'settings form submit' => 'panels_nid_settings_form_submit',
+ 'displays' => 'panels_nid_displays',
+ 'choose display' => 'panels_nid_choose_display',
+ );
+ return $args;
+}
+
+/**
+ * Discover if this argument gives us the node we crave.
+ */
+function panels_nid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return panels_context_create_empty('node');
+ }
+
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $node = node_load($arg);
+ if (!$node) {
+ return FALSE;
+ }
+
+ if (array_filter($conf['types']) && empty($conf['types'][$node->type])) {
+ return FALSE;
+ }
+
+ return panels_context_create('node', $node);
+}
+
+/**
+ * Settings form for the argument
+ */
+function panels_nid_settings_form($conf) {
+ $options = array();
+ foreach (node_get_types() as $type => $info) {
+ $options[$type] = $info->name;
+ }
+
+ $form['types'] = array(
+ '#title' => t('Node types'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('You can restrict this argument to use the checked node types. Arguments from non-conforming node types will be ignored, and Panels will behave as if no argument were given. Leave all unchecked to impose no restriction.'),
+ '#default_value' => $conf['types'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ $form['own_default'] = array(
+ '#title' => t('Use different default display'),
+ '#type' => 'checkbox',
+ '#description' => t('If checked, when this argument is present it will use its own display rather than the default. Node types not selected in the "Own display" field will use this one.'),
+ '#default_value' => $conf['own_default'],
+ );
+
+ $form['displays'] = array(
+ '#title' => t('Own display'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => $conf['displays'],
+ '#description' => t('Each checked node type will get its own special display to layout its content. Only node types set above should be set here. Node types not set here will use the default display.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ return $form;
+}
+
+/**
+ * There appears to be a bit of a bug with the way we're handling forms; it causes
+ * 'checkboxes' to get invalid values added to them when empty. This takes care
+ * of that.
+ */
+function panels_nid_settings_form_submit(&$values) {
+ $types = node_get_types();
+ if (!empty($values['types'])) {
+ foreach ($values['types'] as $type => $value) {
+ if (empty($types[$type])) {
+ unset($values['types'][$type]);
+ }
+ }
+ }
+ if (!empty($values['displays'])) {
+ foreach ($values['displays'] as $type => $value) {
+ if (empty($types[$type])) {
+ unset($values['displays'][$type]);
+ }
+ }
+ }
+}
+
+/**
+ * What additional displays does this argument provide?
+ */
+function panels_nid_displays($conf, $id) {
+ $displays = array();
+ if (!empty($conf['own_default'])) {
+ $displays['default'] = array(
+ 'title' => t('Node ID @id Default', array('@id' => $id)),
+ 'context' => 'node',
+ );
+ }
+
+ if (is_array($conf['displays'])) {
+ $options = array();
+ foreach (node_get_types() as $type => $info) {
+ $options[$type] = $info->name;
+ }
+ foreach (array_keys(array_filter($conf['displays'])) as $type) {
+ $displays[$type] = array(
+ 'title' => t('Node ID @id @type', array('@id' => $id, '@type' => $options[$type])),
+ // Tell it to base the template for this display off of the default.
+ 'default' => 'default',
+ 'context' => 'node',
+ );
+ }
+ }
+
+ return $displays;
+}
+
+/**
+ * Based upon the settings and the context, choose which display to use.
+ */
+function panels_nid_choose_display($conf, $context) {
+ if (empty($context->data)) {
+ return;
+ }
+
+ if (!empty($conf['displays'][$context->data->type])) {
+ return $context->data->type;
+ }
+
+ // Please note that 'default' is a special display.
+ if (!empty($conf['own_default'])) {
+ return 'default';
+ }
+
+ // Empty return says to use the default display.
+ return;
+}
+
diff --git a/arguments/node_add.inc b/arguments/node_add.inc
new file mode 100644
index 0000000000000000000000000000000000000000..ccf4fc970011904ea0be4e69be46ce367b4ef491
--- /dev/null
+++ b/arguments/node_add.inc
@@ -0,0 +1,154 @@
+ t("Node add form"),
+ // keyword to use for %substitution
+ 'keyword' => 'node_type',
+ 'description' => t('Displays the node add form for a content type.'),
+ 'context' => 'panels_node_add_context',
+ 'settings form' => 'panels_node_add_settings_form',
+ 'settings form submit' => 'panels_node_add_settings_form_submit',
+ 'displays' => 'panels_node_add_displays',
+ 'choose display' => 'panels_node_add_choose_display',
+ );
+ return $args;
+}
+
+/**
+ * Discover if this argument gives us the node we crave.
+ */
+function panels_node_add_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if (!isset($arg)) {
+ return panels_context_create_empty('node_add_form');
+ }
+
+ if (array_filter($conf['types']) && empty($conf['types'][$arg])) {
+ return FALSE;
+ }
+
+ return panels_context_create('node_add_form', $arg);
+}
+
+/**
+ * Settings form for the argument
+ */
+function panels_node_add_settings_form($conf) {
+ $options = array();
+ foreach (node_get_types() as $type => $info) {
+ $options[$type] = $info->name;
+ }
+ $form['types'] = array(
+ '#title' => t('Node types'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('You can restrict this argument to use the checked node types. Arguments from non-conforming node types will be ignored, and Panels will behave as if no argument were given. Leave all unchecked to impose no restriction.'),
+ '#default_value' => $conf['types'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ $form['own_default'] = array(
+ '#title' => t('Use different default display'),
+ '#type' => 'checkbox',
+ '#description' => t('If checked, when this argument is present it will use its own display rather than the default. Node types not selected in the "Own display" field will use this one.'),
+ '#default_value' => $conf['own_default'],
+ );
+
+ $form['displays'] = array(
+ '#title' => t('Own display'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => $conf['displays'],
+ '#description' => t('Each checked node type will get its own special display to layout its content. Only node types set above should be set here. Node types not set here will use the default display.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ return $form;
+}
+
+/**
+ * There appears to be a bit of a bug with the way we're handling forms; it causes
+ * 'checkboxes' to get invalid values added to them when empty. This takes care
+ * of that.
+ */
+function panels_node_add_settings_form_submit(&$values) {
+ $types = node_get_types();
+ if (!empty($values['types'])) {
+ foreach ($values['types'] as $type => $value) {
+ if (empty($types[$type])) {
+ unset($values['types'][$type]);
+ }
+ }
+ }
+ if (!empty($values['displays'])) {
+ foreach ($values['displays'] as $type => $value) {
+ if (empty($types[$type])) {
+ unset($values['displays'][$type]);
+ }
+ }
+ }
+}
+
+/**
+ * Based upon the settings and the context, choose which display to use.
+ */
+function panels_node_add_choose_display($conf, $context) {
+ if (empty($context->form)) {
+ return;
+ }
+
+ if (!empty($conf['displays'][$context->node_type])) {
+ return $context->node_type;
+ }
+
+ // Please note that 'default' is a special display.
+ if (!empty($conf['own_default'])) {
+ return 'default';
+ }
+
+ // Empty return says to use the default display.
+ return;
+}
+
+/**
+ * What additional displays does this argument provide?
+ */
+function panels_node_add_displays($conf, $id) {
+ $displays = array();
+ if (!empty($conf['own_default'])) {
+ $displays['default'] = array(
+ 'title' => t('Node add form @id Default', array('@id' => $id)),
+ 'context' => 'node',
+ );
+ }
+
+ if (is_array($conf['displays'])) {
+ $options = array();
+ foreach (node_get_types() as $type => $info) {
+ $options[$type] = $info->name;
+ }
+ foreach (array_keys(array_filter($conf['displays'])) as $type) {
+ if (!empty($options[$type])) {
+ $displays[$type] = array(
+ 'title' => t('Node add form @id @type', array('@id' => $id, '@type' => $options[$type])),
+ // Tell it to base the template for this display off of the default.
+ 'default' => 'default',
+ 'context' => 'node',
+ );
+ }
+ }
+ }
+
+ return $displays;
+}
+
diff --git a/arguments/node_edit.inc b/arguments/node_edit.inc
new file mode 100644
index 0000000000000000000000000000000000000000..480b29878aede980e9e8042c6982d3af2c8b9915
--- /dev/null
+++ b/arguments/node_edit.inc
@@ -0,0 +1,162 @@
+ t("Node edit form"),
+ // keyword to use for %substitution
+ 'keyword' => 'node',
+ 'description' => t('Displays the node edit form for a node.'),
+ 'context' => 'panels_node_edit_context',
+ 'settings form' => 'panels_node_edit_settings_form',
+ 'settings form submit' => 'panels_node_edit_settings_form_submit',
+ 'displays' => 'panels_node_edit_displays',
+ 'choose display' => 'panels_node_edit_choose_display',
+ );
+ return $args;
+}
+
+/**
+ * Discover if this argument gives us the node we crave.
+ */
+function panels_node_edit_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return panels_context_create_empty('node_edit_form');
+ }
+
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $node = node_load($arg);
+ if (!$node) {
+ return FALSE;
+ }
+
+ if (array_filter($conf['types']) && empty($conf['types'][$node->type])) {
+ return FALSE;
+ }
+
+ // This will perform a node_access check, so we don't have to.
+ return panels_context_create('node_edit_form', $node);
+}
+
+/**
+ * Settings form for the argument
+ */
+function panels_node_edit_settings_form($conf) {
+ $options = array();
+ foreach (node_get_types() as $type => $info) {
+ $options[$type] = $info->name;
+ }
+ $form['types'] = array(
+ '#title' => t('Node types'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('You can restrict this argument to use the checked node types. Arguments from non-conforming node types will be ignored, and Panels will behave as if no argument were given. Leave all unchecked to impose no restriction.'),
+ '#default_value' => $conf['types'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ $form['own_default'] = array(
+ '#title' => t('Use different default display'),
+ '#type' => 'checkbox',
+ '#description' => t('If checked, when this argument is present it will use its own display rather than the default. Node types not selected in the "Own display" field will use this one.'),
+ '#default_value' => $conf['own_default'],
+ );
+
+ $form['displays'] = array(
+ '#title' => t('Own display'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => $conf['displays'],
+ '#description' => t('Each checked node type will get its own special display to layout its content. Only node types set above should be set here. Node types not set here will use the default display.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ return $form;
+}
+
+/**
+ * There appears to be a bit of a bug with the way we're handling forms; it causes
+ * 'checkboxes' to get invalid values added to them when empty. This takes care
+ * of that.
+ */
+function panels_node_edit_settings_form_submit(&$values) {
+ $types = node_get_types();
+ if (!empty($values['types'])) {
+ foreach ($values['types'] as $type => $value) {
+ if (empty($types[$type])) {
+ unset($values['types'][$type]);
+ }
+ }
+ }
+ if (!empty($values['displays'])) {
+ foreach ($values['displays'] as $type => $value) {
+ if (empty($types[$type])) {
+ unset($values['displays'][$type]);
+ }
+ }
+ }
+}
+
+/**
+ * What additional displays does this argument provide?
+ */
+function panels_node_edit_displays($conf, $id) {
+ $displays = array();
+ if (!empty($conf['own_default'])) {
+ $displays['default'] = array(
+ 'title' => t('Node edit form @id Default', array('@id' => $id)),
+ 'context' => 'node',
+ );
+ }
+
+ if (is_array($conf['displays'])) {
+ $options = array();
+ foreach (node_get_types() as $type => $info) {
+ $options[$type] = $info->name;
+ }
+ foreach (array_keys(array_filter($conf['displays'])) as $type) {
+ $displays[$type] = array(
+ 'title' => t('Node edit form @id @type', array('@id' => $id, '@type' => $options[$type])),
+ // Tell it to base the template for this display off of the default.
+ 'default' => 'default',
+ 'context' => 'node',
+ );
+ }
+ }
+
+ return $displays;
+}
+
+/**
+ * Based upon the settings and the context, choose which display to use.
+ */
+function panels_node_edit_choose_display($conf, $context) {
+ if (empty($context->form)) {
+ return;
+ }
+
+ if (!empty($conf['displays'][$context->node_type])) {
+ return $context->node_type;
+ }
+
+ // Please note that 'default' is a special display.
+ if (!empty($conf['own_default'])) {
+ return 'default';
+ }
+
+ // Empty return says to use the default display.
+ return;
+}
+
diff --git a/arguments/term.inc b/arguments/term.inc
new file mode 100644
index 0000000000000000000000000000000000000000..31a05f1adc3684d88fb5feab14140c4214f58564
--- /dev/null
+++ b/arguments/term.inc
@@ -0,0 +1,213 @@
+ t("Taxonomy term"),
+ // keyword to use for %substitution
+ 'keyword' => 'term',
+ 'description' => t('Restricts the argument to a taxonomy term.'),
+ 'context' => 'panels_term_context',
+ 'settings form' => 'panels_term_settings_form',
+ 'settings form submit' => 'panels_term_settings_form_submit',
+ 'title function' => 'panels_term_title',
+ 'displays' => 'panels_term_displays',
+ 'choose display' => 'panels_term_choose_display',
+ );
+ return $args;
+}
+
+/**
+ * Discover if this argument gives us the term we crave.
+ */
+function panels_term_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return panels_context_create_empty('term');
+ }
+
+ switch ($conf['input_form']) {
+ case 'tid':
+ default:
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+ $term = taxonomy_get_term($arg);
+ break;
+
+ case 'term':
+ $terms = taxonomy_get_term_by_name($arg);
+ if (count($terms) != 1) {
+ foreach ($terms as $potential) {
+ foreach ($conf['vids'] as $vid => $active) {
+ if ($active == 1 && $potential->vid == $vid) {
+ $term = $potential;
+ // break out of the foreaches AND the case
+ break 3;
+ }
+ }
+ }
+ }
+ $term = array_shift($terms);
+ break;
+ }
+
+ if (empty($term)) {
+ return FALSE;
+ }
+
+ if (!empty($conf['vids']) && array_filter($conf['vids']) && empty($conf['vids'][$term->vid])) {
+ return FALSE;
+ }
+
+ return panels_context_create('term', $term);
+}
+
+/**
+ * Settings form for the argument
+ */
+function panels_term_settings_form($conf) {
+ if (empty($conf)) {
+ $conf = array(
+ 'input_form' => 'tid',
+ );
+ }
+
+ $options = array();
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ }
+
+ $form['input_form'] = array(
+ '#title' => t('Argument type'),
+ '#type' => 'radios',
+ '#options' => array('tid' => t('Term ID'), 'term' => t('Term name')),
+ '#default_value' => $conf['input_form'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+
+ $form['vids'] = array(
+ '#title' => t('Vocabularies'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#description' => t('You can restrict this argument to use the checked vocabularies. Arguments from non-conforming vocabularies will be ignored, and Panels will behave as if no argument were given. Leave all unchecked to impose no restriction.'),
+ '#default_value' => $conf['vids'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ $form['own_default'] = array(
+ '#title' => t('Use different default display'),
+ '#type' => 'checkbox',
+ '#description' => t('If checked, when this argument is present it will use its own display rather than the default. Vocabularies not selected in the "Own display" field will use this one.'),
+ '#default_value' => $conf['own_default'],
+ );
+
+ $form['displays'] = array(
+ '#title' => t('Own display'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => $conf['displays'],
+ '#description' => t('Each checked vocabulary will get its own special display to layout its content. Only vocabularies set above should be set here. Vocabularies not set here will use the default display.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ return $form;
+}
+
+/**
+ * There appears to be a bit of a bug with the way we're handling forms; it causes
+ * 'checkboxes' to get invalid values added to them when empty. This takes care
+ * of that.
+ */
+function panels_term_settings_form_submit(&$values) {
+ $vocs = taxonomy_get_vocabularies();
+ if (!empty($values['vids'])) {
+ foreach ($values['vids'] as $vid => $value) {
+ if (empty($vocs[$vid])) {
+ unset($values['vids'][$vid]);
+ }
+ }
+ }
+ if (!empty($values['displays'])) {
+ foreach ($values['displays'] as $vid => $value) {
+ if (empty($vocs[$vid])) {
+ unset($values['displays'][$vid]);
+ }
+ }
+ }
+}
+
+/**
+ * What additional displays does this argument provide?
+ */
+function panels_term_displays($conf, $id) {
+ $displays = array();
+ if (!empty($conf['own_default'])) {
+ $displays['default'] = array(
+ 'title' => t('Taxonomy term @id Default', array('@id' => $id)),
+ 'context' => 'term',
+ );
+ }
+
+ if (is_array($conf['displays'])) {
+ $options = array();
+ foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+ $options[$vid] = $vocabulary->name;
+ }
+ foreach (array_keys(array_filter($conf['displays'])) as $vid) {
+ $displays[$vid] = array(
+ 'title' => t('Taxonomy term @id @vocabulary', array('@id' => $id, '@vocabulary' => $options[$vid])),
+ // Tell it to base the template for this display off of the default.
+ 'default' => 'default',
+ 'context' => 'term',
+ );
+ }
+ }
+
+ return $displays;
+}
+
+/**
+ * Based upon the settings and the context, choose which display to use.
+ */
+function panels_term_choose_display($conf, $context) {
+ if (empty($context->data)) {
+ return;
+ }
+
+ if (!empty($conf['displays'][$context->data->vid])) {
+ return $context->data->vid;
+ }
+
+ // Please note that 'default' is a special display.
+ if (!empty($conf['own_default'])) {
+ return 'default';
+ }
+
+ // Empty return says to use the default display.
+ return;
+}
+
+/**
+ * Determine the title for substitution with this context
+ */
+function panels_term_title($context) {
+ if (isset($context->data->name)) {
+ return $context->data->name;
+ }
+
+ if (isset($context->page_title)) {
+ return $context->page_title;
+ }
+}
+
diff --git a/arguments/uid.inc b/arguments/uid.inc
new file mode 100644
index 0000000000000000000000000000000000000000..7d7984bd0f1d6d56206acf654ea6e5fd74a9fa5d
--- /dev/null
+++ b/arguments/uid.inc
@@ -0,0 +1,73 @@
+ t("User ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'user',
+ 'description' => t('Creates a user object from the argument.'),
+ 'context' => 'panels_uid_context',
+ 'settings form' => 'panels_uid_settings_form',
+ 'settings form submit' => 'panels_uid_settings_form_submit',
+ 'displays' => 'panels_uid_displays',
+ 'choose display' => 'panels_uid_choose_display',
+ );
+ return $args;
+}
+
+/**
+ * Discover if this argument gives us the user we crave.
+ */
+function panels_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return panels_context_create_empty('user');
+ }
+
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $user = user_load(array('uid' => $arg));
+ if (!$user) {
+ return FALSE;
+ }
+
+ return panels_context_create('user', $user);
+}
+
+/**
+ * Settings form for the argument
+ */
+function panels_uid_settings_form($conf) {
+ // Doing different displays based upon role is hard because roles are not
+ // 1:1 like type/vocabulary are for node and term.
+}
+
+/**
+ * There appears to be a bit of a bug with the way we're handling forms; it causes
+ * 'checkboxes' to get invalid values added to them when empty. This takes care
+ * of that.
+ */
+function panels_uid_settings_form_submit(&$values) {
+}
+
+/**
+ * What additional displays does this argument provide?
+ */
+function panels_uid_displays($conf, $id) {
+ return array();
+}
+
+/**
+ * Based upon the settings and the context, choose which display to use.
+ */
+function panels_uid_choose_display($conf, $context) {
+}
diff --git a/arguments/vid.inc b/arguments/vid.inc
new file mode 100644
index 0000000000000000000000000000000000000000..bcde19d7d319a982e5552186d091096dc83520f7
--- /dev/null
+++ b/arguments/vid.inc
@@ -0,0 +1,123 @@
+ t("Vocabulary ID"),
+ // keyword to use for %substitution
+ 'keyword' => 'vocabulary',
+ 'description' => t('Loads a vocabulary object from the argument.'),
+ 'context' => 'panels_vid_context',
+ 'settings form' => 'panels_vid_settings_form',
+ 'settings form submit' => 'panels_vid_settings_form_submit',
+ 'displays' => 'panels_vid_displays',
+ 'choose display' => 'panels_vid_choose_display',
+ );
+ return $args;
+}
+
+/**
+ * Discover if this argument gives us the vocabulary we crave.
+ */
+function panels_vid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+ // If unset it wants a generic, unfilled context.
+ if ($empty) {
+ return panels_context_create_empty('vocabulary');
+ }
+
+ if (!is_numeric($arg)) {
+ return FALSE;
+ }
+
+ $vocabulary = taxonomy_get_vocabulary($arg);
+ if (!$vocabulary) {
+ return FALSE;
+ }
+
+ return panels_context_create('vocabulary', $vocabulary);
+}
+
+/**
+ * Settings form for the argument
+ */
+function panels_vid_settings_form($conf) {
+ $options = array();
+ foreach (taxonomy_get_vocabularies() as $vid => $voc) {
+ $options[$vid] = $voc->name;
+ }
+
+ $form['displays'] = array(
+ '#title' => t('Own display'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#default_value' => $conf['displays'],
+ '#description' => t('Each checked vocabulary will get its own special display to layout its content.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ return $form;
+}
+
+/**
+ * There appears to be a bit of a bug with the way we're handling forms; it causes
+ * 'checkboxes' to get invalid values added to them when empty. This takes care
+ * of that.
+ */
+function panels_vid_settings_form_submit(&$values) {
+ $vocs = taxonomy_get_vocabularies();
+ if (!empty($values['displays'])) {
+ foreach ($values['displays'] as $vid => $value) {
+ if (empty($vocs[$vid])) {
+ unset($values['displays'][$vid]);
+ }
+ }
+ }
+}
+
+/**
+ * What additional displays does this argument provide?
+ */
+function panels_vid_displays($conf, $id) {
+ $displays = array();
+
+ if (is_array($conf['displays'])) {
+ $options = array();
+ foreach (taxonomy_get_vocabularies() as $vid => $info) {
+ $options[$vid] = $info->name;
+ }
+ foreach (array_keys(array_filter($conf['displays'])) as $vid) {
+ $displays[$vid] = array(
+ 'title' => t('vocabulary ID @id @type', array('@id' => $id, '@type' => $options[$vid])),
+ // Tell it to base the template for this display off of the default.
+ 'default' => 'default',
+ 'context' => 'vocabulary',
+ );
+ }
+ }
+
+ return $displays;
+}
+
+/**
+ * Based upon the settings and the context, choose which display to use.
+ */
+function panels_vid_choose_display($conf, $context) {
+ if (empty($context->data)) {
+ return;
+ }
+
+ if (!empty($conf['displays'][$context->data->vid])) {
+ return $context->data->vid;
+ }
+
+ // Empty return says to use the default display.
+ return;
+}
+
diff --git a/content_types/block.inc b/content_types/block.inc
index 0d6e7f5af02a65fb1f47cab13d7967fb733b90d6..555e23522cddb46c31d0727243a506a8317bade5 100644
--- a/content_types/block.inc
+++ b/content_types/block.inc
@@ -1,111 +1,323 @@
- 'panels_content_block',
- 'admin' => 'panels_admin_block',
- );
- return $items;
-}
-
-/**
- * Output function for the 'block' content type. Outputs a block
- * based on the module and delta supplied in the configuration.
- */
-function panels_content_block($conf) {
- $block = (object) module_invoke($conf['module'], 'block', 'view', $conf['delta']);
- $block->module = $conf['module'];
- $block->delta = $conf['delta'];
- if ($conf['override_title']) {
- $block->subject = check_plain($conf['override_title_text']);
- }
-
- $output = theme('block', $block);
- return $output;
-}
-
-/**
- * Callback to perform administrative functions on the content block
- */
-function panels_admin_block($op, &$arg, $arg2 = NULL) {
- switch ($op) {
- case 'list':
- $conf = $arg;
- $block = module_invoke($conf['module'], 'block', 'list');
- $title = $block[$conf['delta']]['info'];
- if ($conf['override_title']) {
- $title .= ' [' . check_plain($conf['override_title_text']) . ']';
- }
- return '
Block: ' . $title . ' (' . $conf['module'] . '-' . $conf['delta'] . ')';
- case 'add button':
- foreach (module_list() as $module) {
- $module_blocks = module_invoke($module, 'block', 'list');
- if ($module_blocks) {
- $array = array();
- foreach ($module_blocks as $delta => $block) {
- // strip_tags used because it goes through check_plain and that
- // just looks bad.
- $array["$module-$delta"] = strip_tags($block['info']);
- }
- $options[$module] = $array;
- }
- }
- $form['block'] = array(
- '#type' => 'select',
- '#options' => $options,
- '#title' => t('Block'),
- );
- $form['submit'] = array(
- '#type' => 'button',
- '#value' => t('Add block'),
- );
-
- $form['#prefix'] = '
';
- $form['#suffix'] = '
';
- return $form;
- case 'add':
- if ($_POST['op'] != t('Add block')) {
- return;
- }
- $form = &$arg;
- $conf = array();
- list($conf['module'], $conf['delta']) = explode('-', $form['block'], 2);
- // take the data given to us and return a fully formed $area object.
- return $conf;
- case 'edit':
- $conf = &$arg;
- $form['module'] = array(
- '#type' => 'hidden',
- '#default_value' => $conf['module'],
- );
- $form['delta'] = array(
- '#type' => 'hidden',
- '#default_value' => $conf['delta'],
- );
- $form['override_title'] = array(
- '#type' => 'checkbox',
- '#default_value' => $conf['override_title'],
- '#title' => t('Override title'),
- '#description' => t('If checked, the block title will be overridden with the override title text.')
- );
- $form['override_title_text'] = array(
- '#type' => 'textfield',
- '#default_value' => $conf['override_title_text'],
- '#title' => t('Override title text'),
- '#size' => 15,
- );
-
- return $form;
- case 'validate':
- // This one has nothing to validate.
- $form_values = &$arg;
- $form = $arg2;
- return;
- case 'save':
- // For this one, the form values go directly into the config.
- $form = &$arg;
- return $form;
- }
-}
+ t('Block'),
+ 'content_types' => 'panels_admin_content_types_block',
+ 'render callback' => 'panels_content_block',
+ 'add callback' => 'panels_admin_add_block',
+ 'edit callback' => 'panels_admin_edit_block',
+ 'title callback' => 'panels_admin_title_block',
+ 'add submit callback' => 'panels_admin_submit_block',
+ 'edit submit callback' => 'panels_admin_submit_block',
+ //'validate callback' => 'panels_admin_validate_block',
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'block' content type. Outputs a block
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_block($conf) {
+ $block = (object) module_invoke($conf['module'], 'block', 'view', $conf['delta']);
+ if (empty($block)) {
+ return;
+ }
+
+ $block->module = $conf['module'];
+ $block->delta = $conf['delta'];
+
+ if (user_access('administer blocks')) {
+ $block->admin_links = array(
+ array(
+ 'title' => t('Configure block'),
+ 'alt' => t("Configure this pane's 'block settings' in administer >> site building >> blocks"),
+ 'href' => "admin/build/block/configure/$block->module/$block->delta",
+ 'query' => drupal_get_destination(),
+ ),
+ );
+ }
+
+ // This seems extra but it prevents an unnecessary query sometimes.
+ if (empty($conf['block_visibility']) && $block->module != 'block') {
+ return $block;
+ }
+
+ // Test for block visibility
+ $result = db_query("SELECT title, pages, visibility FROM {blocks} WHERE module = '%s' AND delta = '%s'", $block->module, $block->delta);
+ $block_visibility = db_fetch_object($result);
+
+ if ($block->module == 'block') {
+ $block->title = $block_visibility->title;
+ }
+
+ if (empty($conf['block_visibility'])) {
+ return $block;
+ }
+
+ if ($block_visibility && $block_visibility->pages) {
+ if ($block_visibility->visibility < 2) {
+ $path = drupal_get_path_alias($_GET['q']);
+ $regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\
($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($block_visibility->pages, '/')) .')$/';
+ $page_match = !($block_visibility->visibility xor preg_match($regexp, $path));
+ }
+ else {
+ $page_match = drupal_eval($block_visibility->pages);
+ }
+ }
+ else {
+ $page_match = TRUE;
+ }
+
+ if ($page_match) {
+ return $block;
+ }
+}
+
+/**
+ * Return all block content types available.
+ *
+ * @ingroup hook_invocations
+ *
+ * Modules wanting to make special adjustments the way that panels handles their blocks
+ * can implement an extension to the hook_block() family, where the function name is
+ * of the form "$module . '_panels_block_info'".
+ */
+function panels_admin_content_types_block() {
+ $types = array();
+ foreach (module_list() as $module) {
+ $module_blocks = module_invoke($module, 'block', 'list');
+ if ($module_blocks) {
+ foreach ($module_blocks as $delta => $block) {
+ // strip_tags used because it goes through check_plain and that
+ // just looks bad.
+ $info = array(
+ 'title' => strip_tags($block['info']),
+ );
+
+ // Ask around for further information by invoking the hook_block() extension.
+ $function = $module . '_panels_block_info';
+ if (!function_exists($function)) {
+ $function = 'panels_default_block_info';
+ }
+ $function($module, $delta, $info);
+
+ // this check means modules can remove their blocks; particularly useful
+ // if they offer the block some other way (like we do for views)
+ if ($info) {
+ $types["$module-$delta"] = $info;
+ }
+ }
+ }
+ }
+ return $types;
+}
+
+/**
+ * Returns the form for a new block.
+ */
+function panels_admin_add_block($id, $parents, $conf = array()) {
+ list($conf['module'], $conf['delta']) = explode('-', $id, 2);
+ return panels_admin_edit_block($id, $parents, $conf);
+}
+
+/**
+ * Returns an edit form for a block.
+ */
+function panels_admin_edit_block($id, $parents, $conf) {
+ $form['module'] = array(
+ '#type' => 'value',
+ '#value' => $conf['module'],
+ );
+ $form['delta'] = array(
+ '#type' => 'value',
+ '#value' => $conf['delta'],
+ );
+
+ if (user_access('administer advanced pane settings')) {
+ $form['block_visibility'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use block visibility settings (see block config)'),
+ '#default_value' => !empty($conf['block_visibility']),
+ '#description' => t('If checked, the block visibility settings for this block will apply to this block.'),
+ );
+ // Module-specific block configurations.
+ if ($settings = module_invoke($conf['module'], 'block', 'configure', $conf['delta'])) {
+ // Specifically modify a couple of core block forms.
+ if ($conf['module'] == 'block') {
+ unset($settings['submit']);
+ $settings['info']['#type'] = 'value';
+ $settings['info']['#value'] = $settings['info']['#default_value'];
+ }
+ panels_admin_fix_block_tree($settings);
+ $form['block_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Block settings'),
+ '#description' => t('Settings in this section are global and are for all blocks of this type, anywhere in the system.'),
+ '#tree' => FALSE,
+ );
+
+
+ $form['block_settings'] += $settings;
+ }
+ }
+
+ return $form;
+}
+
+function panels_admin_submit_block(&$form_values) {
+ if (!empty($form_values['block_settings'])) {
+ module_invoke($form_values['module'], 'block', 'save', $form_values['delta'], $form_values['block_settings']);
+ }
+}
+
+/**
+ * Because form api cannot collapse just part of a tree, and the block settings
+ * assume no tree, we have to collapse the tree ourselves.
+ */
+function panels_admin_fix_block_tree(&$form, $key = NULL) {
+ if ($key) {
+ if (!empty($form['#parents'])) {
+ $form['#parents'] = array_merge(array('configuration', 'block_settings'), $form['#parents']);
+ }
+ else if (empty($form['#tree'])) {
+ $form['#parents'] = array('configuration', 'block_settings', $key);
+ }
+ }
+
+ if (isset($form['#type']) && $form['#type'] == 'textarea' && !empty($form['#rows']) && $form['#rows'] > 10) {
+ $form['#rows'] = 10;
+ }
+
+ foreach (element_children($form) as $key) {
+ panels_admin_fix_block_tree($form[$key], $key);
+ }
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function panels_admin_title_block($conf) {
+ $block = module_invoke($conf['module'], 'block', 'list');
+ if (empty($block) || empty($block[$conf['delta']])) {
+ return t('Deleted/missing block @module-@delta', array('@module' => $conf['module'], '@delta' => $conf['delta']));
+ }
+
+ $title = filter_xss_admin($block[$conf['delta']]['info']);
+ return $title;
+}
+
+function panels_default_block_info($module, $delta, &$info) {
+ $core_modules = array('aggregator', 'block', 'blog', 'blogapi', 'book', 'color', 'comment', 'contact', 'drupal', 'filter', 'forum', 'help', 'legacy', 'locale', 'menu', 'node', 'path', 'ping', 'poll', 'profile', 'search', 'statistics', 'taxonomy', 'throttle', 'tracker', 'upload', 'user', 'watchdog', 'system');
+
+ if (in_array($module, $core_modules)) {
+ $info['icon'] = 'icon_core_block.png';
+ $info['category'] = array(t('Core blocks'), -5);
+ }
+ else {
+ $info['icon'] = 'icon_contrib_block.png';
+ $info['category'] = t('Contributed blocks');
+ }
+}
+
+// These are all on behalf of modules that don't implement panels but we that
+// we care about.
+function menu_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_block_menu.png';
+ $info['category'] = array(t('Menus'), -2);
+}
+
+function forum_panels_block_info($module, $delta, &$info) {
+ $info['category'] = t('Core blocks');
+ switch ($delta) {
+ case '0':
+ $info['icon'] = 'icon_core_activeforumtopics.png';
+ break;
+
+ case '1':
+ $info['icon'] = 'icon_core_newforumtopics.png';
+ break;
+
+ default:
+ // safety net
+ panels_default_block_info($module, $delta, $info);
+ }
+}
+
+function profile_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_authorinformation.png';
+ $info['category'] = t('Core blocks');
+}
+
+function book_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_booknavigation.png';
+ $info['category'] = t('Core blocks');
+}
+
+function blog_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_recentblogposts.png';
+ $info['category'] = t('Core blocks');
+}
+
+function poll_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_recentpoll.png';
+ $info['category'] = t('Core blocks');
+}
+
+function comment_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_recentcomments.png';
+ $info['category'] = t('Core blocks');
+}
+
+function search_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_searchform.png';
+ $info['category'] = t('Core blocks');
+}
+
+function node_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_syndicate.png';
+ $info['category'] = t('Core blocks');
+}
+
+function aggregator_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_syndicate.png';
+ $info['category'] = array(t('Syndicated Feeds'), -3);
+}
+
+function block_panels_block_info($module, $delta, &$info) {
+ $info['icon'] = 'icon_core_block_empty.png';
+ $info['category'] = array(t('Custom'), -10);
+}
+
+function user_panels_block_info($module, $delta, &$info) {
+ $info['category'] = t('Core blocks');
+ switch ($delta) {
+ case '0':
+ $info['icon'] = 'icon_core_userlogin.png';
+ break;
+
+ case '1':
+ $info['icon'] = 'icon_core_navigation.png';
+ $info['category'] = array(t('Menus'), -2);
+ break;
+
+ case '2':
+ $info['icon'] = 'icon_core_whosnew.png';
+ break;
+
+ case '2':
+ $info['icon'] = 'icon_core_whosonline.png';
+ break;
+
+ default:
+ // safety net
+ panels_default_block_info($module, $delta, $info);
+ }
+}
+
diff --git a/content_types/custom.inc b/content_types/custom.inc
index 37d863afc882408a3d615cffaeb08fcfad6a8f33..70f1006e1471bd0c61164c9de21d1e0ddc34cfb0 100644
--- a/content_types/custom.inc
+++ b/content_types/custom.inc
@@ -1,90 +1,114 @@
- 'panels_content_custom',
- 'admin' => 'panels_admin_custom',
- );
- return $items;
-}
-
-/**
- * Output function for the 'custom' content type. Outputs a custom
- * based on the module and delta supplied in the configuration.
- */
-function panels_content_custom($conf) {
- $title = filter_xss_admin($conf['title']);
- $body = check_markup($conf['body'], $conf['format'], FALSE);
- return theme('panels_content_custom', $title, $body);
-}
-
-function theme_panels_content_custom($title, $body) {
- $output = '';
- if ($title) {
- $output .= '
' . $title . '
';
- }
- $output .= $body;
- $output .= '';
-EOT;
- return $output;
-}
-/**
- * Callback to perform administrative functions on the content block
- */
-function panels_admin_custom($op, &$arg, $arg2 = NULL) {
- switch ($op) {
- case 'list':
- $conf = $arg;
- return 'Custom: ' . filter_xss_admin($conf['title']);
- case 'add button':
- $form['title'] = array(
- '#title' => t('Enter an optional title for custom content you define'),
- '#type' => 'textfield',
- '#maxlength' => 512,
- '#weight' => -10,
- );
- $form['submit'] = array(
- '#type' => 'button',
- '#value' => t('Add custom'),
- );
-
- $form['#prefix'] = '';
- $form['#suffix'] = '
';
- return $form;
- case 'add':
- if ($_POST['op'] != t('Add custom')) {
- return;
- }
- return $arg;
- case 'edit':
- $conf = &$arg;
- $form['title'] = array(
- '#type' => 'textfield',
- '#default_value' => $conf['title'],
- '#title' => t('Title'),
- '#description' => t('Title'),
- '#size' => 15,
- );
- $form['body'] = array(
- '#title' => t('Body'),
- '#type' => 'textarea',
- '#default_value' => $conf['body'],
- '#rows' => 10,
- '#cols' => 20,
- );
- $arg2[] = 'format';
- $form['format'] = filter_form($conf['format'], 1, $arg2);
-
- return $form;
- case 'validate':
- // This one has nothing to validate.
- $form = &$arg;
- return;
- case 'save':
- // For this one, the form values go directly into the config.
- $form = &$arg;
- return $form;
- }
-}
+ t('Custom'),
+ 'weight' => -10,
+ 'single' => TRUE,
+ 'content_types' => 'panels_admin_content_types_custom',
+ 'render callback' => 'panels_content_custom',
+ 'editor render callback' => 'panels_admin_content_custom',
+ 'add callback' => 'panels_admin_edit_custom',
+ 'edit callback' => 'panels_admin_edit_custom',
+ 'title callback' => 'panels_admin_title_custom',
+ 'no override title' => TRUE,
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'custom' content type. Outputs a custom
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_custom($conf) {
+ static $delta = 0;
+
+ $block = new stdClass();
+ $block->module = 'custom';
+ $block->delta = ++$delta;
+ $block->subject = filter_xss_admin($conf['title']);
+ $block->content = check_markup($conf['body'], $conf['format'], FALSE);
+
+ return $block;
+}
+
+/**
+ * Render callback for when the custom content is in the editor so that people
+ * can have a preview on the spot.
+ *
+ * @param panels_display $display
+ * @param stdClass $pane
+ * @return stdClass $block
+ */
+function panels_admin_content_custom($display, $pane) {
+ $block = new stdClass();
+ $block->title = filter_xss_admin($pane->configuration['title']);
+ // We don't want to render php output on preview here, because if something is
+ // wrong the whole display will be borked. So we check to see if the php
+ // evaluator filter is being used, and make a temporary change to the filter
+ // so that we get the printed php, not the eval'ed php.
+ $php_filter = FALSE;
+ foreach (filter_list_format($pane->configuration['format']) as $filter) {
+ if ($filter->name == 'PHP evaluator') { // TODO stupid way to check
+ $php_filter = TRUE;
+ }
+ }
+ // If a php filter is active, pass 1 to use core's most restrictive filter.
+ $block->content = check_markup($pane->configuration['body'], $php_filter ? 1 : $pane->configuration['format']);
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_custom() {
+ return array(
+ 'custom' => array(
+ 'title' => t('New custom content'),
+ 'icon' => 'icon_block_custom.png',
+ 'path' => panels_get_path('content_types/custom'),
+ 'description' => t('Create a completely custom piece of HTML content.'),
+ 'category' => array(t('Custom'), -10),
+ ),
+ );
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function panels_admin_edit_custom($id, $parents, $conf = NULL) {
+ if (empty($conf)) {
+ $conf = array('title' => '', 'body' => '', 'format' => FILTER_FORMAT_DEFAULT);
+ }
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['title'],
+ '#title' => t('Title'),
+ );
+ $form['body'] = array(
+ '#title' => t('Body'),
+ '#type' => 'textarea',
+ '#default_value' => $conf['body'],
+ );
+ $parents[] = 'format';
+ $form['format'] = filter_form($conf['format'], 1, $parents);
+
+ return $form;
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function panels_admin_title_custom($conf) {
+ $output = t('Custom');
+ if (!empty($conf['title'])) {
+ $output .= " (" . filter_xss_admin($conf['title']) . ")";
+ }
+ return $output;
+}
+
diff --git a/content_types/custom_php.inc b/content_types/custom_php.inc
new file mode 100644
index 0000000000000000000000000000000000000000..f667289a62c7fa4b3f3dfbb112d1449bda7627d5
--- /dev/null
+++ b/content_types/custom_php.inc
@@ -0,0 +1,125 @@
+ t('Custom PHP'),
+ 'weight' => -10,
+ 'single' => TRUE,
+ 'content_types' => 'panels_admin_content_types_custom_php',
+ 'render callback' => 'panels_content_custom_php',
+ 'editor render callback' => 'panels_admin_content_custom_php',
+ 'add callback' => 'panels_admin_edit_custom_php',
+ 'edit callback' => 'panels_admin_edit_custom_php',
+ 'title callback' => 'panels_admin_title_custom_php',
+ 'no override title' => TRUE,
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'custom_php' content type. Allows sufficiently
+ * privileged users to enter custom php code that is evaluated while
+ * $context is in scope. Use with caution!
+ */
+function panels_content_custom_php($conf, $panel_args, $context) {
+ static $delta = 0;
+ $block = new stdClass();
+
+ // eval() the php that has been entered by the user.
+ eval($conf['body']);
+ $block->delta = ++$delta;
+ // Put our default member values into a separate array.
+ $members = array(
+ 'module' => 'custom',
+ 'subject' => filter_xss_admin($conf['title']),
+ 'content' => '',
+ );
+ // Iterate through the array of defaults and assign put in
+ // any values that haven't already been set by the custom
+ // php code.
+ foreach ($members as $member => $value) {
+ if (empty($block->$member)) {
+ $block->$member = $value;
+ }
+ }
+
+ return $block;
+}
+
+/**
+ * Render callback for when the custom content is in the editor so that people
+ * can have a preview on the spot.
+ *
+ * @param panels_display $display
+ * @param stdClass $pane
+ * @return stdClass $block
+ */
+function panels_admin_content_custom_php($display, $pane) {
+ $block = new stdClass();
+ $block->title = filter_xss_admin($pane->configuration['title']);
+ // We don't want to render php output on preview here, because if something is
+ // wrong the whole display will be borked.
+ $block->content = check_markup($pane->configuration['body'], 1);
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_custom_php() {
+ if (filter_access(2)) {
+ return array(
+ 'custom_php' => array(
+ 'title' => t('Custom PHP content'),
+ 'icon' => 'icon_block_custom.png',
+ 'path' => panels_get_path('content_types/custom'),
+ 'description' => t('Custom PHP block with access to Panels context data. Use sparingly, and with caution!'),
+ 'category' => array(t('Custom'), -10),
+ ),
+ );
+ }
+}
+
+/**
+ * Returns an edit form for the custom_php type.
+ */
+function panels_admin_edit_custom_php($id, $parents, $conf = NULL) {
+ if (!is_array($conf)) {
+ $conf = array('title' => '', 'body' => '');
+ }
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['title'],
+ '#title' => t('Title'),
+ );
+ $body = t('Any content you want to have passed along to theme function for displaying MUST be stored in $block->content.') . "\n\n";
+ $body .= t('MAKE SURE YOU ERASE ALL OF THIS HELP TEXT. Only error-free php code should be saved in this field.') . "\n\n";
+ $body .= t('Do NOT use php tags.');
+
+ $form['body'] = array(
+ '#title' => t('Body'),
+ '#type' => 'textarea',
+ '#default_value' => isset($conf['body']) ? $conf['body'] : $body,
+ '#rows' => 10,
+ '#description' => t('The PHP code you enter here will be evaluated at render-time. Any context data present in the display will be available to this code in the $context variable. DO NOT include @php tags.', array('@php' => '')),
+ );
+
+ return $form;
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function panels_admin_title_custom_php($conf) {
+ $output = t('Custom');
+ if ($conf['title']) {
+ $output .= " (" . filter_xss_admin($conf['title']) . ")";
+ }
+ return $output;
+}
+
diff --git a/content_types/form.inc b/content_types/form.inc
new file mode 100644
index 0000000000000000000000000000000000000000..74f5f4831f65f04421d8d1cedc8e530b18ac6b24
--- /dev/null
+++ b/content_types/form.inc
@@ -0,0 +1,72 @@
+ t('Generic form'),
+ 'content_types' => 'panels_admin_content_types_form',
+ // only provides a single content type
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_form',
+ 'add callback' => 'panels_admin_edit_form',
+ 'edit callback' => 'panels_admin_edit_form',
+ 'title callback' => 'panels_admin_title_form',
+ 'render last' => TRUE,
+ );
+ return $items;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_form() {
+ return array(
+ 'node_type' => array(
+ 'title' => t('General form'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('The general parts of a form.'),
+ 'required context' => new panels_required_context(t('Form'), 'form'),
+ 'category' => array(t('Form'), -9),
+ ),
+ );
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_form($conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = 'form';
+
+ if (isset($context->form)) {
+ $block->subject = $context->form_title;
+ if (!empty($context->form_id)) {
+ // If this is a form, drupal_render it.
+ $block->content = drupal_render($context->form);
+ }
+ else {
+ // Otherwise just spit back what we were given. This is probably an
+ // error message or something.
+ $block->content = $context->form;
+ }
+ $block->delta = $context->form_id;
+ }
+ else {
+ $block->subject = t('Form');
+ $block->content = t('Form goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+function panels_admin_title_form($conf, $context) {
+ return t('"@s" base form', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/node.inc b/content_types/node.inc
index 1383ab7ac9ca39d33d0329951e9e7a33e6431215..90780914ed6a329bd43b78bd7180b575e3cbd2e9 100644
--- a/content_types/node.inc
+++ b/content_types/node.inc
@@ -1,96 +1,5 @@
- 'panels_content_node',
- 'admin' => 'panels_admin_node',
- );
- return $items;
-}
-
-/**
- * Output function for the 'node' content type. Outputs a node
- * based on the module and delta supplied in the configuration.
- */
-function panels_content_node($conf) {
- $node = node_load($conf['nid']);
- $output = node_view($node, $conf['teaser'], FALSE, $conf['links']);
- return $output;
-}
-
-/**
- * Callback to perform administrative functions on the content block
- */
-function panels_admin_node($op, &$arg, $arg2 = NULL) {
- switch ($op) {
- case 'list':
- $conf = $arg;
- $node = node_load($conf['nid']);
- return 'Node: ' . check_plain($node->title);
- case 'add button':
- $form['nid'] = array(
- '#title' => t('Enter the title or NID of a post'),
- '#type' => 'textfield',
- '#maxlength' => 512,
- '#autocomplete_path' => 'panels/node/autocomplete',
- '#weight' => -10,
- );
- $form['submit'] = array(
- '#type' => 'button',
- '#value' => t('Add post'),
- );
-
- $form['#prefix'] = '';
- $form['#suffix'] = '
';
- return $form;
- case 'add':
- if ($_POST['op'] != t('Add post')) {
- return;
- }
- $form = &$arg;
- if (is_numeric($form['nid'])) {
- $conf = array();
- $conf['nid'] = $form['nid'];
- }
- else {
- $conf = db_fetch_array(db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE LOWER(title) = LOWER('%s')"), $form['nid']));
- if (!$conf['nid']) {
- drupal_set_message(t('Unable to find "%s"', array('%s' => check_plain($form['nid']))));
- return;
- }
- }
- // default to just teaser
- $conf['teaser'] = TRUE;
- return $conf;
- case 'edit':
- $conf = &$arg;
- $form['nid'] = array(
- '#type' => 'hidden',
- '#default_value' => $conf['nid'],
- );
- $form['teaser'] = array(
- '#title' => t('Teaser'),
- '#type' => 'checkbox',
- '#default_value' => $conf['teaser'],
- '#description' => t('Check here to show only the node teaser'),
- );
- $form['links'] = array(
- '#type' => 'checkbox',
- '#default_value' => $conf['links'],
- '#title' => t('Display links'),
- '#description' => t('Check here to display the links with the post.')
- );
-
- return $form;
- case 'validate':
- // This one has nothing to validate.
- $form = &$arg;
- return;
- case 'save':
- // For this one, the form values go directly into the config.
- $form = &$arg;
- return $form;
- }
-}
+ t('Node type description'),
+ // only provides a single content type
+ 'single' => TRUE,
+ 'content_types' => 'panels_admin_content_types_node_attachments',
+ 'render callback' => 'panels_content_node_attachments',
+// 'add callback' => 'panels_admin_edit_node_attachments',
+// 'edit callback' => 'panels_admin_edit_node_attachments',
+ 'title callback' => 'panels_admin_title_node_attachments',
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_node_attachments($conf, $panel_args, $context) {
+ $node = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'attachments';
+
+ $block->subject = t('Attached files');
+ if ($node) {
+ $block->content = theme('upload_attachments', $node->files);
+ $block->delta = $node->nid;
+ }
+ else {
+ $block->content = t('Attached files go here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_node_attachments() {
+ return array(
+ 'node_type' => array(
+ 'title' => t('Attached files'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('A list of files attached to the node.'),
+ 'required context' => new panels_required_context(t('Node'), 'node'),
+ 'category' => array(t('Node context'), -9),
+ ),
+ );
+}
+
+function panels_admin_title_node_attachments($conf, $context) {
+ return t('"@s" attachments', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/node_book_nav.inc b/content_types/node_book_nav.inc
new file mode 100644
index 0000000000000000000000000000000000000000..669c05175c2503c1f1e26b7b13a6af7cc6b7781e
--- /dev/null
+++ b/content_types/node_book_nav.inc
@@ -0,0 +1,63 @@
+ t('Node book navigation'),
+ // only provides a single content type
+ 'single' => TRUE,
+ 'content_types' => 'panels_admin_content_types_node_book_nav',
+ 'render callback' => 'panels_content_node_book_nav',
+ 'title callback' => 'panels_admin_title_node_book_nav',
+ );
+ return $items;
+ }
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_node_book_nav($conf, $panel_args, $context) {
+ $node = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'book_nav';
+
+ $block->subject = t('Book navigation');
+ if ($node) {
+ $block->content = theme('book_navigation', $node);
+ $block->delta = $node->nid;
+ }
+ else {
+ $block->content = t('Book navigation goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_node_book_nav() {
+ return array(
+ 'node_type' => array(
+ 'title' => t('Book navigation'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('A list of files attached to the node.'),
+ 'required context' => new panels_required_context(t('Node'), 'node'),
+ 'category' => array(t('Node context'), -9),
+ ),
+ );
+}
+
+function panels_admin_title_node_book_nav($conf, $context) {
+ return t('"@s" book navigation', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/node_comment_form.inc b/content_types/node_comment_form.inc
new file mode 100644
index 0000000000000000000000000000000000000000..aa9fa10fc67a7263f8c9b3e56d4c968dba0a2dd3
--- /dev/null
+++ b/content_types/node_comment_form.inc
@@ -0,0 +1,79 @@
+ t('Node comment form'),
+ // only provides a single content type
+ 'single' => TRUE,
+ 'content_types' => 'panels_admin_content_types_node_comment_form',
+ 'render callback' => 'panels_content_node_comment_form',
+ 'add callback' => 'panels_admin_edit_node_comment_form',
+ 'edit callback' => 'panels_admin_edit_node_comment_form',
+ 'title callback' => 'panels_admin_title_node_comment_form',
+ );
+ return $items;
+ }
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_node_comment_form($conf, $panel_args, $context) {
+ $node = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'comments';
+ $block->delta = $node->nid;
+
+ $block->subject = t('Add comment');
+
+ if (empty($node)) {
+ $block->content = t('Comment form here.');
+ }
+ else {
+ if (user_access('post comments') && node_comment_mode($node->nid) == COMMENT_NODE_READ_WRITE) {
+ $block->content = drupal_get_form('panels_comment_form', array('nid' => $node->nid));
+ }
+ }
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_node_comment_form() {
+ return array(
+ 'comment_form' => array(
+ 'title' => t('Comment form'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('A form to add a new comment.'),
+ 'required context' => new panels_required_context(t('Node'), 'node'),
+ 'category' => array(t('Node context'), -9),
+ ),
+ );
+}
+
+function panels_admin_title_node_comment_form($conf, $context) {
+ return t('"@s" comment form', array('@s' => $context->identifier));
+}
+
+function panels_comment_form($edit) {
+ $form = comment_form($edit);
+ // force the form to come back to here.
+ $url = parse_url($_GET['q']);
+ $path = $url['path'];
+ $form['#action'] = url($path, NULL, 'new');
+ $form['#redirect'] = array($path, NULL, 'new');
+ $form['#validate']['comment_form_validate'] = array();
+ $form['#submit']['comment_form_submit'] = array();
+ return $form;
+}
+
diff --git a/content_types/node_comments.inc b/content_types/node_comments.inc
new file mode 100644
index 0000000000000000000000000000000000000000..614f24f5cadffc366b3d95f7dc3933dd6606a1b1
--- /dev/null
+++ b/content_types/node_comments.inc
@@ -0,0 +1,190 @@
+ t('Node comments'),
+ 'content_types' => 'panels_admin_content_types_node_comments',
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_node_comments',
+ 'add callback' => 'panels_admin_edit_node_comments',
+ 'edit callback' => 'panels_admin_edit_node_comments',
+ 'title callback' => 'panels_admin_title_node_comments',
+ );
+ }
+ return $items;
+}
+
+/**
+ * Output function for the 'node comments' content type. Outputs the comments
+ * that have been made on a given nid, as provided by the $context.
+ */
+function panels_content_node_comments($conf, $panel_args, $context) {
+ $node = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'comments';
+ $block->delta = $node->nid;
+
+ $block->subject = t('Comments');
+ if (empty($node)) {
+ $block->content = t('Node comments go here.');
+ }
+ else {
+ $block->content = panels_comment_render($node, $conf);
+ // Update the history table, stating that this user viewed this node.
+ node_tag_new($node->nid);
+ }
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_node_comments() {
+ return array(
+ 'comments' => array(
+ 'title' => t('Node comments'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('The comments of the referenced node.'),
+ 'required context' => new panels_required_context(t('Node'), 'node'),
+ 'category' => array(t('Node context'), -9),
+ ),
+ );
+}
+
+function panels_admin_edit_node_comments($id, $parents, $conf = array()) {
+ if (empty($conf)) {
+ $conf = array(
+ 'mode' => _comment_get_display_setting('mode'),
+ 'order' => _comment_get_display_setting('sort'),
+ 'comments_per_page' => _comment_get_display_setting('comments_per_page'),
+ );
+ }
+
+ $form['mode'] = array(
+ '#type' => 'select',
+ '#title' => t('Mode'),
+ '#default_value' => $conf['mode'],
+ '#options' => _comment_get_modes(),
+ '#weight' => 1,
+ );
+ $form['order'] = array(
+ '#type' => 'select',
+ '#title' => t('Sort'),
+ '#default_value' => $conf['order'],
+ '#options' => _comment_get_orders(),
+ '#weight' => 2,
+ );
+ foreach (_comment_per_page() as $i) {
+ $options[$i] = t('!a comments per page', array('!a' => $i));
+ }
+ $form['comments_per_page'] = array('#type' => 'select',
+ '#title' => t('Pager'),
+ '#default_value' => $conf['comments_per_page'],
+ '#options' => $options,
+ '#weight' => 3,
+ );
+ return $form;
+}
+
+function panels_admin_title_node_comments($conf, $context) {
+ return t('"@s" comments', array('@s' => $context->identifier));
+}
+
+/**
+ * This function is a somewhat stripped down version of comment_render
+ * that removes a bunch of cruft that we both don't need, and makes it
+ * difficult to modify this.
+ */
+function panels_comment_render($node, $conf) {
+ if (!user_access('access comments')) {
+ return;
+ }
+
+ $mode = $conf['mode'];
+ $order = $conf['order'];
+ $comments_per_page = $conf['comments_per_page'];
+
+ // Multiple comment view
+ $query_count = 'SELECT COUNT(*) FROM {comments} WHERE nid = %d';
+ $query = 'SELECT c.cid as cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS registered_name, u.picture, u.data, c.score, c.users, c.thread, c.status FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.nid = %d';
+
+ $query_args = array($node->nid);
+ if (!user_access('administer comments')) {
+ $query .= ' AND c.status = %d';
+ $query_count .= ' AND status = %d';
+ $query_args[] = COMMENT_PUBLISHED;
+ }
+
+ if ($order == COMMENT_ORDER_NEWEST_FIRST) {
+ if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
+ $query .= ' ORDER BY c.timestamp DESC';
+ }
+ else {
+ $query .= ' ORDER BY c.thread DESC';
+ }
+ }
+ else if ($order == COMMENT_ORDER_OLDEST_FIRST) {
+ if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
+ $query .= ' ORDER BY c.timestamp';
+ }
+ else {
+ $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))';
+ }
+ }
+
+ // Start a form, for use with comment control.
+ $result = pager_query($query, $comments_per_page, 0, $query_count, $query_args);
+
+ $divs = 0;
+ $last_depth = 0;
+ drupal_add_css(drupal_get_path('module', 'comment') .'/comment.css');
+ while ($comment = db_fetch_object($result)) {
+ $comment = drupal_unpack($comment);
+ $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+ $comment->depth = count(explode('.', $comment->thread)) - 1;
+
+ if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode == COMMENT_MODE_THREADED_EXPANDED) {
+ if ($comment->depth > $last_depth) {
+ $divs++;
+ $output .= '';
+ $last_depth++;
+ }
+ else {
+ while ($comment->depth < $last_depth) {
+ $divs--;
+ $output .= '
';
+ $last_depth--;
+ }
+ }
+ }
+
+ if ($mode == COMMENT_MODE_FLAT_COLLAPSED) {
+ $output .= theme('comment_flat_collapsed', $comment);
+ }
+ else if ($mode == COMMENT_MODE_FLAT_EXPANDED) {
+ $output .= theme('comment_flat_expanded', $comment);
+ }
+ else if ($mode == COMMENT_MODE_THREADED_COLLAPSED) {
+ $output .= theme('comment_thread_collapsed', $comment);
+ }
+ else if ($mode == COMMENT_MODE_THREADED_EXPANDED) {
+ $output .= theme('comment_thread_expanded', $comment);
+ }
+ }
+ for ($i = 0; $i < $divs; $i++) {
+ $output .= '';
+ }
+ $output .= theme('pager', NULL, $comments_per_page, 0);
+
+ return $output;
+}
+
diff --git a/content_types/node_content.inc b/content_types/node_content.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a9351d2b9ff621e459c14d1733fff7fc4c886d7f
--- /dev/null
+++ b/content_types/node_content.inc
@@ -0,0 +1,205 @@
+ t('Node content'),
+ 'weight' => -10,
+ // only provides a single content type
+ 'single' => TRUE,
+ 'content_types' => 'panels_admin_content_types_node_content',
+ 'render callback' => 'panels_content_node_content',
+ 'add callback' => 'panels_admin_edit_node_content',
+ 'edit callback' => 'panels_admin_edit_node_content',
+ 'title callback' => 'panels_admin_title_node_content',
+ );
+ return $items;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_node_content() {
+ return array(
+ 'content' => array(
+ 'title' => t('Node content'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('The content of the referenced node.'),
+ 'required context' => new panels_required_context(t('Node'), 'node'),
+ 'category' => array(t('Node context'), -9),
+ ),
+ );
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_node_content($conf, $panel_args, $context) {
+ if (!empty($context) && empty($context->data)) {
+ return;
+ }
+
+ $node = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'node';
+ $block->delta = $node->nid;
+
+ if (empty($node)) {
+ $block->delta = 'placeholder';
+ $block->subject = t('Node title.');
+ $block->content = t('Node content goes here.');
+ }
+ else {
+ if (!empty($conf['identifier'])) {
+ $node->panel_identifier = $conf['identifier'];
+ }
+
+ $block->subject = $node->title;
+
+ unset($node->title);
+ $block->content = panels_admin_node_content($node, $conf);
+ }
+
+ if (node_access('update', $node)) {
+ $block->admin_links['update'] = array(
+ 'title' => t('Edit node'),
+ 'alt' => t("Edit this node"),
+ 'href' => "node/$node->nid/edit",
+ 'query' => drupal_get_destination(),
+ );
+ }
+
+ if (!empty($conf['link']) && $node) {
+ $block->title_link = "node/$node->nid";
+ }
+
+ return $block;
+}
+
+function panels_admin_node_content($node, $conf) {
+ // Remove the delimiter (if any) that separates the teaser from the body.
+ $node->body = str_replace('', '', $node->body);
+
+ // The 'view' hook can be implemented to overwrite the default function
+ // to display nodes.
+ if (node_hook($node, 'view')) {
+ $node = node_invoke($node, 'view', $conf['teaser'], $conf['page']);
+ }
+ else {
+ $node = node_prepare($node, $conf['teaser']);
+ }
+
+ if (empty($conf['no_extras'])) {
+ // Allow modules to make their own additions to the node.
+ node_invoke_nodeapi($node, 'view', $conf['teaser'], $conf['page']);
+ }
+
+ if ($conf['links']) {
+ $node->links = module_invoke_all('link', 'node', $node, $conf['teaser']);
+
+ foreach (module_implements('link_alter') AS $module) {
+ $function = $module .'_link_alter';
+ $function($node, $node->links);
+ }
+ }
+
+ // Set the proper node part, then unset unused $node part so that a bad
+ // theme can not open a security hole.
+ $content = drupal_render($node->content);
+ if ($conf['teaser']) {
+ $node->teaser = $content;
+ unset($node->body);
+ }
+ else {
+ $node->body = $content;
+ unset($node->teaser);
+ }
+
+ // Allow modules to modify the fully-built node.
+ node_invoke_nodeapi($node, 'alter', $conf['teaser'], $conf['page']);
+
+ return theme('node', $node, $conf['teaser'], $conf['page']);
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function panels_admin_edit_node_content($id, $parents, $conf = array()) {
+ if ($conf == array()) {
+ $conf = array(
+ 'links' => TRUE,
+ 'page' => TRUE,
+ 'no_extras' => TRUE,
+ );
+ }
+
+ $form['aligner_start'] = array(
+ '#value' => '
',
+ );
+ $form['override_title'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['override_title'],
+ '#title' => t('Override title'),
+ '#id' => 'override-title-checkbox',
+ );
+ $form['override_title_text'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['override_title_text'],
+ '#size' => 35,
+ '#id' => 'override-title-textfield',
+ );
+ $form['aligner_stop'] = array(
+ '#value' => '
',
+ );
+ $form['link'] = array(
+ '#title' => t('Link title to node'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['link'],
+ '#description' => t('Check here to make the title link to the node.'),
+ );
+ $form['teaser'] = array(
+ '#title' => t('Teaser'),
+ '#type' => 'checkbox',
+ '#default_value' => $conf['teaser'],
+ '#description' => t('Check here to show only the node teaser.'),
+ );
+ $form['page'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['page'],
+ '#title' => t('Node page'),
+ '#description' => t('Check here if the node is being displayed on a page by itself.'),
+ );
+ $form['links'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['links'],
+ '#title' => t('Display links'),
+ '#description' => t('Check here to display the links with the post.'),
+ );
+
+ $form['no_extras'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => $conf['no_extras'],
+ '#title' => t('No extras'),
+ '#description' => t('Check here to disable additions that modules might make to the node, such as file attachments and CCK fields; this should just display the basic teaser or body.'),
+ );
+
+ $form['identifier'] = array(
+ '#type' => 'textfield',
+ '#default_value' => $conf['identifier'],
+ '#title' => t('Identifier'),
+ '#description' => t('Whatever is placed here will appear in $node->panel_identifier to make it easier to theme a node or part of a node as necessary.'),
+ );
+
+ return $form;
+}
+
+function panels_admin_title_node_content($conf, $context) {
+ return t('"@s" content', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/node_form.inc b/content_types/node_form.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a7df3f6eebe553f2cd1660f53efe107364c64a2d
--- /dev/null
+++ b/content_types/node_form.inc
@@ -0,0 +1,303 @@
+ t('Node form'),
+ 'content_types' => 'panels_admin_content_types_node_form',
+ 'render callback' => 'panels_content_node_form',
+ 'add callback' => 'panels_admin_edit_node_form',
+ 'edit callback' => 'panels_admin_edit_node_form',
+ 'title callback' => 'panels_admin_title_node_form',
+ );
+ return $items;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_node_form() {
+ $content = array(
+ 'publishing' => array(
+ 'title' => t('Node form publishing options'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Publishing options on the Node form.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ ),
+ 'author' => array(
+ 'title' => t('Node form author information'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Author information on the Node form.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ ),
+ 'input_format' => array(
+ 'title' => t('Node form input format'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Input format for the body field on a node.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ ),
+ 'log' => array(
+ 'title' => t('Node form log message'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Log message for the node.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ ),
+ );
+ if (module_exists('comment')) {
+ $content['comment'] = array(
+ 'title' => t('Node form comment settings'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Comment settings on the Node form.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ );
+ }
+ if (module_exists('menu')) {
+ $content['menu'] = array(
+ 'title' => t('Node form menu settings'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('menu settings on the Node form.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ );
+ }
+ if (module_exists('path')) {
+ $content['url_path'] = array(
+ 'title' => t('URL Path settings'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Publishing options on the Node form.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ );
+ }
+ if (module_exists('upload')) {
+ $content['attachments'] = array(
+ 'title' => t('Node form file attachments'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('File attachments on the Node form.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ );
+ }
+ if (module_exists('taxonomy')) {
+ $content['taxonomy'] = array(
+ 'title' => t('Node form categories'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Taxonomy categories for the node.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ );
+ }
+ if (module_exists('book')) {
+ $content['book'] = array(
+ 'title' => t('Node form book options'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Book options for the node.'),
+ 'required context' => new panels_required_context(t('Form'), 'node_form'),
+ 'category' => array(t('Form'), -9),
+ );
+ }
+ return $content;
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_node_form($conf, $panel_args, &$context) {
+ $block = new stdClass();
+ $block->module = 'node-form';
+
+ switch ($conf['id']) {
+ case 'publishing':
+ $block->subject = t('Publishing options');
+ $block->delta = 'publishing-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && $context->form['options']['#type'] == 'fieldset') {
+ // remove the fieldset
+ unset($context->form['options']['#type']);
+ $block->content = drupal_render($context->form['options']);
+ }
+ }
+ else {
+ $block->content = t('Publishing options.');
+ }
+ break;
+
+ case 'comment':
+ $block->subject = t('Comment options');
+ $block->delta = 'comment-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && !empty($context->form['comment_settings']['#access'])) {
+ // remove the fieldset
+ unset($context->form['comment_settings']['#type']);
+ $block->content = drupal_render($context->form['comment_settings']);
+ }
+ }
+ else {
+ $block->content = t('Comment options.');
+ }
+ break;
+
+ case 'author':
+ $block->subject = $block->content = t('Authoring information');
+ $block->delta = 'author-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && !empty($context->form['author']['#access'])) {
+ // remove the fieldset
+ unset($context->form['author']['#type']);
+ $context->form['author']['name']['#size'] /= 2;
+ $context->form['author']['date']['#size'] /= 2;
+ $block->content = drupal_render($context->form['author']);
+ }
+ }
+ else {
+ $block->content = t('Authoring information.');
+ }
+ break;
+
+ case 'menu':
+ $block->subject = t('Menu options');
+ $block->delta = 'menu-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && !empty($context->form['menu']['#access'])) {
+ // remove the fieldset
+ unset($context->form['menu']['#type']);
+ $context->form['menu']['title']['#size'] /= 2;
+ $context->form['menu']['description']['#size'] /= 2;
+ $block->content = drupal_render($context->form['menu']);
+ }
+ }
+ else {
+ $block->content = t('Menu options.');
+ }
+ break;
+
+ case 'url_path':
+ $block->subject = t('URL path options');
+ $block->delta = 'url-path-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && !empty($context->form['path']['#access'])) {
+ // remove the fieldset
+ unset($context->form['path']['#type']);
+ $context->form['path']['path']['#size'] /= 2;
+ $block->content = drupal_render($context->form['path']);
+ }
+ }
+ else {
+ $block->content = t('URL Path options.');
+ }
+ break;
+
+ case 'attachments':
+ $block->subject = t('Attach files');
+ $block->delta = 'url-path-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && !empty($context->form['attachments']['#access'])) {
+ // remove the fieldset
+ unset($context->form['attachments']['#type']);
+ $block->content = drupal_render($context->form['attachments']);
+ }
+ }
+ else {
+ $block->content = t('Attach files.');
+ }
+ break;
+
+ case 'taxonomy':
+ $block->subject = t('Categories');
+ $block->delta = 'url-path-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && !empty($context->form['taxonomy'])) {
+ // remove the fieldset
+ unset($context->form['taxonomy']['#type']);
+ $block->content = drupal_render($context->form['taxonomy']);
+ }
+ }
+ else {
+ $block->content = t('Categories.');
+ }
+ break;
+
+ case 'book':
+ $block->subject = t('Book options');
+ $block->delta = 'book-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id)) {
+ $block->content = '';
+ if ($context->form['parent']['#type'] != 'value') {
+ $block->content .= drupal_render($context->form['parent']);
+ }
+ if ($context->form['weight']['#type'] != 'value') {
+ $block->content .= drupal_render($context->form['weight']);
+ }
+ }
+ }
+ else {
+ $block->content = t('Book options.');
+ }
+ break;
+
+ case 'input_format':
+ $block->subject = t('Input format');
+ $block->delta = 'format-options';
+
+ if (isset($context->form)) {
+ if (!empty($context->form->form_id) && !empty($context->form['body_filter']['format'])) {
+ // remove the fieldset
+ unset($context->form['body_filter']['format']['#type']);
+ $block->content = drupal_render($context->form['body_filter']['format']);
+ }
+ }
+ else {
+ $block->content = t('Input format.');
+ }
+ break;
+ }
+
+ return $block;
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function panels_admin_edit_node_form($id, $parents, $conf = array()) {
+ $form['id'] = array(
+ '#type' => 'value',
+ '#value' => $id,
+ );
+
+ return $form;
+}
+
+function panels_admin_title_node_form($conf, $context) {
+ $choices = panels_admin_content_types_node_form();
+ return t('"@s" @type', array('@s' => $context->identifier, '@type' => drupal_strtolower($choices[$conf['id']]['title'])));
+}
+
diff --git a/content_types/node_type_desc.inc b/content_types/node_type_desc.inc
new file mode 100644
index 0000000000000000000000000000000000000000..c831c2742f52b9458ecf3d2646a8e8f94340c724
--- /dev/null
+++ b/content_types/node_type_desc.inc
@@ -0,0 +1,65 @@
+ t('Node type description'),
+ 'content_types' => 'panels_admin_content_types_node_type_desc',
+ // only provides a single content type
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_node_type_desc',
+ 'add callback' => 'panels_admin_edit_node_type_desc',
+ 'edit callback' => 'panels_admin_edit_node_type_desc',
+ 'title callback' => 'panels_admin_title_node_type_desc',
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_node_type_desc($conf, $panel_args, $context) {
+ $node = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'node_type';
+
+ if ($node) {
+ $type = node_get_types('type', $node);
+ $block->subject = $type->name;
+ $block->content = filter_xss_admin($type->description);
+ $block->delta = $node->type;
+ }
+ else {
+ $block->subject = t('Node type description');
+ $block->content = t('Node type description goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_node_type_desc() {
+ return array(
+ 'node_type' => array(
+ 'title' => t('Node type description'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Node type description.'),
+ 'required context' => new panels_required_context(t('Node'), 'node'),
+ 'category' => array(t('Node context'), -9),
+ ),
+ );
+}
+
+function panels_admin_title_node_type_desc($conf, $context) {
+ return t('"@s" type description', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/profile_fields.inc b/content_types/profile_fields.inc
new file mode 100644
index 0000000000000000000000000000000000000000..5b975663863af4bca33eb6b7dee311a7ae9ed1c3
--- /dev/null
+++ b/content_types/profile_fields.inc
@@ -0,0 +1,153 @@
+ t('Profile Fields'),
+ 'content_types' => 'panels_profile_fields_content_type',
+ // only provides a single content type
+ 'single' => TRUE,
+ 'render callback' => 'panels_profile_fields_content',
+ 'add callback' => 'panels_profile_fields_configure',
+ 'edit callback' => 'panels_profile_fields_configure',
+ 'title callback' => 'panels_profile_fields_configure_title',
+ );
+ }
+ return $items;
+}
+
+/**
+ * 'Render' callback for the 'profile fields' content type.
+ */
+function panels_profile_fields_content($conf, $panel_args, $context) {
+ $account = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'profile fields';
+
+ if ($account) {
+ // Get the category from the options
+ $category = str_replace("_", " ", $conf['category']);
+
+ // Set the subject to the name of the category
+ $block->subject = $category;
+
+ // Put all the fields in the category into an array
+ $profile = profile_view_profile($account);
+ if (is_array($profile[$category])) {
+ foreach ($profile[$category] as $field) {
+ $vars[$field['class']]['title'] = $field['title'];
+ $vars[$field['class']]['value'] = $field['value'];
+ }
+ }
+
+ if (sizeof($vars) == 0) {
+ // Output the given empty text
+ $output = $conf['empty'];
+ }
+ else {
+ // Call the theme function with the field vars
+ $output = theme('profile_fields_pane', $category, $vars);
+ }
+
+ $block->content = $output;
+ $block->delta = $account->uid;
+ }
+ else {
+ $block->subject = $conf['category'];
+ $block->content = t('Profile content goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+/**
+ * Theme the profile fields retrieved in panels_profile_fields_content
+ */
+function theme_profile_fields_pane($category, $vars) {
+ if (is_array($vars)) {
+ foreach ($vars as $class => $field) {
+ $output .= '
';
+ $output .= '- ' . $field['title'] . '
';
+ $output .= '- ' . $field['value'] . '
';
+ $output .= '
';
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_profile_fields_content_type() {
+ return array(
+ 'description' => array(
+ 'title' => t('Profile Category'),
+ 'icon' => 'icon_user.png',
+ 'path' => panels_get_path('content_types/user'),
+ 'description' => t('Profile category contents.'),
+ 'required context' => new panels_required_context(t('User'), 'user'),
+ 'category' => array(t('User context'), -9),
+ ),
+ );
+}
+
+/**
+ * Helper function : build the list of categories for the 'edit' form.
+ */
+function _panels_profile_fields_options() {
+ $cat_list = array();
+
+ $categories = profile_categories();
+ foreach ($categories as $key => $value) {
+ $cat_list[str_replace(" ", "_", $value['name'])] = $value['title'];
+ }
+
+ return $cat_list;
+}
+
+/**
+ * 'Edit' callback for the 'profile fields' content type.
+ */
+function panels_profile_fields_configure($id, $parents, $conf = array()) {
+ // Apply defaults
+ if (empty($conf)) {
+ $conf = array('title' => '', 'category' => '', 'empty' => '');
+ }
+
+ $form['category'] = array(
+ '#type' => 'radios',
+ '#title' => t('Which category'),
+ '#options' => _panels_profile_fields_options(),
+ '#default_value' => $conf['category'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ $form['empty'] = array(
+ '#type' => 'textarea',
+ '#title' => 'Empty text',
+ '#description' => t('Text to display if category has no data. Note that title will not display unless overridden.'),
+ '#rows' => 5,
+ '#default_value' => $conf['empty'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ return $form;
+}
+
+/**
+ * 'Title' callback for the 'profile fields' content type.
+ */
+function panels_profile_fields_configure_title($conf, $context) {
+ return t('"@s" profile fields', array('@s' => $conf['category']));
+}
+
diff --git a/content_types/term_description.inc b/content_types/term_description.inc
new file mode 100644
index 0000000000000000000000000000000000000000..70ea7660c3e7d02f7f00e78638a901dbc19429b9
--- /dev/null
+++ b/content_types/term_description.inc
@@ -0,0 +1,72 @@
+ t('Term description'),
+ 'content_types' => 'panels_admin_content_types_term_description',
+ // only provides a single content type
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_term_description',
+ 'add callback' => 'panels_admin_edit_term_description',
+ 'edit callback' => 'panels_admin_edit_term_description',
+ 'title callback' => 'panels_admin_title_term_description',
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_term_description($conf, $panel_args, $context) {
+ $term = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'node_type';
+
+ $block->subject = $term->name;
+ if ($term) {
+ $block->content = _filter_autop(filter_xss_admin($term->description));
+ $block->delta = $term->tid;
+
+ if (user_access('administer taxonomy')) {
+ $block->admin_links['update'] = array(
+ 'title' => t('Edit term'),
+ 'alt' => t("Edit this term"),
+ 'href' => "admin/content/taxonomy/edit/term/$term->tid",
+ 'query' => drupal_get_destination(),
+ );
+ }
+ }
+ else {
+ $block->content = t('Term description goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_term_description() {
+ return array(
+ 'description' => array(
+ 'title' => t('Term description'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Term description.'),
+ 'required context' => new panels_required_context(t('Term'), 'term'),
+ 'category' => array(t('Term context'), -9),
+ ),
+ );
+}
+
+function panels_admin_title_term_description($conf, $context) {
+ return t('"@s" term description', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/term_list.inc b/content_types/term_list.inc
new file mode 100644
index 0000000000000000000000000000000000000000..4f1d3b2816b540d1722d303e8621acb0e6d0b4ae
--- /dev/null
+++ b/content_types/term_list.inc
@@ -0,0 +1,134 @@
+ t('List of related terms'),
+ 'content_types' => 'panels_admin_content_types_term_list',
+ // only provides a single content type
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_term_list',
+ 'add callback' => 'panels_admin_edit_term_list',
+ 'edit callback' => 'panels_admin_edit_term_list',
+ 'title callback' => 'panels_admin_title_term_list',
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_term_list($conf, $panel_args, $context) {
+ $term = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'term-list';
+
+ $options = panels_admin_term_list_options();
+ if ($term) {
+ $block->subject = $options[$conf['type']];
+ $block->delta = $conf['type'];
+ switch ($conf['type']) {
+ case 'related':
+ $terms = taxonomy_get_related($term->tid);
+ break;
+
+ case 'child':
+ default:
+ $terms = taxonomy_get_children($term->tid);
+ break;
+
+ case 'top':
+ $terms = taxonomy_get_children(0, $term->vid);
+ break;
+
+ case 'sibling':
+ $parent = db_result(db_query("SELECT parent FROM {term_hierarchy} WHERE tid = %d", $term->tid));
+ $terms = taxonomy_get_children($parent, $term->vid);
+ // Remove the term that started this.
+ unset($terms[$term->tid]);
+ break;
+
+ case 'synonyms':
+ $terms = taxonomy_get_synonyms($term->tid);
+ break;
+ }
+ if ($terms) {
+ foreach ($terms as $related) {
+ $items[$related->tid] = l($related->name, taxonomy_term_path($related), array('rel' => 'tag', 'title' => strip_tags($related->description)));
+ }
+
+ $block->content = theme('item_list', $items, NULL, $conf['list_type']);
+ }
+ }
+ else {
+ $block->content = t('Term description goes here.');
+ $block->delta = 'unknown';
+ }
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_term_list() {
+ return array(
+ 'description' => array(
+ 'title' => t('List of related terms'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('Terms related to an existing term; may be child, siblings or top level.'),
+ 'required context' => new panels_required_context(t('Term'), 'term'),
+ 'category' => array(t('Term context'), -9),
+ ),
+ );
+}
+
+function panels_admin_term_list_options() {
+ return array(
+ 'child' => t('Child terms'),
+ 'related' => t('Related terms'),
+ 'sibling' => t('Sibling terms'),
+ 'top' => t('Top level terms'),
+ 'synonyms' => t('Term synonyms'),
+ );
+}
+
+/**
+ * Returns an edit form for the custom type.
+ */
+function panels_admin_edit_term_list($id, $parents, $conf = array()) {
+ // Apply defaults
+ if (empty($conf)) {
+ $conf = array('title' => '', 'type' => 'child', 'list_type' => 'ul');
+ }
+
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Which terms'),
+ '#options' => panels_admin_term_list_options(),
+ '#default_value' => $conf['type'],
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+
+ $form['list_type'] = array(
+ '#type' => 'select',
+ '#title' => t('List type'),
+ '#options' => array('ul' => t('Unordered'), 'ol' => t('Ordered')),
+ '#default_value' => $conf['list_type'],
+ );
+
+ return $form;
+}
+
+function panels_admin_title_term_list($conf, $context) {
+ $options = panels_admin_term_list_options();
+ return t('"@s" @type', array('@s' => $context->identifier, '@type' => drupal_strtolower($options[$conf['type']])));
+}
+
diff --git a/content_types/user_picture.inc b/content_types/user_picture.inc
new file mode 100644
index 0000000000000000000000000000000000000000..03f447c6930502e334569a152692e6755edca6a0
--- /dev/null
+++ b/content_types/user_picture.inc
@@ -0,0 +1,60 @@
+ t('User picture'),
+ 'content_types' => 'panels_admin_content_types_user_picture',
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_user_picture',
+ 'title callback' => 'panels_admin_title_user_picture',
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_user_picture($conf, $panel_args, $context) {
+ $account = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'term-list';
+
+ if ($account === FALSE || ($account->access == 0 && !user_access('administer users'))) {
+ return drupal_not_found();
+ }
+
+ $block->title = check_plain($account->name);
+ $block->content = theme('user_picture', $account);
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_user_picture() {
+ return array(
+ 'description' => array(
+ 'title' => t('User picture'),
+ 'icon' => 'icon_user.png',
+ 'path' => panels_get_path('content_types/user'),
+ 'description' => t('The picture of a user.'),
+ 'required context' => new panels_required_context(t('User'), 'user'),
+ 'category' => array(t('User context'), -9),
+ ),
+ );
+}
+
+/**
+ * Display the administrative title for a panel pane in the drag & drop UI
+ */
+function panels_admin_title_user_picture($conf, $context) {
+ return t('"@s" user picture', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/user_profile.inc b/content_types/user_profile.inc
new file mode 100644
index 0000000000000000000000000000000000000000..8e8e8356fa7cd286efb8cbde2fb187cd7d4b81b2
--- /dev/null
+++ b/content_types/user_profile.inc
@@ -0,0 +1,80 @@
+ t('User profile'),
+ 'content_types' => 'panels_admin_content_types_user_profile',
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_user_profile',
+ 'title callback' => 'panels_admin_title_user_profile',
+ );
+ return $items;
+}
+
+/**
+ * Output function for the 'node' content type. Outputs a node
+ * based on the module and delta supplied in the configuration.
+ */
+function panels_content_user_profile($conf, $panel_args, $context) {
+ $account = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $block = new stdClass();
+ $block->module = 'term-list';
+
+ if ($account === FALSE || ($account->access == 0 && !user_access('administer users'))) {
+ return drupal_not_found();
+ }
+ // Retrieve and merge all profile fields:
+ $fields = array();
+ foreach (module_list() as $module) {
+ if ($data = module_invoke($module, 'user', 'view', '', $account)) {
+ foreach ($data as $category => $items) {
+ foreach ($items as $key => $item) {
+ $item['class'] = "$module-". $item['class'];
+ $fields[$category][$key] = $item;
+ }
+ }
+ }
+ }
+
+ // Let modules change the returned fields - useful for personal privacy
+ // controls. Since modules communicate changes by reference, we cannot use
+ // module_invoke_all().
+ foreach (module_implements('profile_alter') as $module) {
+ $function = $module .'_profile_alter';
+ $function($account, $fields);
+ }
+
+ $block->title = check_plain($account->name);
+ $block->content = theme('user_profile', $account, $fields);
+
+ return $block;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_user_profile() {
+ return array(
+ 'description' => array(
+ 'title' => t('User profile'),
+ 'icon' => 'icon_user.png',
+ 'path' => panels_get_path('content_types/user'),
+ 'description' => t('The profile of a user.'),
+ 'required context' => new panels_required_context(t('User'), 'user'),
+ 'category' => array(t('User context'), -9),
+ ),
+ );
+}
+
+/**
+ * Display the administrative title for a panel pane in the drag & drop UI
+ */
+function panels_admin_title_user_profile($conf, $context) {
+ return t('"@s" user profile', array('@s' => $context->identifier));
+}
+
diff --git a/content_types/views.inc b/content_types/views.inc
index e893d6c356a087c6bce53a8c4302f3a193d4df78..32c5665159f52c03a7e7dfd63a3a380ab2767b2e 100644
--- a/content_types/views.inc
+++ b/content_types/views.inc
@@ -1,154 +1,5 @@
- 'panels_content_views',
- 'admin' => 'panels_admin_views',
- );
- return $items;
- }
-
- /**
- * Output function for the 'views' content type. Outputs a views
- * based on the module and delta supplied in the configuration.
- */
- function panels_content_views($conf) {
- $view = views_get_view($conf['view']);
- if ($view) {
- $arguments = explode('/', $_GET['q']);
- $args = $conf['args'];
-
- foreach ($arguments as $id => $arg) {
- $args = str_replace("%$id", $arg, $args);
- }
-
- $args = preg_replace('/\%\d/', '', $args);
- $args = $args ? explode('/', $args) : array();
-
- if ($conf['url']) {
- $view->url = $conf['url'];
- }
-
- $output = views_build_view($conf['type'], $view, $args, intval($conf['pager_id']), intval($conf['nodes_per_page']));
- }
- return $output;
- }
-
- /**
- * Callback to perform administrative functions on the content views
- */
- function panels_admin_views($op, &$arg, $arg2 = NULL) {
- switch ($op) {
- case 'list':
- $conf = $arg;
- $view = views_get_view($conf['view']);
- return '
Views: ' . $view->name . ' (' . $view->description . ')';
- case 'add button':
- $result = db_query("SELECT name, description FROM {view_view}");
- while ($view = db_fetch_object($result)) {
- $views[$view->name] = $view->name . ': ' . $view->description;
- }
-
- $default_views = _views_get_default_views();
- $views_status = variable_get('views_defaults', array());
- foreach ($default_views as $view) {
- if (!$views[$view->name] &&
- ($views_status[$view->name] == 'enabled' || (!$views_status[$view->name] && !$view->disabled))) {
- $views[$view->name] = check_plain($view->name . ': ' . $view->description);
- }
- }
-
- ksort($views);
-
- $form['view'] = array(
- '#type' => 'select',
- '#options' => $views,
- '#title' => t('Choose a view from the views module'),
- );
- $form['submit'] = array(
- '#type' => 'button',
- '#value' => t('Add view'),
- );
-
- $form['#prefix'] = '
';
- $form['#suffix'] = '
';
- return $form;
- case 'add':
- if ($_POST['op'] != t('Add view')) {
- return;
- }
- $conf = $arg;
- $view = views_get_view($conf['view']);
- if ($view->page) {
- $conf['type'] = 'page';
- $conf['nodes_per_page'] = $view->nodes_per_page;
- }
- else {
- $conf['type'] = 'block';
- $conf['nodes_per_page'] = $view->nodes_per_block;
- }
- $conf['pager_id'] = 0;
- return $conf;
- case 'edit':
- $conf = &$arg;
- $form['view'] = array(
- '#type' => 'hidden',
- '#default_value' => $conf['view'],
- );
- $form['type'] = array(
- '#type' => 'select',
- '#default_value' => $conf['type'],
- '#title' => t('View type'),
- '#description' => t('Select which type of the view to display.'),
- '#options' => array('page' => t('Page'), 'block' => t('Block'), 'embed' => t('Embedded')),
- );
- $form['pager_id'] = array(
- '#type' => 'textfield',
- '#default_value' => $conf['pager_id'],
- '#title' => t('Pager ID'),
- '#size' => 4,
- '#description' => t('Select the numeric pager ID to use, or 0 to not have use paging. Select "1" if you aren\'t sure what this means'),
- );
-
- $form['nodes_per_page'] = array(
- '#type' => 'textfield',
- '#default_value' => $conf['nodes_per_page'],
- '#title' => t('Posts to Display'),
- '#size' => 4,
- '#description' => t('Select the number of posts to display, or 0 to display all results.'),
- );
-
- $form['args'] = array(
- '#type' => 'textfield',
- '#default_value' => $conf['args'],
- '#title' => t('View arguments'),
- '#size' => 12,
- '#description' => t('Arguments to send to the view as if they were part of the URL in the form of arg1/arg2/arg3. You can use %0, %1, %2, etc, to use arguments from the actual URL. For example, if your panel URL is foo/bar, and someone hits foo/bar/5 use %2 to get the 5.'),
- );
-
- $form['url'] = array(
- '#type' => 'textfield',
- '#default_value' => $conf['url'],
- '#title' => t('Override URL'),
- '#size' => 12,
- '#description' => t('If this is set, override the View URL; this can sometimes be useful to set to the panel URL'),
- );
-
- return $form;
- case 'validate':
- // This one has nothing to validate.
- $form_values = &$arg;
- $form = $arg2;
- return;
- case 'save':
- // For this one, the form values go directly into the config.
- $form = &$arg;
- return $form;
- }
- }
-
-}
\ No newline at end of file
+ t('Term description'),
+ 'content_types' => 'panels_admin_content_types_vocabulary_terms',
+ 'single' => TRUE,
+ 'render callback' => 'panels_content_vocabulary_terms',
+ 'add callback' => 'panels_admin_edit_vocabulary_terms',
+ 'edit callback' => 'panels_admin_edit_vocabulary_terms',
+ 'title callback' => 'panels_admin_title_vocabulary_terms',
+ );
+ return $items;
+ }
+}
+
+/**
+ * Output function for the 'vocabulary terms' content type. Outputs a
+ * list of terms for the input vocabulary.
+ */
+function panels_content_vocabulary_terms($conf, $panel_args, $context) {
+ $vocab = isset($context->data) ? drupal_clone($context->data) : NULL;
+ $max_depth = (!empty($conf['max_depth']) ? (int)$conf['max_depth'] : NULL);
+ if ($conf['tree'] == FALSE) {
+ $terms = taxonomy_get_tree($vocab->vid, 0, -1, $max_depth);
+ $items = array();
+ foreach ($terms as $term) {
+ $items[] = l(check_plain($term->name), 'taxonomy/term/'. $term->tid);
+ }
+ $output = theme('item_list', $items);
+ }
+ else {
+ $output = theme('item_list', _panels_content_vocabulary_terms($vocab->vid, $max_depth));
+ }
+
+ $block = new stdClass();
+ $block->module = 'node_type';
+ $block->subject = $vocab->name;
+ $block->content = $output;
+ $block->delta = $vocab->tid;
+
+ return $block;
+}
+
+function _panels_content_vocabulary_terms($vid, $max_depth, $depth = -1, $tid = 0) {
+ $depth++;
+ if ($max_depth != NULL && $depth == $max_depth) {
+ return array();
+ }
+ $return = array();
+ $query = db_query('SELECT t.name, t.tid FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d AND h.parent = %d ORDER BY t.weight ASC, t.name ASC', $vid, $tid);
+ while ($result = db_fetch_object($query)) {
+ $return[] = array(
+ 'data' => l($result->name, 'taxonomy/term/'. $result->tid),
+ 'children' => _panels_content_vocabulary_terms($vid, $max_depth, $depth, $result->tid),
+ );
+ }
+ return $return;
+}
+
+/**
+ * Return all content types available.
+ */
+function panels_admin_content_types_vocabulary_terms() {
+ return array(
+ 'description' => array(
+ 'title' => t('Vocabulary terms'),
+ 'icon' => 'icon_node.png',
+ 'path' => panels_get_path('content_types/node'),
+ 'description' => t('All the terms in a vocabulary.'),
+ 'required context' => new panels_required_context(t('Vocabulary'), 'vocabulary'),
+ 'category' => array(t('Vocabulary context'), -9),
+ ),
+ );
+}
+
+function panels_admin_title_vocabulary_terms($conf, $context) {
+ return t('"@s" terms', array('@s' => $context->identifier));
+}
+
+function panels_admin_edit_vocabulary_terms($id, $parents, $conf = array()) {
+ // Apply defaults
+ if (empty($conf)) {
+ $conf = array('max_depth' => 0, 'tree' => 1);
+ }
+
+ $form['max_depth'] = array(
+ '#type' => 'select',
+ '#title' => t('Maximum depth'),
+ '#options' => array_merge(array(t('unlimited')), range(1, 9)),
+ '#default_value' => $conf['max_depth'],
+ '#description' => t('Define the maximum depth of terms being displayed.'),
+ );
+
+ $form['tree'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display as tree'),
+ '#default_value' => $conf['tree'],
+ '#description' => t('If checked, the terms are displayed in a tree, otherwise in a flat list.'),
+ );
+
+ return $form;
+}
+
diff --git a/images/blank.gif b/images/blank.gif
index c31552c825663a028bc9b69dad6cd3b920052f38..75b945d2553848b8b6f41fe5e24599c0687b8472 100644
Binary files a/images/blank.gif and b/images/blank.gif differ
diff --git a/images/close.gif b/images/close.gif
new file mode 100644
index 0000000000000000000000000000000000000000..46891b0c0d4871eb17b8e71690f42c6eae76bd7c
Binary files /dev/null and b/images/close.gif differ
diff --git a/images/delete.png b/images/delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..f790555163b1abcad7e350282a47ef8f77d8ec4a
Binary files /dev/null and b/images/delete.png differ
diff --git a/images/go-bottom.png b/images/go-bottom.png
deleted file mode 100644
index 741b6191fe7aa90d935d44be384913602734659a..0000000000000000000000000000000000000000
Binary files a/images/go-bottom.png and /dev/null differ
diff --git a/images/go-down.png b/images/go-down.png
index 30acf6863f2ed514ad7aefe7d923ad9c74342e8c..c2def1a3124f8aa309bdcd7a0b3aef6c191b4616 100644
Binary files a/images/go-down.png and b/images/go-down.png differ
diff --git a/images/go-right.png b/images/go-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..dd6058c8652f419bbef9f6c3259c080bcf6ab364
Binary files /dev/null and b/images/go-right.png differ
diff --git a/images/go-top.png b/images/go-top.png
deleted file mode 100644
index e6f9532e120166efa448eaa722895ffa459f194b..0000000000000000000000000000000000000000
Binary files a/images/go-top.png and /dev/null differ
diff --git a/images/go-up.png b/images/go-up.png
index 17b374f91b2ab67d1549972674885beed2d9d8f9..a52c7dcb66d2be7169372a149058892482eccc6c 100644
Binary files a/images/go-up.png and b/images/go-up.png differ
diff --git a/images/icon-addcontent.png b/images/icon-addcontent.png
new file mode 100644
index 0000000000000000000000000000000000000000..788d01ff2574383caf22d8e553949fe3c4b95468
Binary files /dev/null and b/images/icon-addcontent.png differ
diff --git a/images/icon-cache.png b/images/icon-cache.png
new file mode 100644
index 0000000000000000000000000000000000000000..3e6f46b3fb5586a57d939939536f484a67dcda19
Binary files /dev/null and b/images/icon-cache.png differ
diff --git a/images/icon-configure.png b/images/icon-configure.png
new file mode 100644
index 0000000000000000000000000000000000000000..e23d67cc04b84880d0437e23ffcba837d2dc4121
Binary files /dev/null and b/images/icon-configure.png differ
diff --git a/images/icon-delete.png b/images/icon-delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f0cf695b0cac487efecfd7eae66e7492a2ba306
Binary files /dev/null and b/images/icon-delete.png differ
diff --git a/images/icon-draggable.png b/images/icon-draggable.png
new file mode 100644
index 0000000000000000000000000000000000000000..dba8b67cdf37714c933bc9514cfdf5e35df89fb3
Binary files /dev/null and b/images/icon-draggable.png differ
diff --git a/images/icon-hidepane.png b/images/icon-hidepane.png
new file mode 100644
index 0000000000000000000000000000000000000000..851698056e69dd3058fe1cc0456064ebb49909be
Binary files /dev/null and b/images/icon-hidepane.png differ
diff --git a/images/icon-showpane.png b/images/icon-showpane.png
new file mode 100644
index 0000000000000000000000000000000000000000..7549dd94d646ef74cb640beeeb911bfec9895bf5
Binary files /dev/null and b/images/icon-showpane.png differ
diff --git a/images/no-icon.png b/images/no-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..30584e6f2540ad49dac98060d1a7f8a5af1b27b7
Binary files /dev/null and b/images/no-icon.png differ
diff --git a/images/portlet-collapsed.png b/images/portlet-collapsed.png
new file mode 100644
index 0000000000000000000000000000000000000000..95a214a6e6d17fee2f098804997f3826ffc9d4ca
Binary files /dev/null and b/images/portlet-collapsed.png differ
diff --git a/images/portlet-expanded.png b/images/portlet-expanded.png
new file mode 100644
index 0000000000000000000000000000000000000000..46f39ecb351cff65243fa9a614a69d039e1302a5
Binary files /dev/null and b/images/portlet-expanded.png differ
diff --git a/images/throbber.gif b/images/throbber.gif
new file mode 100644
index 0000000000000000000000000000000000000000..8a084b8447d506e9b655ad52405cbf7a73034550
Binary files /dev/null and b/images/throbber.gif differ
diff --git a/layouts/flexible.css b/layouts/flexible.css
new file mode 100644
index 0000000000000000000000000000000000000000..fe12ca5a25d6f28c0bc2b7fb0ed91ff3196af31a
--- /dev/null
+++ b/layouts/flexible.css
@@ -0,0 +1,58 @@
+/* $Id$ */
+
+.panel-flexible {
+/* overflow: hidden; */
+}
+
+.panel-flexible {
+ width: 99.9%;
+}
+
+.panel-flexible .panel-col {
+ float: left;
+ padding: 0;
+ margin: 1px 0 .5em 0;
+}
+
+.panel-flexible .panel-row {
+ padding: 0;
+ margin: 0;
+ padding-bottom: .5em;
+ width: 99.9%;
+}
+
+.panel-flexible .panel-col-first .inside {
+ padding-right: .5em;
+}
+
+.panel-flexible .panel-col-inside .inside {
+ padding-right: .5em;
+ padding-left: .5em;
+}
+
+.panel-flexible .panel-col-last .inside {
+ padding-left: .5em;
+}
+
+#panels-edit-display .panel-pane,
+#panels-edit-display .helperclass {
+ margin: .5em;
+}
+
+.panel-flexible .panel-separator {
+ margin: 0 0 1em 0;
+}
+
+/* sidebar css */
+div.panel-flexible div.panel-flexible-sidebars div.panel-sidebar {
+ float: left;
+ position: relative;
+}
+
+* html div.panel-sidebar-left div.inside {
+ background-color: transparent;
+}
+
+div.panel-flexible div.panel-flexible-sidebars div.panel-sidebar-middle {
+ width: 100%;
+}
diff --git a/layouts/flexible.inc b/layouts/flexible.inc
new file mode 100644
index 0000000000000000000000000000000000000000..e840a7db96ff0c6836e07918dc5988d1547b7d69
--- /dev/null
+++ b/layouts/flexible.inc
@@ -0,0 +1,366 @@
+ t('Flexible'),
+ 'icon' => 'layouts/flexible.png',
+ 'theme' => 'panels_flexible',
+ 'theme arguments' => array('id', 'content', 'settings'),
+ 'css' => 'layouts/flexible.css',
+ 'settings form' => 'panels_flexible_settings_form',
+ 'settings submit' => 'panels_flexible_settings_submit',
+ 'panels function' => 'panels_flexible_panels',
+ );
+
+ return $items;
+}
+
+function panels_flexible_default_panels() {
+ return array(
+ 'percent_width' => 100,
+ 'rows' => 3,
+ 'width_type' => '%',
+ 'row_1' => array(
+ 'columns' => 1,
+ 'width_1' => 100,
+ 'names' => array(t('Top')),
+ ),
+ 'row_2' => array(
+ 'columns' => 3,
+ 'width_1' => 25,
+ 'width_2' => 50,
+ 'width_3' => 25,
+ 'names' => array(t('Left'), t('Middle'), t('Right')),
+ ),
+ // row 3
+ 'row_3' => array(
+ 'columns' => 1,
+ 'width_1' => 100,
+ 'names' => array(t('Bottom')),
+ ),
+ 'sidebars' => array(
+ 'left' => FALSE,
+ 'left_width' => 200,
+ 'right' => FALSE,
+ 'right_width' => 200,
+ 'width_type' => 'px',
+ ),
+ );
+}
+
+function panels_flexible_settings_form($display, $layout, $settings) {
+ if (empty($settings)) {
+ // default for a new flexible layout
+ $settings = panels_flexible_default_panels();
+ }
+
+ // Special check for updating:
+ if (empty($settings['width_type'])) {
+ $settings['width_type'] = '%';
+ $settings['percent_width'] = 100;
+ $settings['sidebars']['left'] = FALSE;
+ $settings['sidebars']['left_width'] = 200;
+ $settings['sidebars']['right'] = FALSE;
+ $settings['sidebars']['right_width'] = 200;
+ $settings['sidebars']['width_type'] = 'px';
+ }
+
+ $form['instructions'] = array(
+ '#value' => t('
Here you may determine the number of rows and columns your layout may have. Each row can have its own number of columns, and each column can have its width set independently. When changing the number of rows or columns, click Save to update the form so you can set the widths for new cells properly.
Note: Removing cells which contain panes will cause those panes to be disappear. Please move any content you wish to keep.
'),
+ );
+
+ $form['width_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Width unit type'),
+ '#options' => array('%' => t('% (percentage)'), 'px' => t('px (pixels)'), 'em' => t('em (current)')),
+ '#description' => t('The width unit type this layout can have: %, px or em. When using percentage, your layout will be fluid; when using px or em, your layout will be fixed.'),
+ '#default_value' => $settings['width_type'],
+ );
+
+ $form['percent_width'] = array(
+ '#type' => 'textfield',
+ '#size' => 2,
+ '#width' => 10,
+ '#title' => t('Total width'),
+ '#description' => t('If using the percentage width, choose the total width that this layout must add up to; if you are having problems with your flexible layout having columns fall off, try lowering this number and adjusting the width of individual columns to match.'),
+ '#default_value' => $settings['percent_width'],
+ );
+
+ $form['rows'] = array(
+ '#type' => 'textfield',
+ '#size' => 2,
+ '#width' => 10,
+ '#title' => t('Rows'),
+ '#description' => t('The number of rows this layout can have.'),
+ '#default_value' => $settings['rows'],
+ );
+
+ for ($row = 1; $row <= intval($settings['rows']); $row++) {
+ $form["row_$row"] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Row @d', array('@d' => $row)),
+ );
+ $form["row_$row"]["columns"] = array(
+ '#prefix' => '
',
+ '#suffix' => '
',
+ '#type' => 'textfield',
+ '#size' => 2,
+ '#width' => 10,
+ '#title' => t('Columns'),
+// '#description' => t('The number of columns in row @d.', array('@d' => $row)),
+ '#default_value' => $settings["row_$row"]["columns"],
+ );
+ for ($col = 1; $col <= intval($settings["row_$row"]["columns"]); $col++) {
+ $form["row_$row"]["width_$row_$col"] = array(
+ '#prefix' => '
',
+ '#suffix' => '
',
+ '#type' => 'textfield',
+ '#size' => 2,
+ '#width' => 10,
+ '#title' => t('Width @d', array('@d' => $col)),
+ '#default_value' => $settings["row_$row"]["width_$col"],
+ );
+ }
+ if (is_array($settings["row_$row"]["names"])) {
+ $names = implode(', ', $settings["row_$row"]["names"]);
+ }
+ else {
+ $names = '';
+ }
+ $form["row_$row"]['names'] = array(
+ '#prefix' => '
',
+ '#suffix' => '
',
+ '#type' => 'textfield',
+ '#title' => t('Column titles, separated by commas'),
+ '#default_value' => $names,
+ );
+ }
+
+ $form['sidebars'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Sidebars'),
+ );
+
+ $form['sidebars']['left_aligner_start'] = array(
+ '#value' => '
',
+ '#tree' => TRUE,
+ );
+ $form['sidebars']['left'] = array(
+ '#type' => 'checkbox',
+ '#id' => 'sidebar-left-checkbox',
+ '#title' => t('Sidebar left'),
+ '#default_value' => $settings['sidebars']['left'],
+ );
+ $form['sidebars']['left_width'] = array(
+ '#type' => 'textfield',
+ '#id' => 'sidebar-left-width',
+ '#size' => 2,
+ '#width' => 10,
+ '#default_value' => $settings['sidebars']['left_width'],
+ );
+ $form['sidebars']['right'] = array(
+ '#type' => 'checkbox',
+ '#id' => 'sidebar-right-checkbox',
+ '#title' => t('Sidebar right'),
+ '#default_value' => $settings['sidebars']['right'],
+ );
+
+ $form['sidebars']['right_width'] = array(
+ '#type' => 'textfield',
+ '#id' => 'sidebar-right-width',
+ '#size' => 2,
+ '#width' => 10,
+ '#default_value' => $settings['sidebars']['right_width'],
+ );
+ $form['sidebars']['left_aligner_stop'] = array(
+ '#value' => '
',
+ );
+ $form['sidebars']['left_title_markup'] = array(
+ '#prefix' => '
',
+ '#suffix' => '
',
+ '#value' => t('If a sidebar is selected, enter the width of the sidebar.'),
+ );
+
+ $form['sidebars']['width_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Width unit type'),
+ '#options' => array('%' => t('% (percentage)'), 'px' => t('px (pixels)'), 'em' => t('em (current)')),
+ '#description' => t('The width unit type activated sidebars will have: %, px or em. When using percentage, your sidebars will be fluid; when using px or em, your sidebars will be fixed.'),
+ '#default_value' => $settings['sidebars']['width_type'],
+ );
+
+ $js_settings = array('panels' => array('checkboxes' => array(
+ '#sidebar-left-checkbox' => array('#sidebar-left-width'),
+ '#sidebar-right-checkbox' => array('#sidebar-right-width'),
+ )));
+ drupal_add_js(panels_get_path('js/checkboxes.js'));
+ drupal_add_js($js_settings, 'setting');
+ return $form;
+}
+
+function panels_flexible_settings_validate($values, $form, $display, $layout, $settings) {
+ if ($values['rows'] < 1) {
+ form_error($form['rows'], t('Rows must be a positive integer.'));
+ return;
+ }
+
+ // Validate that percentages add up to the stated maximum.
+ if ($settings['width_type'] == '%') {
+ for ($row = 1; $row <= intval($values['rows']); $row++) {
+ // This takes into account whether or even had a previous setting here.
+ if ($settings['rows'] >= $row) {
+ if ($values["row_$row"]['columns'] < 1) {
+ form_error($form["row_$row"]['columns'], t('Columns must be a positive integer.'));
+ return;
+ }
+ $total = 0;
+ for ($col = 1; $col <= intval($values["row_$row"]["columns"]); $col++) {
+ $total += $values["row_$row"]["width_$col"];
+ }
+ if ($total != $settings['percent_width']) {
+ form_error($form["row_$row"]['columns'], t('Column widths must add up to 100.'));
+ }
+ }
+ }
+ }
+}
+
+function panels_flexible_settings_submit(&$values, $display, $layout, $settings) {
+ for ($row = 1; $row <= $values['rows']; $row++) {
+ if ($row > $settings['rows'] && empty($values["row_$row"]['columns'])) {
+ $values["row_$row"]['columns'] = 1;
+ $values["row_$row"]['width_1'] = 100;
+ }
+ if (!empty($values["row_$row"]['names'])) {
+ $names = explode(',', $values["row_$row"]['names']);
+ foreach ($names as $nid => $name) {
+ $names[$nid] = trim($name);
+ }
+ $values["row_$row"]['names'] = $names;
+ }
+ }
+}
+
+/**
+ * Define the actual list of columns and rows for this flexible panel.
+ */
+function panels_flexible_panels($display, $settings) {
+ $panels = array();
+ if (empty($settings)) {
+ $settings = panels_flexible_default_panels();
+ }
+
+ if (!empty($settings['sidebars']['left'])) {
+ $panels['sidebar_left'] = t('Left sidebar');
+ }
+
+ if (!empty($settings['sidebars']['right'])) {
+ $panels['sidebar_right'] = t('Right sidebar');
+ }
+
+ for ($row = 1; $row <= intval($settings['rows']); $row++) {
+ for ($col = 1; $col <= intval($settings["row_$row"]['columns']); $col++) {
+ if (!empty($settings["row_$row"]['names'][$col - 1])) {
+ $panels["row_${row}_$col"] = $settings["row_$row"]['names'][$col - 1];
+ }
+ else {
+ $panels["row_${row}_$col"] = t("Row @row, Column @col", array('@row' => $row, '@col' => $col));
+ }
+ }
+ }
+ return $panels;
+}
+
+/**
+ * This function uses heredoc notation to make it easier to convert
+ * to a template.
+ */
+function theme_panels_flexible($id, $content, $settings) {
+ if (empty($settings)) {
+ $settings = panels_flexible_default_panels();
+ }
+
+ // Special check for updating.
+ if (empty($settings['width_type'])) {
+ $settings['width_type'] = '%';
+ $settings['percent_width'] = 100;
+ }
+
+ if ($id) {
+ $idstr = " id='$id'";
+ $idcss = "#$id";
+ }
+ else {
+ $idcss = "div.panel-flexible";
+ }
+
+ $css = '';
+ $output = '';
+
+ for ($row = 1; $row <= intval($settings['rows']); $row++) {
+ $output .= "
\n";
+ for ($col = 1; $col <= intval($settings["row_$row"]["columns"]); $col++) {
+ // We do a width reduction formula to help IE out a little bit. If width is 100%, we take 1%
+ // off the total; by dividing by the # of columns, that gets us the reduction overall.
+ $reduce = 0;
+ if ($settings['width_type'] == '%' && $settings['percent_width'] == 100) {
+ $reduce = 1 / $settings["row_$row"]["columns"];
+ }
+ if ($col == 1) {
+ if (intval($settings["row_$row"]["columns"]) == 1) {
+ $class = 'panel-col-only';
+ }
+ else {
+ $class = 'panel-col-first';
+ }
+ }
+ elseif ($col == intval($settings["row_$row"]["columns"])) {
+ $class = 'panel-col-last';
+ }
+ else {
+ $class = 'panel-col-inside';
+ }
+ $output .= "
\n";
+ $output .= "
" . $content["row_${row}_$col"] . "
\n";
+ $output .= "
\n"; // panel-col-$col
+ $css .= "$idcss div.panel-row-$row div.panel-col-$col { width: " . ((intval($settings["row_$row"]["width_$col"])) - $reduce) . $settings["width_type"] ."; }\n";
+ }
+ $output .= "
\n"; // panel-row-$row
+ }
+
+ // Add our potential sidebars
+ if (!empty($settings['sidebars']['left']) || !empty($settings['sidebars']['right'])) {
+ // provide a wrapper if we have a sidebar
+ $output = "\n";
+ if ($settings['sidebars']['width_type'] == '%') {
+ $css .= "$idcss div.panel-flexible-sidebars div.panel-sidebar-middle { width: " . (intval($settings['percent_width']) - intval($settings['sidebars']['left_width']) - intval($settings['sidebars']['right_width'])) . "; }\n";
+ }
+ }
+
+ if (!empty($settings['sidebars']['left'])) {
+ $size = intval($settings['sidebars']['left_width']) . $settings['sidebars']['width_type'];
+ $output = "\n" . $output;
+ $css .= "$idcss div.panel-flexible-sidebars div.panel-sidebar-left { width: $size; margin-left: -$size; }\n";
+ $css .= "$idcss div.panel-flexible-sidebars { padding-left: $size; }\n";
+ // IE hack
+ $css .= "* html $idcss div.panel-flexible-sidebars div.panel-sidebar-left { left: $size; }\n";
+ }
+
+ if (!empty($settings['sidebars']['right'])) {
+ $size = intval($settings['sidebars']['right_width']) . $settings['sidebars']['width_type'];
+ $output .= "\n";
+ $css .= "$idcss div.panel-flexible-sidebars div.panel-sidebar-right { width: $size; margin-right: -$size; }\n";
+ $css .= "$idcss div.panel-flexible-sidebars { padding-right: $size; }\n";
+ }
+
+ // Wrap the whole thing up nice and snug
+ $output = "
\n\n
\n";
+ drupal_set_html_head("\n");
+ return $output;
+}
+
diff --git a/layouts/flexible.png b/layouts/flexible.png
new file mode 100644
index 0000000000000000000000000000000000000000..14b4779a0b4f7d3516092e6790310aa559c96fe9
Binary files /dev/null and b/layouts/flexible.png differ
diff --git a/layouts/onecol.css b/layouts/onecol.css
new file mode 100644
index 0000000000000000000000000000000000000000..c5a45e794ce978f3a8228e94b052818476858884
--- /dev/null
+++ b/layouts/onecol.css
@@ -0,0 +1,22 @@
+/* $Id$ */
+
+.panel-1col {
+/* overflow: hidden; */
+}
+
+.panel-2col .panel-col-first .inside {
+ margin: 0;
+}
+
+
+.panel-1col .panel-col {
+}
+
+#panels-edit-display .panel-pane,
+#panels-edit-display .helperclass {
+ margin: .5em;
+}
+
+.panel-2col .panel-separator {
+ margin: 0 0 1em 0;
+}
diff --git a/layouts/onecol.inc b/layouts/onecol.inc
new file mode 100644
index 0000000000000000000000000000000000000000..0e226702e8eefa9a2bfda967d301202f7d031324
--- /dev/null
+++ b/layouts/onecol.inc
@@ -0,0 +1,40 @@
+ 'panels',
+ 'title' => t('Single column'),
+ 'icon' => 'layouts/onecol.png',
+ 'theme' => 'panels_onecol',
+ 'theme arguments' => array('id', 'content'),
+ 'css' => 'layouts/onecol.css',
+ 'panels' => array('middle' => t('Middle column')),
+ );
+
+ return $items;
+}
+
+/**
+ * This function uses heredoc notation to make it easier to convert
+ * to a template.
+ */
+function theme_panels_onecol($id, $content) {
+ if ($id) {
+ $idstr = " id='$id'";
+ }
+
+ $output = <<
+
+
+EOT;
+ return $output;
+}
+
diff --git a/layouts/onecol.png b/layouts/onecol.png
new file mode 100644
index 0000000000000000000000000000000000000000..176ed69e7f59d6705471a00705b143131cdc03a4
Binary files /dev/null and b/layouts/onecol.png differ
diff --git a/layouts/twocol_bricks.css b/layouts/twocol_bricks.css
new file mode 100644
index 0000000000000000000000000000000000000000..cc29e86e873ea903c1cd1ef9825fe039dc443f8f
--- /dev/null
+++ b/layouts/twocol_bricks.css
@@ -0,0 +1,47 @@
+/* $Id$ */
+
+.panel-2col-bricks {
+/* overflow: hidden; */
+ margin-top: 0;
+ padding-top: 0;
+}
+
+.panel-2col-bricks .panel-col-top,
+.panel-2col-bricks .panel-col-middle,
+.panel-2col-bricks .panel-col-bottom {
+ width: 99.9%;
+ clear: both;
+}
+
+.panel-2col-bricks .panel-col-top .inside,
+.panel-2col-bricks .panel-col-middle .inside {
+ margin-bottom: .5em;
+}
+
+.panel-2col-bricks .panel-col-first {
+ float: left;
+ width: 50%;
+}
+* html .panel-2col-bricks .panel-col-first {
+ width: 49.9%;
+}
+
+.panel-2col-bricks .panel-col-first .inside {
+ margin: 0 .5em .5em 0;
+}
+
+.panel-2col-bricks .panel-col-last {
+ float: left;
+ width: 50%;
+}
+* html .panel-2col-bricks .panel-col-last {
+ width: 49.9%;
+}
+
+.panel-2col-bricks .panel-col-last .inside {
+ margin: 0 0 .5em .5em;
+}
+
+.panel-2col-bricks .panel-separator {
+ margin: 0 0 1em 0;
+}
diff --git a/layouts/twocol_bricks.inc b/layouts/twocol_bricks.inc
new file mode 100644
index 0000000000000000000000000000000000000000..412892662f33aa07d1413a6a5b314336e0e77ed4
--- /dev/null
+++ b/layouts/twocol_bricks.inc
@@ -0,0 +1,77 @@
+ 'panels',
+ 'title' => t('Two column bricks'),
+ 'icon' => 'layouts/twocol_bricks.png',
+ 'theme' => 'panels_twocol_bricks',
+ 'theme arguments' => array('id', 'content'),
+ 'css' => 'layouts/twocol_bricks.css',
+ 'panels' => array(
+ 'top' => t('Top'),
+ 'left_above' => t('Left above'),
+ 'right_above' => t('Right above'),
+ 'middle' => t('Middle'),
+ 'left_below' => t('Left below'),
+ 'right_below' => t('Right below'),
+ 'bottom' => t('Bottom'),
+ ),
+ );
+ return $items;
+}
+
+/**
+ * This function uses heredoc notation to make it easier to convert
+ * to a template.
+ */
+function theme_panels_twocol_bricks($id, $content) {
+ if ($id) {
+ $idstr = " id='$id'";
+ }
+
+ $output = <<