initial commit of starter install drupal 7

This commit is contained in:
Bachir Soussi Chiadmi
2015-02-21 17:21:40 +01:00
commit eeba51cea6
6113 changed files with 1249218 additions and 0 deletions
@@ -0,0 +1,232 @@
<?php
/**
* @file
*
* Class for handling background processes.
*/
/**
* BackgroundProcess class.
*/
class BackgroundProcess {
public $handle;
public $connection;
public $service_host;
public $service_group;
public $uid;
public static function load($process) {
$new = new BackgroundProcess($process->handle);
@$new->callback = $process->callback;
@$new->args = $process->args;
@$new->uid = $process->uid;
@$new->token = $process->token;
@$new->service_host = $process->service_host;
@$new->service_group = $process->service_group;
@$new->exec_status = $process->exec_status;
@$new->start_stamp = $process->start_stamp;
@$new->status = $process->exec_status;
@$new->start = $process->start_stamp;
return $new;
}
/**
* Constructor.
*
* @param type $handle
* Handle to use. Optional; leave out for auto-handle.
*/
public function __construct($handle = NULL) {
$this->handle = $handle ? $handle : background_process_generate_handle('auto');
$this->token = background_process_generate_handle('token');
$this->service_group = variable_get('background_process_default_service_group', 'default');
}
public function lock($status = BACKGROUND_PROCESS_STATUS_LOCKED) {
// Preliminary select to avoid unnecessary write-attempt
if (background_process_get_process($this->handle)) {
// watchdog('bg_process', 'Will not attempt to lock handle %handle, already exists', array('%handle' => $this->handle), WATCHDOG_NOTICE);
return FALSE;
}
// "Lock" handle
$this->start_stamp = $this->start = microtime(TRUE);
if (!background_process_lock_process($this->handle, $status)) {
// If this happens, we might have a race condition or an md5 clash
watchdog('bg_process', 'Could not lock handle %handle', array('%handle' => $this->handle), WATCHDOG_ERROR);
return FALSE;
}
$this->exec_status = $this->status = BACKGROUND_PROCESS_STATUS_LOCKED;
$this->sendMessage('locked');
return TRUE;
}
/**
* Start background process
*
* Calls the service handler through http passing function arguments as serialized data
* Be aware that the callback will run in a new request
*
* @global string $base_url
* Base URL for this Drupal request
*
* @param $callback
* Function to call.
* @param $args
* Array containg arguments to pass on to the callback.
* @return mixed
* TRUE on success, NULL on failure, FALSE on handle locked.
*/
public function start($callback, $args = array()) {
if (!$this->lock()) {
return FALSE;
}
return $this->execute($callback, $args);
}
public function queue($callback, $args = array()) {
if (!$this->lock(BACKGROUND_PROCESS_STATUS_QUEUED)) {
return FALSE;
}
if (!background_process_set_process($this->handle, $callback, $this->uid, $args, $this->token)) {
// Could not update process
return NULL;
}
module_invoke_all('background_process_pre_execute', $this->handle, $callback, $args, $this->token);
// Initialize progress stats
$old_db = db_set_active('background_process');
progress_remove_progress($this->handle);
db_set_active($old_db);
$queues = variable_get('background_process_queues', array());
$queue_name = isset($queues[$callback]) ? 'bgp:' . $queues[$callback] : 'background_process';
$queue = DrupalQueue::get($queue_name);
$queue->createItem(array(rawurlencode($this->handle), rawurlencode($this->token)));
_background_process_ensure_cleanup($this->handle, TRUE);
}
public function determineServiceHost() {
// Validate explicitly selected service host
$service_hosts = background_process_get_service_hosts();
if ($this->service_host && empty($service_hosts[$this->service_host])) {
$this->service_host = variable_get('background_process_default_service_host', 'default');
if (empty($service_hosts[$this->service_host])) {
$this->service_host = NULL;
}
}
// Find service group if a service host is not explicitly specified.
if (!$this->service_host) {
if (!$this->service_group) {
$this->service_group = variable_get('background_process_default_service_group', 'default');
}
if ($this->service_group) {
$service_groups = variable_get('background_process_service_groups', array());
if (isset($service_groups[$this->service_group])) {
$service_group = $service_groups[$this->service_group];
// Default method if none is provided
$service_group += array(
'method' => 'background_process_service_group_round_robin'
);
if (is_callable($service_group['method'])) {
$this->service_host = call_user_func($service_group['method'], $service_group);
// Revalidate service host
if ($this->service_host && empty($service_hosts[$this->service_host])) {
$this->service_host = NULL;
}
}
}
}
}
// Fallback service host
if (!$this->service_host || empty($service_hosts[$this->service_host])) {
$this->service_host = variable_get('background_process_default_service_host', 'default');
if (empty($service_hosts[$this->service_host])) {
$this->service_host = 'default';
}
}
return $this->service_host;
}
public function execute($callback, $args = array()) {
if (!background_process_set_process($this->handle, $callback, $this->uid, $args, $this->token)) {
// Could not update process
return NULL;
}
module_invoke_all('background_process_pre_execute', $this->handle, $callback, $args, $this->token);
// Initialize progress stats
$old_db = db_set_active('background_process');
progress_remove_progress($this->handle);
db_set_active($old_db);
$this->connection = FALSE;
$this->determineServiceHost();
return $this->dispatch();
}
function dispatch() {
$this->sendMessage('dispatch');
$handle = rawurlencode($this->handle);
$token = rawurlencode($this->token);
list($url, $headers) = background_process_build_request('bgp-start/' . $handle . '/' . $token, $this->service_host);
background_process_set_service_host($this->handle, $this->service_host);
$options = array('method' => 'POST', 'headers' => $headers);
$result = background_process_http_request($url, $options);
if (empty($result->error)) {
$this->connection = $result->fp;
_background_process_ensure_cleanup($this->handle, TRUE);
return TRUE;
}
else {
background_process_remove_process($this->handle);
watchdog('bg_process', 'Could not call service %handle for callback %callback: %error', array('%handle' => $this->handle, '%callback' => $callback, '%error' => $result->error), WATCHDOG_ERROR);
// Throw exception here instead?
return NULL;
}
return FALSE;
}
function sendMessage($action) {
if (module_exists('nodejs')) {
if (!isset($this->progress_object)) {
if ($progress = progress_get_progress($this->handle)) {
$this->progress_object = $progress;
$this->progress = $progress->progress;
$this->progress_message = $progress->message;
}
else {
$this->progress = 0;
$this->progress_message = '';
}
}
$object = clone $this;
$message = (object) array(
'channel' => 'background_process',
'data' => (object) array(
'action' => $action,
'background_process' => $object,
'timestamp' => microtime(TRUE),
),
'callback' => 'nodejsBackgroundProcess',
);
drupal_alter('background_process_message', $message);
nodejs_send_content_channel_message($message);
}
}
}
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,14 @@
name = Background Batch
description = Adds background processing to Drupals batch API
core = 7.x
php = 5.0
dependencies[] = background_process
dependencies[] = progress
; Information added by drupal.org packaging script on 2013-01-06
version = "7.x-1.14"
core = "7.x"
project = "background_process"
datestamp = "1357473962"
@@ -0,0 +1,15 @@
<?php
/**
* @file
* This is the installation file for the Background Batch submodule
*/
/**
* Implements hook_uninstall().
*/
function background_batch_uninstall() {
// Removing used variables.
variable_del('background_batch_delay');
variable_del('background_batch_process_lifespan');
variable_del('background_batch_show_eta');
}
@@ -0,0 +1,377 @@
<?php
/**
* @file
* This module adds background processing to Drupals batch API
*
* @todo Add option to stop a running batch job.
*/
/**
* Default value for delay (in microseconds).
*/
define('BACKGROUND_BATCH_DELAY', 1000000);
/**
* Default value for process lifespan (in miliseconds).
*/
define('BACKGROUND_BATCH_PROCESS_LIFESPAN', 10000);
/**
* Default value wether ETA information should be shown.
*/
define('BACKGROUND_BATCH_PROCESS_ETA', TRUE);
/**
* Implements hook_menu().
*/
function background_batch_menu() {
$items = array();
$items['admin/config/system/batch/settings'] = array(
'type' => MENU_DEFAULT_LOCAL_TASK,
'title' => 'Settings',
'weight' => 1,
);
$items['admin/config/system/batch'] = array(
'title' => 'Batch',
'description' => 'Administer batch jobs',
'page callback' => 'drupal_get_form',
'page arguments' => array('background_batch_settings_form'),
'access arguments' => array('administer site'),
'file' => 'background_batch.pages.inc',
);
$items['admin/config/system/batch/overview'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Overview',
'description' => 'Batch job overview',
'page callback' => 'background_batch_overview_page',
'access arguments' => array('administer site'),
'file' => 'background_batch.pages.inc',
'weight' => 3,
);
return $items;
}
/**
* Implements hook_menu_alter().
*/
function background_batch_menu_alter(&$items) {
$items['batch'] = array(
'page callback' => 'background_batch_page',
'access callback' => TRUE,
'theme callback' => '_system_batch_theme',
'type' => MENU_CALLBACK,
'file' => 'background_batch.pages.inc',
'module' => 'background_batch',
);
}
/**
* Implements hook_batch_alter().
* Steal the operation and hook into context data.
*/
function background_batch_batch_alter(&$batch) {
if ($batch['progressive'] && $batch['url'] == 'batch') {
foreach ($batch['sets'] as &$set) {
if (!empty($set['operations'])) {
foreach ($set['operations'] as &$operation) {
$operation = array('_background_batch_operation', array($operation));
}
}
}
$batch['timestamp'] = microtime(TRUE);
}
// In order to make this batch session independend we save the owner UID.
global $user;
$batch['uid'] = $user->uid;
}
/**
* Implements hook_library().
*/
function background_batch_library() {
$libraries = array();
$libraries['background-process.batch'] = array(
'title' => 'Background batch API',
'version' => '1.0.0',
'js' => array(
drupal_get_path('module', 'background_batch') . '/js/batch.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
),
'dependencies' => array(
array('background_batch', 'background-process.progress'),
),
);
$libraries['background-process.progress'] = array(
'title' => 'Background batch progress',
'version' => VERSION,
'js' => array(
drupal_get_path('module', 'background_batch') . '/js/progress.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
),
);
return $libraries;
}
/**
* Run a batch operation with "listening" context.
* @param $operation
* Batch operation definition.
* @param &$context
* Context for the batch operation.
*/
function _background_batch_operation($operation, &$context) {
// Steal context and trap finished variable
$fine_progress = !empty($context['sandbox']['background_batch_fine_progress']);
if ($fine_progress) {
$batch_context = new BackgroundBatchContext($context);
}
else {
$batch_context = $context;
}
// Call the original operation
$operation[1][] = &$batch_context;
call_user_func_array($operation[0], $operation[1]);
if ($fine_progress) {
// Transfer back context result to batch api
$batch_context = (array)$batch_context;
foreach (array_keys($batch_context) as $key) {
$context[$key] = $batch_context[$key];
}
}
else {
$batch_context = new BackgroundBatchContext($context);
$batch_context['finished'] = $context['finished'];
}
}
/**
* Process a batch step
* @param type $id
* @return type
*/
function _background_batch_process($id = NULL) {
if (!$id) {
return;
}
// Retrieve the current state of batch from db.
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(':bid' => $id))->fetchColumn();
if (!$data) {
return;
}
require_once('includes/batch.inc');
$batch =& batch_get();
$batch = unserialize($data);
// Check if the current user owns (has access to) this batch.
global $user;
if ($batch['uid'] != $user->uid) {
return drupal_access_denied();
}
// Register database update for the end of processing.
drupal_register_shutdown_function('_batch_shutdown');
timer_start('background_batch_processing');
$percentage = 0;
$mem_max_used = 0;
$mem_last_used = memory_get_usage();
$mem_limit = ini_get('memory_limit');
preg_match('/(\d+)(\w)/', $mem_limit, $matches);
switch ($matches[2]) {
case 'M':
default:
$mem_limit = $matches[1] * 1024 * 1024;
break;
}
while ($percentage < 100) {
list ($percentage, $message) = _batch_process();
$mem_used = memory_get_usage();
// If we memory usage of last run will exceed the memory limit in next run
// then bail out
if ($mem_limit < $mem_used + $mem_last_used) {
break;
}
$mem_last_used = $mem_used - $mem_last_used;
// If we maximum memory usage of previous runs will exceed the memory limit in next run
// then bail out
$mem_max_used = $mem_max_used < $mem_last_used ? $mem_last_used : $mem_max_used;
if ($mem_limit < $mem_used + $mem_max_used) {
break;
}
// Restart background process after X miliseconds
if (timer_read('background_batch_processing') > variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN)) {
break;
}
}
if ($percentage < 100) {
background_process_keepalive($id);
}
}
/**
* Processes the batch.
*
* Unless the batch has been marked with 'progressive' = FALSE, the function
* issues a drupal_goto and thus ends page execution.
*
* This function is not needed in form submit handlers; Form API takes care
* of batches that were set during form submission.
*
* @param $redirect
* (optional) Path to redirect to when the batch has finished processing.
* @param $url
* (optional - should only be used for separate scripts like update.php)
* URL of the batch processing page.
*/
function background_batch_process_batch($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') {
$batch =& batch_get();
drupal_theme_initialize();
if (isset($batch)) {
// Add process information
$process_info = array(
'current_set' => 0,
'progressive' => TRUE,
'url' => $url,
'url_options' => array(),
'source_url' => $_GET['q'],
'redirect' => $redirect,
'theme' => $GLOBALS['theme_key'],
'redirect_callback' => $redirect_callback,
);
$batch += $process_info;
// The batch is now completely built. Allow other modules to make changes
// to the batch so that it is easier to reuse batch processes in other
// environments.
drupal_alter('batch', $batch);
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
// table, since non-progressive batches skip database storage completely.
$batch['id'] = db_next_id();
// Move operations to a job queue. Non-progressive batches will use a
// memory-based queue.
foreach ($batch['sets'] as $key => $batch_set) {
_batch_populate_queue($batch, $key);
}
// Initiate processing.
// Now that we have a batch id, we can generate the redirection link in
// the generic error message.
$t = get_t();
$batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
// Clear the way for the drupal_goto() redirection to the batch processing
// page, by saving and unsetting the 'destination', if there is any.
if (isset($_GET['destination'])) {
$batch['destination'] = $_GET['destination'];
unset($_GET['destination']);
}
// Store the batch.
db_insert('batch')
->fields(array(
'bid' => $batch['id'],
'timestamp' => REQUEST_TIME,
'token' => drupal_get_token($batch['id']),
'batch' => serialize($batch),
))
->execute();
// Set the batch number in the session to guarantee that it will stay alive.
$_SESSION['batches'][$batch['id']] = TRUE;
// Redirect for processing.
$function = $batch['redirect_callback'];
if (function_exists($function)) {
// $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id'])));
}
}
background_process_start('_background_batch_process_callback', $batch);
}
function _background_batch_process_callback($batch) {
$rbatch =& batch_get();
$rbatch = $batch;
require_once('background_batch.pages.inc');
_background_batch_page_start();
}
/**
* Class batch context.
* Automatically updates progress when 'finished' index is changed.
*/
class BackgroundBatchContext extends ArrayObject {
private $batch = NULL;
private $interval = NULL;
private $progress = NULL;
public function __construct() {
$this->interval = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY) / 1000000;
$args = func_get_args();
return call_user_func_array(array('parent', '__construct'), $args);
}
/**
* Set progress update interval in seconds (float).
*/
public function setInterval($interval) {
$this->interval = $interval;
}
/**
* Override offsetSet().
* Update progress if needed.
*/
public function offsetSet($name, $value) {
if ($name == 'finished') {
if (!isset($this->batch)) {
$this->batch =& batch_get();
$this->progress = progress_get_progress('_background_batch:' . $this->batch['id']);
}
if ($this->batch) {
$total = $this->batch['sets'][$this->batch['current_set']]['total'];
$count = $this->batch['sets'][$this->batch['current_set']]['count'];
$elapsed = $this->batch['sets'][$this->batch['current_set']]['elapsed'];
$progress_message = $this->batch['sets'][$this->batch['current_set']]['progress_message'];
$current = $total - $count;
$step = 1 / $total;
$base = $current * $step;
$progress = $base + $value * $step;
progress_estimate_completion($this->progress);
$elapsed = floor($this->progress->current - $this->progress->start);
$values = array(
'@remaining' => $count,
'@total' => $total,
'@current' => $current,
'@percentage' => $progress * 100,
'@elapsed' => format_interval($elapsed),
// If possible, estimate remaining processing time.
'@estimate' => format_interval(floor($this->progress->estimate) - floor($this->progress->current)),
);
$message = strtr($progress_message, $values);
$message .= $message && $this['message'] ? '<br/>' : '';
$message .= $this['message'];
progress_set_intervalled_progress('_background_batch:' . $this->batch['id'], $message ? $message : $this->progress->message, $progress, $this->interval);
}
}
return parent::offsetSet($name, $value);
}
}
@@ -0,0 +1,328 @@
<?php
/**
* @file
*
* Pages for background batch.
*
* @todo Implement proper error page instead of just 404.
*/
/**
* System settings page.
*/
function background_batch_settings_form() {
$form = array();
$form['background_batch_delay'] = array(
'#type' => 'textfield',
'#default_value' => variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY),
'#title' => 'Delay',
'#description' => t('Time in microseconds for progress refresh'),
);
$form['background_batch_process_lifespan'] = array(
'#type' => 'textfield',
'#default_value' => variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN),
'#title' => 'Process lifespan',
'#description' => t('Time in milliseconds for progress lifespan'),
);
$form['background_batch_show_eta'] = array(
'#type' => 'checkbox',
'#default_value' => variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA),
'#title' => 'Show ETA of batch process',
'#description' => t('Whether ETA (estimated time of arrival) information should be shown'),
);
return system_settings_form($form);
}
/**
* Overview of current and recent batch jobs.
*/
function background_batch_overview_page() {
$data = array();
$bids = db_select('batch', 'b')
->fields('b', array('bid'))
->orderBy('b.bid', 'ASC')
->execute()
->fetchAllKeyed(0, 0);
foreach ($bids as $bid) {
$progress = progress_get_progress('_background_batch:' . $bid);
$eta = progress_estimate_completion($progress);
$data[] = array(
$progress->end ? $bid : l($bid, 'batch', array('query' => array('op' => 'start', 'id' => $bid))),
sprintf("%.2f%%", $progress->progress * 100),
$progress->message,
$progress->start ? format_date((int)$progress->start, 'small') : t('N/A'),
$progress->end ? format_date((int)$progress->end, 'small') : ($eta ? format_date((int)$eta, 'small') : t('N/A')),
);
}
$header = array('Batch ID', 'Progress', 'Message', 'Started', 'Finished/ETA');
return theme('table', array(
'header' => $header,
'rows' => $data
));
}
/**
* State-based dispatcher for the batch processing page.
*/
function background_batch_page() {
$id = isset($_REQUEST['id']) ? $_REQUEST['id'] : FALSE;
if (!$id) {
return drupal_not_found();
}
// Retrieve the current state of batch from db.
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(':bid' => $id))->fetchColumn();
if (!$data) {
return drupal_not_found();
}
$batch =& batch_get();
$batch = unserialize($data);
// Check if the current user owns (has access to) this batch.
global $user;
if ($batch['uid'] != $user->uid) {
return drupal_access_denied();
}
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
switch ($op) {
case 'start':
return _background_batch_page_start();
case 'do':
return _background_batch_page_do_js();
case 'do_nojs':
return _background_batch_page_do_nojs();
case 'finished':
progress_remove_progress('_background_batch:' . $id);
return _batch_finished();
default:
drupal_goto('admin/config/system/batch/overview');
}
}
/**
* Start a batch job in the background
*/
function _background_batch_initiate($process = NULL) {
require_once('includes/batch.inc');
$batch =& batch_get();
$id = $batch['id'];
$handle = 'background_batch:' . $id;
if (!$process) {
$process = background_process_get_process($handle);
}
if ($process) {
// If batch is already in progress, goto to the status page instead of starting it.
if ($process->exec_status == BACKGROUND_PROCESS_STATUS_RUNNING) {
return $process;
}
// If process is locked and hasn't started for X seconds, then relaunch
if (
$process->exec_status == BACKGROUND_PROCESS_STATUS_LOCKED &&
$process->start_stamp + variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD) < time()
) {
$process = BackgroundProcess::load($process);
$process->dispatch();
}
return $process;
}
else {
// Hasn't run yet or has stopped. (re)start batch job.
$process = new BackgroundProcess($handle);
$process->service_host = 'background_batch';
if ($process->lock()) {
$message = $batch['sets'][0]['init_message'];
progress_initialize_progress('_' . $handle, $message);
if (function_exists('progress_set_progress_start')) {
progress_set_progress_start('_' . $handle, $batch['timestamp']);
}
else {
db_query("UPDATE {progress} SET start = :start WHERE name = :name", array(':start' => $batch['timestamp'], ':name' => '_' . $handle));
}
$result = $process->execute('_background_batch_process', array($id));
return $process;
}
}
}
function _background_batch_page_start() {
_background_batch_initiate();
if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
return _background_batch_page_progress_js();
}
else {
return _background_batch_page_do_nojs();
}
}
/**
* Batch processing page with JavaScript support.
*/
function _background_batch_page_progress_js() {
require_once('includes/batch.inc');
$batch = batch_get();
$current_set = _batch_current_set();
drupal_set_title($current_set['title'], PASS_THROUGH);
// Merge required query parameters for batch processing into those provided by
// batch_set() or hook_batch_alter().
$batch['url_options']['query']['id'] = $batch['id'];
$js_setting['batch'] = array();
$js_setting['batch']['errorMessage'] = $current_set['error_message'] . '<br />' . $batch['error_message'];
// Check wether ETA information should be shown.
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
$js_setting['batch']['initMessage'] = 'ETA: ' . t('N/A') . '<br/>' . $current_set['init_message'];
}
else {
$js_setting['batch']['initMessage'] = $current_set['init_message'];
}
$js_setting['batch']['uri'] = url($batch['url'], $batch['url_options']);
$js_setting['batch']['delay'] = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY);
drupal_add_js($js_setting, 'setting');
drupal_add_library('background_batch', 'background-process.batch');
return '<div id="progress"></div>';
}
/**
* Do one pass of execution and inform back the browser about progression
* (used for JavaScript-mode only).
*/
function _background_batch_page_do_js() {
// HTTP POST required.
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
drupal_set_message(t('HTTP POST is required.'), 'error');
drupal_set_title(t('Error'));
return '';
}
$batch = &batch_get();
$id = $batch['id'];
drupal_save_session(FALSE);
$percentage = t('N/A');
$message = '';
if ($progress = progress_get_progress('_background_batch:' . $id)) {
$percentage = $progress->progress * 100;
$message = $progress->message;
progress_estimate_completion($progress);
// Check wether ETA information should be shown.
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
$message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
}
else {
$js_setting['batch']['initMessage'] = $message;
}
}
if ($batch['sets'][$batch['current_set']]['count'] == 0) {
// The background process has self-destructed, and the batch job is done.
$percentage = 100;
$message = '';
}
elseif ($process = background_process_get_process('background_batch:' . $id)) {
_background_batch_initiate($process);
}
else {
// Not running ... and stale?
_background_batch_initiate();
}
drupal_json_output(array('status' => TRUE, 'percentage' => sprintf("%.02f", $percentage), 'message' => $message));
}
/**
* Output a batch processing page without JavaScript support.
*
* @see _batch_process()
*/
function _background_batch_page_do_nojs() {
$batch = &batch_get();
$id = $batch['id'];
_background_batch_initiate();
$current_set = _batch_current_set();
drupal_set_title($current_set['title'], PASS_THROUGH);
$new_op = 'do_nojs';
// This is one of the later requests; do some processing first.
// Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
// function), it will output whatever is in the output buffer, followed by
// the error message.
ob_start();
$fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
$fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => FALSE));
// We strip the end of the page using a marker in the template, so any
// additional HTML output by PHP shows up inside the page rather than below
// it. While this causes invalid HTML, the same would be true if we didn't,
// as content is not allowed to appear after </html> anyway.
list($fallback) = explode('<!--partial-->', $fallback);
print $fallback;
$percentage = t('N/A');
$message = '';
// Get progress
if ($progress = progress_get_progress('_background_batch:' . $id)) {
$percentage = $progress->progress * 100;
$message = $progress->message;
progress_estimate_completion($progress);
// Check wether ETA information should be shown.
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
$message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
}
}
if ($batch['sets'][$batch['current_set']]['count'] == 0) {
// The background process has self-destructed, and the batch job is done.
$percentage = 100;
$message = '';
}
elseif ($process = background_process_get_process('background_batch:' . $id)) {
_background_batch_initiate($process);
}
else {
// Not running ... and stale?
_background_batch_initiate();
}
if ($percentage == 100) {
$new_op = 'finished';
}
// PHP did not die; remove the fallback output.
ob_end_clean();
// Merge required query parameters for batch processing into those provided by
// batch_set() or hook_batch_alter().
$batch['url_options']['query']['id'] = $batch['id'];
$batch['url_options']['query']['op'] = $new_op;
$url = url($batch['url'], $batch['url_options']);
$element = array(
'#tag' => 'meta',
'#attributes' => array(
'http-equiv' => 'Refresh',
'content' => '0; URL=' . $url,
),
);
drupal_add_html_head($element, 'batch_progress_meta_refresh');
return theme('progress_bar', array('percent' => sprintf("%.02f", $percentage), 'message' => $message));
}
@@ -0,0 +1,32 @@
(function ($) {
/**
* Attaches the batch behavior to progress bars.
*/
Drupal.behaviors.batch = {
attach: function (context, settings) {
$('#progress', context).once('batch', function () {
var holder = $(this);
// Success: redirect to the summary.
var updateCallback = function (progress, status, pb) {
if (progress == 100) {
pb.stopMonitoring();
window.location = settings.batch.uri + '&op=finished';
}
};
var errorCallback = function (pb) {
holder.prepend($('<p class="error"></p>').html(settings.batch.errorMessage));
$('#wait').hide();
};
var progress = new Drupal.progressBar('updateprogress', updateCallback, 'POST', errorCallback);
progress.setProgress(0, settings.batch.initMessage);
holder.append(progress.element);
progress.startMonitoring(settings.batch.uri + '&op=do', Drupal.settings.batch.delay / 1000);
});
}
};
})(jQuery);
@@ -0,0 +1,107 @@
(function ($) {
/**
* A progressbar object. Initialized with the given id. Must be inserted into
* the DOM afterwards through progressBar.element.
*
* method is the function which will perform the HTTP request to get the
* progress bar state. Either "GET" or "POST".
*
* e.g. pb = new progressBar('myProgressBar');
* some_element.appendChild(pb.element);
*/
Drupal.progressBar = function (id, updateCallback, method, errorCallback) {
var pb = this;
this.id = id;
this.method = method || 'GET';
this.updateCallback = updateCallback;
this.errorCallback = errorCallback;
// The WAI-ARIA setting aria-live="polite" will announce changes after users
// have completed their current activity and not interrupt the screen reader.
this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id);
this.element.html('<div class="bar"><div class="filled"></div></div>' +
'<div class="percentage"></div>' +
'<div class="message">&nbsp;</div>');
};
/**
* Set the percentage and status message for the progressbar.
*/
Drupal.progressBar.prototype.setProgress = function (percentage, message) {
if (percentage >= 0 && percentage <= 100) {
$('div.filled', this.element).css('width', percentage + '%');
$('div.percentage', this.element).html(percentage + '%');
}
$('div.message', this.element).html(message);
if (this.updateCallback) {
this.updateCallback(percentage, message, this);
}
};
/**
* Start monitoring progress via Ajax.
*/
Drupal.progressBar.prototype.startMonitoring = function (uri, delay) {
this.delay = delay;
this.uri = uri;
this.sendPing();
};
/**
* Stop monitoring progress via Ajax.
*/
Drupal.progressBar.prototype.stopMonitoring = function () {
clearTimeout(this.timer);
// This allows monitoring to be stopped from within the callback.
this.uri = null;
};
/**
* Request progress data from server.
*/
Drupal.progressBar.prototype.sendPing = function () {
if (this.timer) {
clearTimeout(this.timer);
}
if (this.uri) {
var pb = this;
// When doing a post request, you need non-null data. Otherwise a
// HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
$.ajax({
type: this.method,
url: this.uri,
data: '',
dataType: 'json',
success: function (progress) {
// Display errors.
if (progress.status == 0) {
pb.displayError(progress.data);
return;
}
// Update display.
pb.setProgress(progress.percentage, progress.message);
// Schedule next timer.
pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);
},
error: function (xmlhttp) {
if(xmlhttp.readyState == 0 || xmlhttp.status == 0) return; // it's not really an error
pb.displayError(Drupal.ajaxError(xmlhttp, pb.uri));
}
});
}
};
/**
* Display errors on the page.
*/
Drupal.progressBar.prototype.displayError = function (string) {
var error = $('<div class="messages error"></div>').html(string);
$(this.element).before(error).hide();
if (this.errorCallback) {
this.errorCallback(this);
}
};
})(jQuery);
@@ -0,0 +1,152 @@
<?php
/**
* @file
*/
/**
* FAPI definition for settings page.
*/
function background_process_settings_form() {
$form = array();
$form['background_process_service_timeout'] = array(
'#type' => 'textfield',
'#title' => t('Service timeout'),
'#description' => t('Timeout for service call in seconds (0 = disabled)'),
'#default_value' => variable_get('background_process_service_timeout', BACKGROUND_PROCESS_SERVICE_TIMEOUT),
);
$form['background_process_connection_timeout'] = array(
'#type' => 'textfield',
'#title' => t('Connection timeout'),
'#description' => t('Timeout for connection in seconds'),
'#default_value' => variable_get('background_process_connection_timeout', BACKGROUND_PROCESS_CONNECTION_TIMEOUT),
);
$form['background_process_stream_timeout'] = array(
'#type' => 'textfield',
'#title' => t('Stream timeout'),
'#description' => t('Timeout for stream in seconds'),
'#default_value' => variable_get('background_process_stream_timeout', BACKGROUND_PROCESS_STREAM_TIMEOUT),
);
$form['background_process_redispatch_threshold'] = array(
'#type' => 'textfield',
'#title' => t('Redispatch threshold (for locked processes)'),
'#description' => t('Seconds to wait before redispatching processes that never started.'),
'#default_value' => variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD),
);
$form['background_process_cleanup_age'] = array(
'#type' => 'textfield',
'#title' => t('Cleanup age (for locked processes)'),
'#description' => t('Seconds to wait before unlocking processes that never started.'),
'#default_value' => variable_get('background_process_cleanup_age', BACKGROUND_PROCESS_CLEANUP_AGE),
);
$form['background_process_cleanup_age_running'] = array(
'#type' => 'textfield',
'#title' => t('Cleanup age (for running processes)'),
'#description' => t('Unlock processes that has been running for more than X seconds.'),
'#default_value' => variable_get('background_process_cleanup_age_running', BACKGROUND_PROCESS_CLEANUP_AGE_RUNNING),
);
$form['background_process_cleanup_age_queue'] = array(
'#type' => 'textfield',
'#title' => t('Cleanup age for queued jobs'),
'#description' => t('Unlock queued processes that have been more than X seconds to start.'),
'#default_value' => variable_get('background_process_cleanup_age_queue', BACKGROUND_PROCESS_CLEANUP_AGE_QUEUE),
);
$options = background_process_get_service_hosts();
foreach ($options as $key => &$value) {
$new = empty($value['description']) ? $key : $value['description'];
$base_url = empty($value['base_url']) ? $base_url : $value['base_url'];
$http_host = empty($value['http_host']) ? parse_url($base_url, PHP_URL_HOST) : $value['http_host'];
$new .= ' (' . $base_url . ' - ' . $http_host . ')';
$value = $new;
}
$form['background_process_default_service_host'] = array(
'#type' => 'select',
'#title' => t('Default service host'),
'#description' => t('The default service host to use'),
'#options' => $options,
'#default_value' => variable_get('background_process_default_service_host', 'default'),
);
$methods = module_invoke_all('service_group');
$options = background_process_get_service_groups();
foreach ($options as $key => &$value) {
$value = (empty($value['description']) ? $key : $value['description']) . ' (' . join(',', $value['hosts']) . ') : ' . $methods['methods'][$value['method']];
}
$form['background_process_default_service_group'] = array(
'#type' => 'select',
'#title' => t('Default service group'),
'#description' => t('The default service group to use.'),
'#options' => $options,
'#default_value' => variable_get('background_process_default_service_group', 'default'),
);
$form = system_settings_form($form);
// Add determine button and make sure all the buttons are shown last.
$form['buttons']['#weight'] = 1000;
$form['buttons']['determine'] = array(
'#value' => t("Determine default service host"),
'#description' => t('Tries to determine the default service host.'),
'#type' => 'submit',
'#submit' => array('background_process_settings_form_determine_submit'),
);
return $form;
}
/**
* Submit handler for determining default service host
*/
function background_process_settings_form_determine_submit($form, &$form_state) {
background_process_determine_and_save_default_service_host();
}
/**
* Overview of background processes.
*/
function background_process_overview_page() {
$processes = background_process_get_processes();
$data = array();
foreach ($processes as $process) {
$progress = progress_get_progress($process->handle);
$data[] = array(
$process->handle,
$process->callback,
$process->uid,
$process->service_host,
format_date((int)$process->start, 'custom', 'Y-m-d H:i:s'),
$progress ? sprintf("%.02f%%", $progress->progress * 100) : t('N/A'),
l(t('Unlock'), 'background-process/unlock/' . rawurlencode($process->handle),
array('attributes' => array('class' => 'button-unlock'), 'query' => drupal_get_destination())
)
);
}
$header = array('Handle', 'Callback', 'User', 'Host', 'Start time', 'Progress', '');
$output = '';
$output .= theme('table', array(
'header' => $header,
'rows' => $data,
'class' => 'background-process-overview'
));
return $output;
}
/**
* Unlock background process.
*
* @param $handle
* Handle of process to unlock
*/
function background_process_service_unlock($handle) {
$handle = rawurldecode($handle);
if (background_process_unlock($handle)) {
drupal_set_message(t('Process %handle unlocked', array('%handle' => $handle)));
}
else {
drupal_set_message(t('Process %handle could not be unlocked', array('%handle' => $handle)), 'error');
}
drupal_goto();
}
@@ -0,0 +1,14 @@
name = Background Process
description = Provides framework for running code in the background
core = 7.x
php = 5.0
dependencies[] = progress
configure = admin/config/system/background-process
; Information added by drupal.org packaging script on 2013-01-06
version = "7.x-1.14"
core = "7.x"
project = "background_process"
datestamp = "1357473962"
@@ -0,0 +1,206 @@
<?php
/**
* @file
* This is the installation file for the Background Process module
*/
/**
* Implements of hook_enable().
*/
function background_process_enable() {
$_SESSION['background_process_determine_default_service_host'] = TRUE;
}
/**
* Implements of hook_schema().
*/
function background_process_schema() {
$schema = array();
$schema['background_process'] = array(
'fields' => array(
'handle' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'callback' => array(
'type' => 'text',
'not null' => FALSE,
),
'args' => array(
'type' => 'blob',
'not null' => FALSE,
),
'uid' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'token' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'service_host' => array(
'type' => 'varchar',
'length' => 64,
'not null' => TRUE,
'default' => '',
),
'start_stamp' => array(
'type' => 'varchar',
'length' => '18',
'not null' => FALSE,
),
'exec_status' => array(
'type' => 'int',
'size' => 'normal',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('handle'),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function background_process_uninstall() {
// Removing process variables.
variable_del('background_process_service_timeout');
variable_del('background_process_connection_timeout');
variable_del('background_process_stream_timeout');
variable_del('background_process_service_groups');
variable_del('background_process_default_service_group');
variable_del('background_process_service_hosts');
variable_del('background_process_default_service_host');
variable_del('background_process_cleanup_age');
variable_del('background_process_queues');
variable_del('background_process_derived_default_host');
variable_del('background_process_token');
}
/**
* Implements hook_requirements().
*/
function background_process_requirements($phase) {
$response = array();
switch ($phase) {
case 'install':
return $response;
case 'runtime':
$response['title'] = 'Background Process';
$response['value'] = t('OK');
$response['severity'] = REQUIREMENT_OK;
if (ini_get('safe_mode')) {
$desc = t('Safe mode enabled. Background Process is unable to control maximum execution time for background processes. This may cause background processes to end prematurely.');
if ($response['severity'] < REQUIREMENT_WARNING) {
$response['severity'] = REQUIREMENT_WARNING;
$response['value'] = t('Safe mode enabled');
$response['description'] = $desc;
}
else {
$response['description'] .= '<br/>' . $desc;
}
}
$result = array();
$result['background_process'] = $response;
return $result;
}
}
/**
* Major version upgrade of Drupal
*/
function background_process_update_7000(&$context) {
$context['sandbox']['major_version_upgrade'] = array(
7101 => TRUE,
7102 => TRUE,
7103 => TRUE,
7104 => TRUE,
7105 => TRUE,
7106 => TRUE,
);
}
/**
* Add status column to background_process table.
*/
function background_process_update_7101() {
if (!empty($context['sandbox']['major_version_upgrade'][7101])) {
// This udate is already part of latest 6.x
return;
}
db_add_field('background_process', 'status', array(
'type' => 'int',
'size' => 'normal',
'not null' => TRUE,
'default' => 0,
));
}
/**
* Determine default service host
*/
function background_process_update_7102() {
}
/**
* Determine default service host
*/
function background_process_update_7103() {
}
/**
* Change start column from double to numeric
*/
function background_process_update_7104() {
if (!empty($context['sandbox']['major_version_upgrade'][7104])) {
// This udate is already part of latest 6.x
return;
}
db_change_field('background_process', 'start', 'start', array(
'type' => 'numeric',
'precision' => '16',
'scale' => '6',
'not null' => FALSE,
));
}
/**
* Re-determine default service host.
*/
function background_process_update_7105() {
if (!empty($context['sandbox']['major_version_upgrade'][7105])) {
// This udate is already part of latest 6.x
return;
}
$_SESSION['background_process_determine_default_service_host'] = TRUE;
}
/**
* Change schema to SQL 99 compliance
*/
function background_process_update_7106() {
if (!empty($context['sandbox']['major_version_upgrade'][7106])) {
// This udate is already part of latest 6.x
return;
}
db_change_field('background_process', 'start', 'start_stamp', array(
'type' => 'varchar',
'length' => '18',
'not null' => FALSE,
));
db_change_field('background_process', 'status', 'exec_status', array(
'type' => 'int',
'size' => 'normal',
'not null' => TRUE,
'default' => 0,
));
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
<?php
/**
* @file
* @TODO is this file neccessary?
*/
/**
* Callback for token validation.
*/
function background_process_check_token() {
header("Content-Type: text/plain");
print variable_get('background_process_token', '');
exit;
}
@@ -0,0 +1,19 @@
<?php
/**
* @file
*/
/**
* FAPI definition for settings page.
*/
function background_process_ass_settings_form() {
$form = array();
$form['background_process_ass_max_age'] = array(
'#type' => 'textfield',
'#title' => t('Max age'),
'#description' => t('Time in seconds to wait before considering a process dead.'),
'#default_value' => variable_get('background_process_ass_max_age', BACKGROUND_PROCESS_ASS_MAX_AGE),
);
return system_settings_form($form);
}
@@ -0,0 +1,12 @@
name = Background Process Apache server status
description = Automatically unlocks dead processes using Apache server status
core = 7.x
dependencies[] = background_process
; Information added by drupal.org packaging script on 2013-01-06
version = "7.x-1.14"
core = "7.x"
project = "background_process"
datestamp = "1357473962"
@@ -0,0 +1,384 @@
<?php
/**
* @file
*
* @todo Implement admin interface.
* @todo Fix runtime check of running process.
*/
/**
* Default max age before unlock process.
*/
define('BACKGROUND_PROCESS_ASS_MAX_AGE', 30);
/**
* Implements hook_menu().
*/
function background_process_ass_menu() {
$items = array();
$items['admin/config/system/background-process/ass'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Apache Server Status',
'description' => 'Administer background process apache server status',
'page callback' => 'drupal_get_form',
'page arguments' => array('background_process_ass_settings_form'),
'access arguments' => array('administer background process'),
'file' => 'background_process_ass.admin.inc',
'weight' => 3,
);
return $items;
}
/**
* Implements hook_cron().
*/
function background_process_ass_cron() {
// Don't use more than 30 seconds to unlock
@set_time_limit(30);
background_process_ass_auto_unlock();
}
/**
* Implements hook_cronapi().
*/
function background_process_ass_cronapi($op, $job = NULL) {
switch ($op) {
case 'list':
return array('background_process_ass_cron' => t('Unlock dead processes'));
case 'rule':
return '* * * * *';
case 'configure':
return 'admin/config/system/background-process/ass';
}
}
/**
* Implements hook_cron_alter().
*/
function background_process_ass_cron_alter(&$items) {
$items['background_process_ass_cron']['override_congestion_protection'] = TRUE;
// Unlock background if too old.
// @todo Move to some access handler or pre-execute?
if ($process = background_process_get_process('uc:background_process_ass_cron')) {
if ($process->start + 30 < time()) {
background_process_unlock($process->handle, t('Self unlocking stale lock'));
}
}
}
/**
* Implements hook_service_group().
*/
function background_process_ass_service_group() {
$info = array();
$info['methods']['background_process_ass_service_group_idle'] = t('Idle workers');
return $info;
}
/**
* Determine host with most idle workers and claim it.
*
* @param $service_group
* Service group to check
* @return
* Claimed service host on success, NULL if none found
*/
function background_process_ass_service_group_idle($service_group, $reload = FALSE) {
$result = NULL;
$max = 0;
$msg = "";
$workers = &drupal_static('background_process_ass_idle_workers', array());
// Load idle worker status for all hosts
foreach ($service_group['hosts'] as $idx => $host) {
$name = $host . '_ass';
if ($reload || !isset($workers[$name])) {
$workers[$name] = background_process_ass_get_server_status($name, TRUE, $reload);
}
// Reload apache server status for all hosts, if any is fully loaded
if ($workers[$name] <= 0 && !$reload) {
return background_process_ass_service_group_idle($service_group, TRUE);
}
if ($max < $workers[$name]) {
$result = $host;
$max = $workers[$name];
}
}
if (isset($result)) {
// Claim host and tell caller
$workers[$result . '_ass']--;
return $result;
}
else {
// Could not determine most idle host, fallback to pseudo round robin
return background_process_service_group_round_robin($service_group);
}
}
/**
* Unlock locked processes that aren't really running.
*/
function background_process_ass_auto_unlock() {
$processes = background_process_get_processes();
$service_hosts = background_process_get_service_hosts();
foreach ($processes as $process) {
// Don't even dare try determining state, if not properly configured.
if (!$process->service_host) {
continue;
}
// Naming convention suffix "_ass" for a given service hosts defines the
// host to use for server-status.
if (!isset($service_hosts[$process->service_host . '_ass'])) {
continue;
}
if (!isset($service_hosts[$process->service_host])) {
continue;
}
list($url, $headers) = background_process_build_request('bgp-start/' . rawurlencode($process->handle), $process->service_host);
$process->http_host = $headers['Host'];
// Locate our connection
$url = parse_url($url);
$path = $url['path'] . (isset($url['query']) ? '?' . $url['query'] : '');
if (strlen("POST $path") > 64) {
// Request is larger than 64 characters, which is the max length of
// requests in the extended Apache Server Status. We cannot determine
// if it's running or not ... skip this process!
continue;
}
if ($process->status != BACKGROUND_PROCESS_STATUS_RUNNING) {
// Not ready for unlock yet
continue;
}
if ($process->start > time() - variable_get('background_process_ass_max_age', BACKGROUND_PROCESS_ASS_MAX_AGE)) {
// Not ready for unlock yet
continue;
}
$server_status = background_process_ass_get_server_status($process->service_host . '_ass');
if ($server_status) {
if (!background_process_ass_check_process($process, $server_status, $path)) {
_background_process_ass_unlock($process);
}
}
}
}
/**
* Check if process is really running.
*
* @param $process
* Process object
* @param $server_status
* Server status data
* @return boolean
* TRUE if running, FALSE if not.
*/
function background_process_ass_check_process($process, $server_status, $path) {
$active = TRUE;
// Is status reliable?
if ($server_status && $server_status['status']['Current Timestamp'] > $process->start) {
// Check if process is in the server status
if (!empty($server_status['connections'])) {
$active = FALSE;
foreach ($server_status['connections'] as $conn) {
if ($conn['M'] == 'R') {
// We cannot rely on the server status, assume connection is still
// active, and bail out.
watchdog('bg_process', 'Found reading state ...', array(), WATCHDOG_WARNING);
$active = TRUE;
break;
}
// Empty connections, skip them
if ($conn['M'] == '.' || $conn['M'] == '_') {
continue;
}
if (
$conn['VHost'] == $process->http_host &&
strpos($conn['Request'], 'POST ' . $path) === 0
) {
$active = TRUE;
break;
}
}
}
}
return $active;
}
function _background_process_ass_unlock($process) {
watchdog('bg_process', 'Unlocking: ' . $process->handle);
if ($process->status == BACKGROUND_PROCESS_STATUS_RUNNING) {
$msg = t('Died unexpectedly (auto unlock due to missing connection)');
// Unlock the process
if (background_process_unlock($process->handle, $msg, $process->start)) {
drupal_set_message(t("%handle unlocked: !msg", array('%handle' => $process->handle, '!msg' => $msg)));
}
}
}
/**
* Get apache extended server status.
*
* @staticvar $server_status
* Cached statically to avoid multiple requests to server-status.
* @param $name
* Name of service host for server-status.
* @param $auto
* Load only idle workers, not entire server status.
* @param $reload
* Don't load from cache.
* @return array
* Server status data.
*/
function background_process_ass_get_server_status($name, $auto = FALSE, $reload = FALSE) {
// Sanity check ...
if (!$name) {
return;
}
$service_hosts = variable_get('background_process_service_hosts', array());
if (empty($service_hosts[$name])) {
return;
}
$service_host = $service_hosts[$name];
// Static caching.
$cache = &drupal_static('background_process_ass_server_status', array());
if (!$reload && isset($cache[$name][$auto])) {
return $cache[$name][$auto];
}
$server_status = array();
$options = array();
if ($auto) {
$options['query']['auto'] = 1;
}
list($url, $headers) = background_process_build_request('', $name, $options);
$timestamp = time();
$response = drupal_http_request($url, array('headers' => $headers));
if ($response->code != 200) {
watchdog('bg_process', 'Could not acquire server status from %url - error: %error', array('%url' => $url, '%error' => $response->error), WATCHDOG_ERROR);
return NULL;
}
// If "auto" only collect idle workers
if ($auto) {
preg_match('/IdleWorkers:\s+(\d+)/', $response->data, $matches);
$server_status = $matches[1];
}
else {
$tables = _background_process_ass_parse_table($response->data);
$dls = _background_process_ass_parse_definition_list($response->data);
$server_status = array(
'response' => $response,
'connections' => $tables[0],
'status' => $dls[1],
);
preg_match('/.*?,\s+(\d+-.*?-\d+\s+\d+:\d+:\d+)/', $server_status['status']['Restart Time'], $matches);
// @hack Convert monthly names from Danish to English for strtotime() to work
str_replace('Maj', 'May', $matches[1]);
str_replace('May', 'Oct', $matches[1]);
$server_status['status']['Restart Timestamp'] = strtotime($matches[1]);
$server_status['status']['Current Timestamp'] = $timestamp;
}
$cache[$name][$auto] = $server_status;
return $server_status;
}
/**
* Converts an HTML table into an associative array.
*
* @param $html
* HTML containing table.
* @return array
* Table data.
*/
function _background_process_ass_parse_table($html) {
// Find the table
preg_match_all("/<table.*?>.*?<\/[\s]*table>/s", $html, $table_htmls);
$tables = array();
foreach ($table_htmls[0] as $table_html) {
// Get title for each row
preg_match_all("/<th.*?>(.*?)<\/[\s]*th>/s", $table_html, $matches);
$row_headers = $matches[1];
// Iterate each row
preg_match_all("/<tr.*?>(.*?)<\/[\s]*tr>/s", $table_html, $matches);
$table = array();
foreach ($matches[1] as $row_html) {
$row_html = preg_replace("/\r|\n/", '', $row_html);
preg_match_all("/<td.*?>(.*?)<\/[\s]*td>/", $row_html, $td_matches);
$row = array();
for ($i=0; $i<count($td_matches[1]); $i++) {
$td = strip_tags(html_entity_decode($td_matches[1][$i]));
$i2 = isset($row_headers[$i]) ? $row_headers[$i] : $i;
$row[$i2] = $td;
}
if (count($row) > 0) {
$table[] = $row;
}
}
$tables[] = $table;
}
return $tables;
}
/**
* Converts an HTML table into an associative array.
*
* @param $html
* HTML containing table.
* @return array
* Table data.
*/
function _background_process_ass_parse_definition_list($html) {
// Find the table
preg_match_all("/<dl.*?>.*?<\/[\s]*dl>/s", $html, $dl_htmls);
$dls = array();
foreach ($dl_htmls[0] as $dl_html) {
// Get title for each row
preg_match_all("/<dl.*?>(.*?)<\/[\s]*dl>/s", $dl_html, $matches);
$dl = array();
foreach ($matches[1] as $row_html) {
$row_html = preg_replace("/\r|\n/", '', $row_html);
preg_match_all("/<dt.*?>(.*?)<\/[\s]*dt>/", $row_html, $dt_matches);
$row = array();
for ($i=0; $i<count($dt_matches[1]); $i++) {
$dt = strip_tags(html_entity_decode($dt_matches[1][$i]));
if (strpos($dt, ':') !== FALSE) {
list($key, $value) = explode(': ', $dt, 2);
$dl[$key] = $value;
}
}
}
$dls[] = $dl;
}
return $dls;
}
@@ -0,0 +1,10 @@
(function ($) {
Drupal.Nodejs.callbacks.nodejsBackgroundProcess = {
callback: function (message) {
}
};
}(jQuery));
+4
View File
@@ -0,0 +1,4 @@
-------------------------------------------------------------------------------------
7.x-1.0 07/11/2011
-------------------------------------------------------------------------------------
- First official 1.0 release for D7.
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,38 @@
<?php
/**
* @file
* Hooks provided by Bundle copy.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Implements hook_bundle_copy_info().
*
* Return info for bundle copy. The first key is
* the name of the entity_type.
*/
function hook_bundle_copy_info() {
return array(
'node' => array(
'bundle_export_callback' => 'node_type_get_type',
'bundle_save_callback' => 'node_type_save',
'export_menu' => array(
'path' => 'admin/structure/types/export',
'access arguments' => 'administer content types',
),
'import_menu' => array(
'path' => 'admin/structure/types/import',
'access arguments' => 'administer content types',
),
),
);
}
/**
* @} End of "addtogroup hooks".
*/
+12
View File
@@ -0,0 +1,12 @@
name="Bundle copy"
description="Import and exports bundles through the UI."
core=7.x
dependencies[] = ctools
package="Fields"
files[] = bundle_copy.module
; Information added by drupal.org packaging script on 2012-03-28
version = "7.x-1.1"
core = "7.x"
project = "bundle_copy"
datestamp = "1332926440"
@@ -0,0 +1,560 @@
<?php
/**
* @file
* Bundle copy.
*/
/**
* Api function to get the bundle copy info.
*/
function bundle_copy_get_info() {
static $info = FALSE;
if (!$info) {
return module_invoke_all('bundle_copy_info');
}
return $info;
}
/**
* Implements hook_bundle_copy_info().
*/
function bundle_copy_bundle_copy_info() {
$info = array();
$info['node'] = array(
'bundle_export_callback' => 'node_type_get_type',
'bundle_save_callback' => 'node_type_save',
'export_menu' => array(
'path' => 'admin/structure/types/export',
'access arguments' => 'administer content types',
),
'import_menu' => array(
'path' => 'admin/structure/types/import',
'access arguments' => 'administer content types',
),
);
$info['user'] = array(
'bundle_export_callback' => '_bc_bundle_export_ignore',
'bundle_save_callback' => '_bc_bundle_save_ignore',
'export_menu' => array(
'path' => 'admin/config/people/accounts/export',
'access arguments' => 'administer users',
),
'import_menu' => array(
'path' => 'admin/config/people/accounts/import',
'access arguments' => 'administer users',
),
);
if (module_exists('taxonomy')) {
$info['taxonomy_term'] = array(
'bundle_export_callback' => '_bc_copy_taxonomy_load',
'bundle_save_callback' => '_bc_copy_taxonomy_save',
'export_menu' => array(
'path' => 'admin/structure/taxonomy/export',
'access arguments' => 'administer taxonomy',
),
'import_menu' => array(
'path' => 'admin/structure/taxonomy/import',
'access arguments' => 'administer taxonomy',
),
);
}
return $info;
}
/**
* Implements hook_menu().
*/
function bundle_copy_menu() {
$items = array();
$bc_info = bundle_copy_get_info();
foreach ($bc_info as $entity_type => $info) {
$items[$info['export_menu']['path']] = array(
'title' => 'Export',
'page callback' => 'drupal_get_form',
'page arguments' => array('bundle_copy_export', $entity_type),
'access arguments' => array($info['export_menu']['access arguments']),
'type' => MENU_LOCAL_TASK
);
$items[$info['import_menu']['path']] = array(
'title' => 'Import',
'page callback' => 'drupal_get_form',
'page arguments' => array('bundle_copy_import', $entity_type),
'access callback' => 'bundle_copy_import_access',
'access arguments' => array($info['import_menu']['access arguments']),
'type' => MENU_LOCAL_TASK
);
}
return $items;
}
/**
* Bundle copy import access callback.
*
* Bundle copy imports require an additional access check because they are PHP
* code and PHP is more locked down than the general permission.
*/
function bundle_copy_import_access($permission) {
return user_access($permission) && user_access('use PHP for settings');
}
/**
* Menu callback: present the export page.
*/
function bundle_copy_export($form, &$form_state, $entity_type = 'node') {
if (isset($form_state['step'])) {
$step = $form_state['step'];
}
else {
$step = 1;
$form_state['step'] = $step;
}
switch ($step) {
// Select the bundles.
case 1:
$bundles = _bundle_copy_bundle_info($entity_type, TRUE);
$form['bundle-info'] = array(
'#markup' => t('Select bundles you want to export.'),
);
$form['bundles'] = array(
'#type' => 'tableselect',
'#header' => array('label' => t('Bundle')),
'#options' => $bundles,
'#required' => TRUE,
'#empty' => t('No bundles found.'),
);
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next'),
);
break;
// List the fields / field groups.
case 2:
// Field group.
$all_groups = function_exists('field_group_info_groups') ? field_group_info_groups() : array();
// Fields.
$field_options = $instances = array();
$selected_bundles = $form_state['page_values'][1]['bundles'];
foreach ($selected_bundles as $key => $bundle) {
if ($key === $bundle) {
$instances += field_info_instances($entity_type, $bundle);
}
}
ksort($instances);
foreach ($instances as $key => $info) {
$field_options[$key]['field'] = $info['field_name']; // Same as $key.
$field_options[$key]['label'] = $info['label'];
}
$form['fields-info'] = array(
'#markup' => t('Select fields you want to export.'),
);
$form['fields'] = array(
'#type' => 'tableselect',
'#header' => array('field' => t('Field name'), 'label' => t('Label')),
'#options' => $field_options,
'#empty' => t('No fields found.'),
);
// Field group support.
if (!empty($all_groups)) {
$group_options = $fieldgroups = array();
if (isset($all_groups[$entity_type])) {
foreach ($selected_bundles as $key => $bundle) {
if ($key === $bundle) {
if (!isset($all_groups[$entity_type][$key])) {
continue;
}
foreach ($all_groups[$entity_type][$key] as $view_mode => $groups) {
foreach ($groups as $field_group) {
$group_options[$field_group->id]['fieldgroup'] = $field_group->label . ' (' . $field_group->bundle . ' - ' . $field_group->mode .')';
$fieldgroups[$field_group->id] = $field_group;
}
}
}
}
}
if (!empty($group_options)) {
$form['fieldgroups-info'] = array(
'#markup' => t('Select field groups you want to export.'),
);
$form['fieldgroups'] = array(
'#type' => 'tableselect',
'#header' => array('fieldgroup' => t('Field group name')),
'#options' => $group_options,
);
$form['fieldgroups-full'] = array(
'#type' => 'value',
'#value' => $fieldgroups,
);
}
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['next'] = array(
'#type' => 'submit',
'#value' => t('Export'),
);
$bc_info = bundle_copy_get_info();
$form['actions']['cancel'] = array(
'#markup' => l(t('Cancel'), $bc_info[$entity_type]['export_menu']['path']),
);
break;
// Export data.
case 3:
$data = _bundle_copy_export_data($entity_type, $form_state['page_values']);
$form['export'] = array(
'#title' => t('Export data'),
'#type' => 'textarea',
'#cols' => 60,
'#value' => $data,
'#rows' => 40,
'#description' => t('Copy the export text and paste it into another bundle using the import function.'),
);
break;
}
return $form;
}
/**
* Submit callback: export data.
*/
function bundle_copy_export_submit($form, &$form_state) {
// Save the form state values.
$step = $form_state['step'];
$form_state['page_values'][$step] = $form_state['values'];
// Add step and rebuild.
$form_state['step'] = $form_state['step'] + 1;
$form_state['rebuild'] = TRUE;
}
/**
* Menu callback: present the import page.
*/
function bundle_copy_import($form, $form_state, $entity_type = 'node') {
$form['entity_type'] = array('#type' => 'value', '#value' => $entity_type);
$form['info'] = array(
'#markup' => t('This form will import bundle and field definitions.'),
);
//$form['type_name'] = array(
// '#title' => t('Bundle'),
// '#description' => t('Select the bundle to import these fields into.<br/>Select &lt;Create&gt; to create a new bundle to contain the fields.'),
// '#type' => 'select',
// '#options' => array('<create>' => t('<Create>')) + _bundle_copy_bundle_info($entity_type),
//);
$form['macro'] = array(
'#type' => 'textarea',
'#rows' => 10,
'#title' => t('Import data'),
'#required' => TRUE,
'#description' => t('Paste the text created by a bundle export into this field.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
return $form;
}
/**
* Submit callback: import data.
*/
function bundle_copy_import_submit($form, &$form_state) {
// Evaluate data.
eval($form_state['values']['macro']);
if (isset($data) && is_array($data)) {
$modules = module_list();
$bc_info = bundle_copy_get_info();
// Create bundles.
foreach ($data['bundles'] as $key => $bundle) {
$entity_type = '';
if (is_object($bundle)) {
$entity_type = $bundle->bc_entity_type;
}
elseif (is_array($bundle)) {
$entity_type = $bundle['bc_entity_type'];
}
if (!empty($entity_type)) {
$existing_bundles = _bundle_copy_bundle_info($entity_type);
$bundle_save_callback = $bc_info[$entity_type]['bundle_save_callback'];
$bundle_info = $bundle_save_callback($bundle);
if (!isset($existing_bundles[$key])) {
drupal_set_message(t('%bundle bundle has been created.', array('%bundle' => $bundle->name)));
}
else {
drupal_set_message(t('%bundle bundle has been updated.', array('%bundle' => $bundle->name)));
}
}
}
// Create or update fields and their instances
if (isset($data['fields'])) {
foreach ($data['fields'] as $key => $field) {
// Check if the field module exists.
$module = $field['module'];
if (!isset($modules[$module])) {
drupal_set_message(t('%field_name field could not be created because the module %module is disabled or missing.', array('%field_name' => $key, '%module' => $module)), 'error');
continue;
}
if (isset($data['instances'][$key])) {
// Create or update field.
$prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE));
if (!$prior_field) {
field_create_field($field);
drupal_set_message(t('%field_name field has been created.', array('%field_name' => $key)));
}
else {
$field['id'] = $prior_field['id'];
field_update_field($field);
drupal_set_message(t('%field_name field has been updated.', array('%field_name' => $key)));
}
// Create or update field instances.
foreach ($data['instances'][$key] as $ikey => $instance) {
// Make sure the needed key exists.
if (!isset($instance['field_name'])) {
continue;
}
$prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
if (!$prior_instance) {
field_create_instance($instance);
drupal_set_message(t('%field_name instance has been created for @bundle in @entity_type.', array('%field_name' => $key, '@bundle' => $instance['bundle'], '@entity_type' => $instance['entity_type'])));
}
else {
$instance['id'] = $prior_instance['id'];
$instance['field_id'] = $prior_instance['field_id'];
field_update_instance($instance);
drupal_set_message(t('%field_name instance has been updated for @bundle in @entity_type.', array('%field_name' => $key, '@bundle' => $instance['bundle'], '@entity_type' => $instance['entity_type'])));
}
}
}
}
}
// Create / update fieldgroups.
if (isset($data['fieldgroups'])) {
if (module_exists('field_group')) {
ctools_include('export');
$existing_field_groups = field_group_info_groups();
foreach ($data['fieldgroups'] as $identifier => $fieldgroup) {
if (isset($existing_field_groups[$fieldgroup->entity_type][$fieldgroup->bundle][$fieldgroup->mode][$fieldgroup->group_name])) {
$existing = $existing_field_groups[$fieldgroup->entity_type][$fieldgroup->bundle][$fieldgroup->mode][$fieldgroup->group_name];
$fieldgroup->id = $existing->id;
if (!isset($fieldgroup->disabled)) {
$fieldgroup->disabled = FALSE;
}
ctools_export_crud_save('field_group', $fieldgroup);
ctools_export_crud_set_status('field_group', $fieldgroup, $fieldgroup->disabled);
drupal_set_message(t('%fieldgroup fieldgroup has been updated for @bundle in @entity_type.', array('%fieldgroup' => $fieldgroup->label, '@bundle' => $fieldgroup->bundle, '@entity_type' => $fieldgroup->entity_type)));
}
else {
unset($fieldgroup->id);
unset($fieldgroup->export_type);
if (!isset($fieldgroup->disabled)) {
$fieldgroup->disabled = FALSE;
}
ctools_export_crud_save('field_group', $fieldgroup);
$fieldgroup->export_type = 1;
ctools_export_crud_set_status('field_group', $fieldgroup, $fieldgroup->disabled);
drupal_set_message(t('%fieldgroup fieldgroup has been saved for @bundle in @entity_type.', array('%fieldgroup' => $fieldgroup->label, '@bundle' => $fieldgroup->bundle, '@entity_type' => $fieldgroup->entity_type)));
}
}
}
else {
drupal_set_message(t('The fieldgroups could not be saved because the <em>Field group</em> module is disabled or missing.'), 'error');
}
}
// Clear caches.
field_info_cache_clear();
if (module_exists('field_group')) {
cache_clear_all('field_groups', 'cache_field');
}
}
else {
drupal_set_message(t('The pasted text did not contain any valid export data.'), 'error');
}
}
/**
* Return bundles for a certain entity type.
*
* @param $entity_type
* The name of the entity type.
* @param $table_select
* Whether we're returning for the table select or not.
*/
function _bundle_copy_bundle_info($entity_type, $table_select = FALSE) {
static $bundles = array();
if (!isset($bundles[$entity_type])) {
$bundles[$entity_type] = array();
$entity_info = entity_get_info($entity_type);
$entity_bundles = $entity_info['bundles'];
ksort($entity_bundles);
foreach ($entity_bundles as $key => $info) {
$label = isset($info['label']) ? $info['label'] : drupal_ucfirst(str_replace('_', ' ', $key));
if ($table_select) {
$bundles[$entity_type][$key]['label'] = $label;
}
else {
$bundles[$entity_type][$key] = $label;
}
}
}
return $bundles[$entity_type];
}
/**
* Creates export data
*
* @param $entity_type
* The name of the entity_type
* @param $selected_data
* The selected data.
*/
function _bundle_copy_export_data($entity_type, $selected_data) {
ctools_include('export');
$bc_info = bundle_copy_get_info();
$selected_bundles = $selected_data[1]['bundles'];
$selected_fields = $selected_data[2]['fields'];
$selected_fieldgroups = isset($selected_data[2]['fieldgroups']) ? $selected_data[2]['fieldgroups'] : array();
$full_fieldgroups = isset($selected_data[2]['fieldgroups-full']) ? $selected_data[2]['fieldgroups-full'] : array();
$data = $instances = array();
$fields = field_info_fields();
foreach ($selected_bundles as $bkey => $binfo) {
if ($bkey !== $binfo) {
continue;
}
$field_instances = field_info_instances($entity_type, $bkey);
ksort($field_instances);
// Bundles export data.
$bundle_info_callback = $bc_info[$entity_type]['bundle_export_callback'];
$bundle_info = $bundle_info_callback($bkey, $entity_type);
if (is_object($bundle_info)) {
$bundle_info->bc_entity_type = $entity_type;
}
elseif (is_array($bundle_info)) {
$bundle_info['bc_entity_type'] = $entity_type;
}
$data['bundles'][$bkey] = $bundle_info;
// Fields export data.
foreach ($selected_fields as $fkey => $finfo) {
if ($fkey === $finfo) {
if (!isset($data['fields'][$fkey])) {
unset($fields[$fkey]['id']);
$data['fields'][$fkey] = $fields[$fkey];
}
if (isset($field_instances[$fkey])) {
unset($field_instances[$fkey]['id']);
unset($field_instances[$fkey]['field_id']);
$instances[$fkey][] = $field_instances[$fkey];
}
}
}
}
ksort($instances);
$data['instances'] = $instances;
// Field group export data.
if (!empty($selected_fieldgroups)) {
foreach ($selected_fieldgroups as $key => $value) {
if ($value !== 0) {
$data['fieldgroups'][$full_fieldgroups[$key]->identifier] = $full_fieldgroups[$key];
}
}
}
return '$data = ' . ctools_var_export($data) . ';';
}
/**
* Helper function to load the taxonomy, but remove the vid on the object.
*
* @param $name
* The name of the bundle.
*/
function _bc_copy_taxonomy_load($name) {
$bundle = taxonomy_vocabulary_machine_name_load($name);
return $bundle;
}
/**
* Helper function to save the taxonomy.
*/
function _bc_copy_taxonomy_save($bundle) {
if ($bundle->vid) {
unset($bundle->vid);
}
$vid = db_query('SELECT vid FROM {taxonomy_vocabulary} WHERE machine_name = :machine_name', array(':machine_name' => $bundle->machine_name))->fetchField();
if ($vid) {
$bundle->vid = $vid;
}
taxonomy_vocabulary_save($bundle);
}
/**
* Helper function to ignore a bundle on export.
*/
function _bc_bundle_export_ignore($name) {
}
/**
* Helper function to ignore a bundle save.
*/
function _bc_bundle_save_ignore($bundle) {
}
@@ -0,0 +1,54 @@
Current API Version: 2.0.8
Please note that the API version is an internal number and does not match release numbers. It is entirely possible that releases will not increase the API version number, and increasing this number too often would burden contrib module maintainers who need to keep up with API changes.
This file contains a log of changes to the API.
API Version 2.0.9
Changed import permissions to use the new 'use ctools import' permission.
API Version 2.0.8
Introduce ctools_class_add().
Introduce ctools_class_remove().
API Version 2.0.7
All ctools object cache database functions can now accept session_id as an optional
argument to facilitate using non-session id keys.
API Version 2.0.6
Introduce a hook to alter the implementors of a certain api via hook_[ctools_api_hook]_alter.
API Version 2.0.5
Introduce ctools_fields_get_fields_by_type().
Add language.inc
Introduce hook_ctools_content_subtype_alter($subtype, $plugin);
API Version 2.0.4
Introduce ctools_form_include_file()
API Version 2.0.3
Introduce ctools_field_invoke_field() and ctools_field_invoke_field_default().
API Version 2.0.2
Introduce ctools_export_crud_load_multiple() and 'load multiple callback' to
export schema.
API Version 2.0.1
Introduce ctools_export_crud_enable(), ctools_export_crud_disable() and
ctools_export_crud_set_status() and requisite changes.
Introduce 'object factory' to export schema, allowing modules to control
how the exportable objects are instantiated.
Introduce 'hook_ctools_math_expression_functions_alter'.
API Version 2.0
Remove the deprecated callback-based behavior of the 'defaults' property on
plugin types; array addition is now the only option. If you need more
complex logic, do it with the 'process' callback.
Introduce a global plugin type registration hook and remove the per-plugin
type magic callbacks.
Introduce $owner . '_' . $api . '_hook_name' allowing modules to use their own
API hook in place of 'hook_ctools_plugin_api'.
Introduce ctools_plugin_api_get_hook() to get the hook name above.
Introduce 'cache defaults' and 'default cache bin' keys to export.inc
Versions prior to 2.0 have been removed from this document. See the D6 version
for that information.
@@ -0,0 +1,82 @@
Current API VERSION: 2.0. See API.txt for more information.
ctools 7.x-1.x-dev
==================
#1008120: "New custom content" shows empty form if custom content panes module is not enabled.
#999302 by troky: Fix jump menu. Apparently this wasn't actually committed the last time it was committed.
#1065976 by tekante and David_Rothstein: Reset plugin static cache during module enable to prevent stale data from harming export ui.
#1016510 by EclipseGC: Make the taxonomy system page functional.
ctools 7.x-1.x-alpha2 (05-Jan-2011)
===================================
#911396 by alex_b: Prevent notices in export UI.
#919768 by mikey_p: Allow url options to be sent to ctools_ajax_command_url().
#358953 by cedarm: Allow term context to return lowercase, spaces to dashes versions of terms.
#931434 by EclipseGc: Argument plugin for node revision ID.
#910656: CTools AJAX sample wizard demo "domesticated" checkbox value not stored.
#922442 by EugenMayer, neclimdul and voxpelli: Make sure ctools_include can handle '' or NULL directory.
#919956 by traviss359: Correct example in wizard advanced help.
#942968: Fix taxonomy term access rule with tag term vocabs.
#840344: node add argument had crufty code causing notices.
#944462 by longhairedgit: Invalid character in regex causes rare notice.
#938778 by dereine: Fix profile content type for D7 updates.
Add detach event to modal close so that wysiwyg can detach the editor.
Variant titles showing up as blank if more than one variant on a page.
#940016: token support was not yet updated for D7.
#940446: Skip validation on back and cancel buttons in all wizards.
#954492: Redirect not always working in wizard.inc
#955348: Lack of redirect on "Update" button in Page Manager causing data loss sometimes.
#941778: Update and save button should not appear in the "Add variant" path.
#955070 by EclipseGc: Update ctools internal page tokens to work properly on content all content.
#956890 by EclipseGc: Update views_content to not use views dependency since that is gone.
#954728 by EclipseGc: Update node template page function name to not collide with new hook_node_view().
#946534 by EclipseGc: Add support for field content on all entitities.
#952586 by EclipseGc: Fix node_author content type.
#959206: If a context is not set when rendering content, attempt to guess the context (fixes Views panes where "From context" was added but pane was never edited.)
#961654 by benshell: drupal_alter() only supports 4 arguments.
#911362 by alex_b: Facilitate plugin cache resets for tests.
#945360 by naxoc: node_tag_new() not updated to D7.
#953804 by EclipseGc: Fix node comment rendering.
#953542 by EclipseGc: Fix node rendering.
#953776 by EclipseGc: Fix node link rendering.
#954772 by EclipseGc: Fix node build mode selection in node content type.
#954762 by EclipseGc: Fix comment forbidden theme call.
#954894 by EclipseGc: Fix breadcrumb content type.
#955180 by EclipseGc: Fix page primary navigation type.
#957190 by EclipseGc: Fix page secondary navigation type.
#957194 by EclipseGc: Remove mission content type, since D7 no longer has a site mission.
#957348 by EclipseGc: Fix search form URL path.
#952586 by andypost: Use format_username for displaying unlinked usernames.
#963800 by benshell: Fix query to fetch custom block title.
#983496 by Amitaibu: Fix term argument to use proper load function.
#989484 by Amitaibu: Fix notice in views plugin.
#982496: Fix token context.
#995026: Fix export UI during enable/disable which would throw notices and not properly set/unset menu items.
#998870 by Amitaibu: Fix notice when content has no icon by using function already designed for that.
#983576 by Amitaibu: Node view fallback task showed white screen.
#1004644 by pillarsdotnet: Update a missed theme() call to D7.
#1006162 by aspilicious: .info file cleanup.
#998312 by dereine: Support the expanded/hidden options that Views did for dependent.js
#955030: Remove no longer supported footer message content type.
Fix broken query in term context config.
#992022 by pcambra: Fix node autocomplete.
#946302 by BerdArt and arywyr: Fix PHP 5.3 reference error.
#980528 by das-peter: Notice fix with entity settings.
#999302 by troky: ctools_jump_menu() needed updating to new form parameters.
#964174: stylizer plugin theme delegation was in the wrong place, causing errors.
#991658 by burlap: Fully load the "user" context for the logged in user because not all fields are in $user.
#1014866 by das-peter: Smarter title panes, notice fix on access plugin descriptions.
#1015662 by troky: plugin .info files were not using correct filepaths.
#941780 by EclipseGc: Restore the "No blocks" functionality.
#951048 by EclipseGc: Tighter entity integration so that new entities are automatic contexts and relationships.
#941800 by me and aspilicious: Use Drupal 7 #machine_name automation on page manager pages and all export_ui defaults.
Disabled exportables and pages not properly greyed out.
#969208 by me and benshell: Get user_view and user profile working.
#941796: Recategorize blocks
ctools 7.x-1.x-alpha1
=====================
Changelog reset for 7.x
Basic conversion done during sprint.
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,63 @@
Upgrading from ctools-6.x-1.x to ctools-7.x-2.x:
- Remove ctools_ajax_associate_url_to_element as it shouldn't be necessary
with the new AJAX api's in Drupal core.
- All calls to the ctools_ajax_command_prepend() should be replace with
the core function ajax_command_prepend();
This is also the case for append, insert, after, before, replace, html,
and remove commands.
Each of these commands have been incorporated into the
Drupal.ajax.prototype.commands.insert
function with a corresponding parameter specifying which method to use.
- All calls to ctools_ajax_render() should be replaced with calls to core
ajax_render(). Note that ctools_ajax_render() printed the json object and
exited, ajax_render() gives you this responsibility.
ctools_ajax_render()
becomes
print ajax_render();
exit;
- All calls to ctools_static*() should be replaced with corresponding calls
to drupal_static*().
- All calls to ctools_css_add_css should be replaced with calls to
drupal_add_css(). Note that the arguments to drupal_add_css() have changed.
- All wizard form builder functions must now return a form array().
- ctools_build_form is very close to being removed. In anticipation of this,
all $form_state['wrapper callback']s must now be
$form_state['wrapper_callback']. In addition to this $form_state['args']
must now be $form_state['build_info']['args'].
NOTE: Previously checking to see if the return from ctools_build_form()
is empty would be enough to see if the form was submitted. This is no
longer true. Please check for $form_state['executed']. If using a wizard
check for $form_state['complete'].
- Plugin types now must be explicitly registered via a registration hook,
hook_ctools_plugin_type(); info once provided in magically-named functions
(e.g., ctools_ctools_plugin_content_types() was the old function to
provide plugin type info for ctools' content_type plugins) now must be
provided in that global hook. See http://drupal.org/node/910538 for more
details.
- Plugins that use 'theme arguments' now use 'theme variables' instead.
- Context, argument and relationship plugins now use 'add form' and/or
'edit form' rather than 'settings form'. These plugins now support
form wizards just like content plugins. These forms now all take
$form, &$form_state as arguments, and the configuration for the plugin
can be found in $form_state['conf'].
For all these forms, the submit handler MUST put appropriate data in
$form_state['conf']. Data will no longer be stored automatically.
For all of these forms, the separate settings #trees in the form are now
gone, so form ids may be adjusted. Also, these are now all real forms
using CTools form wizard instead of fake subforms as previously.
@@ -0,0 +1,18 @@
.export-container {
width: 48%;
float: left;
padding: 5px 1% 0;
}
.export-container table {
width: 100%;
}
.export-container table input,
.export-container table th,
.export-container table td {
padding: 0 0 .2em .5em;
margin: 0;
vertical-align: middle;
}
.export-container .select-all {
width: 1.5em;
}
@@ -0,0 +1,14 @@
name = Bulk Export
description = Performs bulk exporting of data objects known about by Chaos tools.
core = 7.x
dependencies[] = ctools
package = Chaos tool suite
version = CTOOLS_MODULE_VERSION
; Information added by Drupal.org packaging script on 2015-01-28
version = "7.x-1.6"
core = "7.x"
project = "ctools"
datestamp = "1422471484"
@@ -0,0 +1,29 @@
/**
* @file
* CTools Bulk Export javascript functions.
*/
(function ($) {
Drupal.behaviors.CToolsBulkExport = {
attach: function (context) {
$('#bulk-export-export-form .vertical-tabs-pane', context).drupalSetSummary(function (context) {
// Check if any individual checkbox is checked.
if ($('.bulk-selection input:checked', context).length > 0) {
return Drupal.t('Exportables selected');
}
return '';
});
// Special bind click on the select-all checkbox.
$('.select-all').bind('click', function(context) {
$(this, '.vertical-tabs-pane').drupalSetSummary(context);
});
}
};
})(jQuery);
@@ -0,0 +1,279 @@
<?php
/**
* @file
* Perform bulk exports.
*/
/**
* Implements hook_permission().
*/
function bulk_export_permission() {
return array(
'use bulk exporter' => array(
'title' => t('Access Bulk Exporter'),
'description' => t('Export various system objects into code.'),
),
);
}
/**
* Implements hook_menu().
*/
function bulk_export_menu() {
$items['admin/structure/bulk-export'] = array(
'title' => 'Bulk Exporter',
'description' => 'Bulk-export multiple CTools-handled data objects to code.',
'access arguments' => array('use bulk exporter'),
'page callback' => 'bulk_export_export',
);
$items['admin/structure/bulk-export/results'] = array(
'access arguments' => array('use bulk exporter'),
'page callback' => 'bulk_export_export',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* FAPI gateway to the bulk exporter.
*
* @param $cli
* Whether this function is called from command line.
* @param $options
* A collection of options, only passed in by drush_ctools_export().
*/
function bulk_export_export($cli = FALSE, $options = array()) {
ctools_include('export');
$form = array();
$schemas = ctools_export_get_schemas(TRUE);
$exportables = $export_tables = array();
foreach ($schemas as $table => $schema) {
if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) {
$exportables[$table] = $schema['export']['list callback']();
}
else {
$exportables[$table] = ctools_export_default_list($table, $schema);
}
natcasesort($exportables[$table]);
$export_tables[$table] = $schema['module'];
}
if ($exportables) {
$form_state = array(
're_render' => FALSE,
'no_redirect' => TRUE,
'exportables' => $exportables,
'export_tables' => $export_tables,
'name' => '',
'code' => '',
'module' => '',
);
// If called from drush_ctools_export, get the module name and
// select all exportables and call the submit function directly.
if ($cli) {
$module_name = $options['name'];
$form_state['values']['name'] = $module_name;
if (isset($options['selections'])) {
$exportables = $options['selections'];
}
$form_state['values']['tables'] = array();
foreach ($exportables as $table => $names) {
if (!empty($names)) {
$form_state['values']['tables'][] = $table;
$form_state['values'][$table] = array();
foreach ($names as $name => $title) {
$form_state['values'][$table][$name] = $name;
}
}
}
$output = bulk_export_export_form_submit($form, $form_state);
}
else {
$output = drupal_build_form('bulk_export_export_form', $form_state);
$module_name = $form_state['module'];
}
if (!empty($form_state['submitted']) || $cli) {
drupal_set_title(t('Bulk export results'));
$output = '';
$module_code = '';
$api_code = array();
$dependencies = $file_data = array();
foreach ($form_state['code'] as $module => $api_info) {
if ($module == 'general') {
$module_code .= $api_info;
}
else {
foreach ($api_info as $api => $info) {
$api_hook = ctools_plugin_api_get_hook($module, $api);
if (empty($api_code[$api_hook])) {
$api_code[$api_hook] = '';
}
$api_code[$api_hook] .= " if (\$module == '$module' && \$api == '$api') {\n";
$api_code[$api_hook] .= " return array('version' => $info[version]);\n";
$api_code[$api_hook] .= " }\n";
$dependencies[$module] = TRUE;
$file = $module_name . '.' . $api . '.inc';
$code = "<?php\n\n";
$code .= "/**\n";
$code .= " * @file\n";
$code .= " * Bulk export of $api objects generated by Bulk export module.\n";
$code .= " */\n\n";
$code .= $info['code'];
if ($cli) {
$file_data[$file] = $code;
}
else {
$export_form = drupal_get_form('ctools_export_form', $code, t('Place this in @file', array('@file' => $file)));
$output .= drupal_render($export_form);
}
}
}
}
// Add hook_ctools_plugin_api at the top of the module code, if there is any.
if ($api_code) {
foreach ($api_code as $api_hook => $text) {
$api = "\n/**\n";
$api .= " * Implements hook_$api_hook().\n";
$api .= " */\n";
$api .= "function {$module_name}_$api_hook(\$module, \$api) {\n";
$api .= $text;
$api .= "}\n";
$module_code = $api . $module_code;
}
}
if ($module_code) {
$module = "<?php\n\n";
$module .= "/**\n";
$module .= " * @file\n";
$module .= " * Bulk export of objects generated by Bulk export module.\n";
$module .= " */\n";
$module .= $module_code;
if ($cli) {
$file_data[$module_name . '.module'] = $module;
}
else {
$export_form = drupal_get_form('ctools_export_form', $module, t('Place this in @file', array('@file' => $form_state['module'] . '.module')));
$output = drupal_render($export_form) . $output;
}
}
$info = strtr("name = @module export module\n", array('@module' => $form_state['module']));
$info .= strtr("description = Export objects from CTools\n", array('@module' => $form_state['values']['name']));
foreach ($dependencies as $module => $junk) {
$info .= "dependencies[] = $module\n";
}
$info .= "package = Chaos tool suite\n";
$info .= "core = 7.x\n";
if ($cli) {
$file_data[$module_name . '.info'] = $info;
}
else {
$export_form = drupal_get_form('ctools_export_form', $info, t('Place this in @file', array('@file' => $form_state['module'] . '.info')));
$output = drupal_render($export_form) . $output;
}
}
if ($cli) {
return $file_data;
}
else {
return $output;
}
}
else {
return t('There are no objects to be exported at this time.');
}
}
/**
* FAPI definition for the bulk exporter form.
*
*/
function bulk_export_export_form($form, &$form_state) {
$files = system_rebuild_module_data();
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
);
$options = $tables = array();
foreach ($form_state['exportables'] as $table => $list) {
if (empty($list)) {
continue;
}
foreach ($list as $id => $title) {
$options[$table][$id] = array($title);
$options[$table][$id]['#attributes'] = array('class' => array('bulk-selection'));
}
$module = $form_state['export_tables'][$table];
$header = array($table);
$module_name = $files[$module]->info['name'];
$tables[] = $table;
if (!isset($form[$module_name])) {
$form[$files[$module]->info['name']] = array(
'#type' => 'fieldset',
'#group' => 'additional_settings',
'#title' => $module_name,
);
}
$form[$module_name]['tables'][$table] = array(
'#prefix' => '<div class="export-container">',
'#suffix' => '</div>',
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options[$table],
);
}
$form['tables'] = array(
'#type' => 'value',
'#value' => $tables,
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Module name'),
'#description' => t('Enter the module name to export code to.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Export'),
);
$form['#action'] = url('admin/structure/bulk-export/results');
$form['#attached']['css'][] = drupal_get_path('module', 'bulk_export') . '/bulk_export.css';
$form['#attached']['js'][] = drupal_get_path('module', 'bulk_export') . '/bulk_export.js';
return $form;
}
/**
* Process the bulk export submit form and make the results available.
*/
function bulk_export_export_form_submit($form, &$form_state) {
$code = array();
$name = empty($form_state['values']['name']) ? 'foo' : $form_state['values']['name'];
$tables = $form_state['values']['tables'];
foreach ($tables as $table) {
$names = array_keys(array_filter($form_state['values'][$table]));
if ($names) {
natcasesort($names);
ctools_export_to_hook_code($code, $table, $names, $name);
}
}
$form_state['code'] = $code;
$form_state['module'] = $name;
}
@@ -0,0 +1,31 @@
.ctools-button-processed {
border-style: solid;
border-width: 1px;
display: inline-block;
line-height: 1;
}
.ctools-button-processed:hover {
cursor: pointer;
}
.ctools-button-processed .ctools-content {
padding-bottom: 2px;
padding-top: 2px;
}
.ctools-no-js .ctools-content ul,
.ctools-button-processed .ctools-content ul {
list-style-image: none;
list-style-type: none;
}
.ctools-button-processed li {
line-height: 1.3333;
}
.ctools-button li a {
padding-left: 12px;
padding-right: 12px;
}
@@ -0,0 +1,26 @@
.ctools-collapsible-container .ctools-toggle {
float: left;
width: 21px;
height: 21px;
cursor: pointer;
background-position: 7px 7px;
background-repeat: no-repeat;
background-image: url(../images/collapsible-expanded.png);
}
.ctools-collapsible-container .ctools-collapsible-handle {
display: none;
}
html.js .ctools-collapsible-container .ctools-collapsible-handle {
display: block;
}
.ctools-collapsible-container .ctools-collapsible-handle {
cursor: pointer;
}
.ctools-collapsible-container .ctools-toggle-collapsed {
background-image: url(../images/collapsible-collapsed.png);
}
@@ -0,0 +1,10 @@
.ctools-context-holder .ctools-context-title {
float: left;
width: 49%;
font-style: italic;
}
.ctools-context-holder .ctools-context-content {
float: right;
width: 49%;
}
@@ -0,0 +1,25 @@
.ctools-locked {
color: red;
border: 1px solid red;
padding: 1em;
}
.ctools-owns-lock {
background: #FFFFDD none repeat scroll 0 0;
border: 1px solid #F0C020;
padding: 1em;
}
a.ctools-ajaxing,
input.ctools-ajaxing,
button.ctools-ajaxing,
select.ctools-ajaxing {
padding-right: 18px !important;
background: url(../images/status-active.gif) right center no-repeat;
}
div.ctools-ajaxing {
float: left;
width: 18px;
background: url(../images/status-active.gif) center center no-repeat;
}
@@ -0,0 +1,66 @@
.ctools-dropbutton-processed {
padding-right: 18px;
position: relative;
background-color: inherit;
}
.ctools-dropbutton-processed.open {
z-index: 200;
}
.ctools-dropbutton-processed .ctools-content li,
.ctools-dropbutton-processed .ctools-content a {
display: block;
}
.ctools-dropbutton-processed .ctools-link {
bottom: 0;
display: block;
height: auto;
position: absolute;
right: 0;
text-indent: -9999px; /* LTR */
top: 0;
width: 17px;
}
.ctools-dropbutton-processed .ctools-link a {
overflow: hidden;
}
.ctools-dropbutton-processed .ctools-content ul {
margin: 0;
overflow: hidden;
}
.ctools-dropbutton-processed.open li + li {
padding-top: 4px;
}
/**
* This creates the dropbutton arrow and inherits the link color
*/
.ctools-twisty {
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
border-style: solid;
border-width: 4px 4px 0;
line-height: 0;
right: 6px;
position: absolute;
top: 0.75em;
}
.ctools-dropbutton-processed.open .ctools-twisty {
border-bottom: 4px solid;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: 0.5em;
}
.ctools-no-js .ctools-twisty {
display: none;
}
@@ -0,0 +1,73 @@
html.js div.ctools-dropdown div.ctools-dropdown-container {
z-index: 1001;
display: none;
text-align: left;
position: absolute;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li a {
display: block;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul {
list-style-type: none;
margin: 0;
padding: 0;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li {
display: block;
/* prevent excess right margin in IE */
margin-right: 0;
margin-left: 0;
padding-right: 0;
padding-left: 0;
background-image: none; /* prevent list backgrounds from mucking things up */
}
.ctools-dropdown-no-js .ctools-dropdown-link,
.ctools-dropdown-no-js span.text {
display: none;
}
/* Everything from here down is purely visual style and can be overridden. */
html.js div.ctools-dropdown a.ctools-dropdown-text-link {
background: url(../images/collapsible-expanded.png) 3px 5px no-repeat;
padding-left: 12px;
}
html.js div.ctools-dropdown div.ctools-dropdown-container {
width: 175px;
background: #fff;
border: 1px solid black;
margin: 4px 1px 0 0;
padding: 0;
color: #494949;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li li a {
padding-left: 25px;
width: 150px;
color: #027AC6;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li a {
text-decoration: none;
padding-left: 5px;
width: 170px;
color: #027AC6;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li span {
display: block;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li span.text {
font-style: italic;
padding-left: 5px;
}
html.js .ctools-dropdown-hover {
background-color: #ECECEC;
}
@@ -0,0 +1,45 @@
body form#ctools-export-ui-list-form {
margin: 0 0 20px 0;
}
#ctools-export-ui-list-form .form-item {
padding-right: 1em; /* LTR */
float: left; /* LTR */
margin-top: 0;
margin-bottom: 0;
}
#ctools-export-ui-list-items {
width: 100%;
}
#edit-order-wrapper {
clear: left; /* LTR */
}
#ctools-export-ui-list-form .form-submit {
margin-top: 1.65em;
float: left; /* LTR */
}
tr.ctools-export-ui-disabled td {
color: #999;
}
th.ctools-export-ui-operations,
td.ctools-export-ui-operations {
text-align: right; /* LTR */
vertical-align: top;
}
/* Force the background color to inherit so that the dropbuttons do not need
a specific background color. */
td.ctools-export-ui-operations {
background-color: inherit;
}
td.ctools-export-ui-operations .ctools-dropbutton {
text-align: left; /* LTR */
position: absolute;
right: 10px;
}
@@ -0,0 +1,130 @@
div.ctools-modal-content {
background: #fff;
color: #000;
padding: 0;
margin: 2px;
border: 1px solid #000;
width: 600px;
text-align: left;
}
div.ctools-modal-content .modal-title {
font-size: 120%;
font-weight: bold;
color: white;
overflow: hidden;
white-space: nowrap;
}
div.ctools-modal-content .modal-header {
background-color: #2385c2;
padding: 0 .25em 0 1em;
}
div.ctools-modal-content .modal-header a {
color: white;
}
div.ctools-modal-content .modal-content {
padding: 1em 1em 0 1em;
overflow: auto;
position: relative; /* Keeps IE7 from flowing outside the modal. */
}
div.ctools-modal-content .modal-form {
}
div.ctools-modal-content a.close {
color: white;
float: right;
}
div.ctools-modal-content a.close:hover {
text-decoration: none;
}
div.ctools-modal-content a.close img {
position: relative;
top: 1px;
}
div.ctools-modal-content .modal-content .modal-throbber-wrapper {
text-align: center;
}
div.ctools-modal-content .modal-content .modal-throbber-wrapper img {
margin-top: 160px;
}
/** modal forms CSS **/
div.ctools-modal-content .form-item label {
width: 15em;
float: left;
}
div.ctools-modal-content .form-item label.option {
width: auto;
float: none;
}
div.ctools-modal-content .form-item .description {
clear: left;
}
div.ctools-modal-content .form-item .description .tips {
margin-left: 2em;
}
div.ctools-modal-content .no-float .form-item * {
float: none;
}
div.ctools-modal-content .modal-form .no-float label {
width: auto;
}
div.ctools-modal-content fieldset,
div.ctools-modal-content .form-radios,
div.ctools-modal-content .form-checkboxes {
clear: left;
}
div.ctools-modal-content .vertical-tabs-panes > fieldset {
clear: none;
}
div.ctools-modal-content .resizable-textarea {
width: auto;
margin-left: 15em;
margin-right: 5em;
}
div.ctools-modal-content .container-inline .form-item {
margin-right: 2em;
}
#views-exposed-pane-wrapper .form-item {
margin-top: 0;
margin-bottom: 0;
}
div.ctools-modal-content label.hidden-options {
background: transparent url(../images/arrow-active.png) no-repeat right;
height: 12px;
padding-right: 12px;
}
div.ctools-modal-content label.expanded-options {
background: transparent url(../images/expanded-options.png) no-repeat right;
height: 12px;
padding-right: 16px;
}
div.ctools-modal-content .option-text-aligner label.expanded-options,
div.ctools-modal-content .option-text-aligner label.hidden-options {
background: none;
}
div.ctools-modal-content .dependent-options {
padding-left: 30px;
}
@@ -0,0 +1,11 @@
.ctools-right-container {
float: right;
padding: 0 0 0 .5em;
margin: 0;
width: 48.5%;
}
.ctools-left-container {
padding-right: .5em;
width: 48.5%;
}
@@ -0,0 +1,129 @@
/* Farbtastic placement */
.color-form {
max-width: 50em;
position: relative;
min-height: 195px;
}
#placeholder {
/*
position: absolute;
top: 0;
right: 0;
*/
margin: 0 auto;
width: 195px;
}
/* Palette */
.color-form .form-item {
height: 2em;
line-height: 2em;
padding-left: 1em; /* LTR */
margin: 0.5em 0;
}
.color-form .form-item input {
margin-top: .2em;
}
.color-form label {
float: left; /* LTR */
clear: left; /* LTR */
width: 14em;
}
.color-form .form-text, .color-form .form-select {
float: left; /* LTR */
}
.color-form .form-text {
text-align: center;
margin-right: 5px; /* LTR */
cursor: pointer;
}
#palette .hook {
float: left; /* LTR */
margin-top: 3px;
width: 16px;
height: 16px;
}
#palette .up {
background-position: 100% -27px; /* LTR */
}
#palette .both {
background-position: 100% -54px; /* LTR */
}
#palette .form-item {
width: 24em;
}
#palette .item-selected {
background: #eee;
}
/* Preview */
#preview {
width: 45%;
float: right;
margin: 0;
}
#ctools_stylizer_color_scheme_form {
float: left;
width: 45%;
margin: 0;
}
/* general style for the layout-icon */
.ctools-style-icon .caption {
width: 100px;
margin-bottom: 1em;
line-height: 1em;
text-align: center;
cursor: default;
}
.ctools-style-icons .form-item {
width: 100px;
float: left;
margin: 0 3px !important;
}
.ctools-style-icons .form-item .ctools-style-icon {
float: none;
height: 150px;
width: 100px;
}
.ctools-style-icons .form-item label.option {
width: 100px;
display: block;
text-align: center;
}
.ctools-style-icons .form-item label.option input {
margin: 0 auto;
}
.ctools-style-icons .ctools-style-category {
height: 190px;
}
.ctools-style-icons .ctools-style-category label {
font-weight: bold;
width: 100%;
float: left;
}
/**
* Stylizer font editor widget
*/
.ctools-stylizer-spacing-form .form-item {
float: left;
margin: .25em;
}
#edit-font-font {
width: 9em;
}
@@ -0,0 +1,8 @@
.wizard-trail {
font-size: 120%;
}
.wizard-trail-current {
font-weight: bold;
}
@@ -0,0 +1,268 @@
<?php
/**
* @file
* Hooks provided by the Chaos Tool Suite.
*
* This file is divided into static hooks (hooks with string literal names) and
* dynamic hooks (hooks with pattern-derived string names).
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Inform CTools about plugin types.
*
* @return array
* An array of plugin types, keyed by the type name.
* See the advanced help topic 'plugins-creating' for details of the array
* properties.
*/
function hook_ctools_plugin_type() {
$plugins['my_type'] = array(
'load themes' => TRUE,
);
return $plugins;
}
/**
* This hook is used to inform the CTools plugin system about the location of a
* directory that should be searched for files containing plugins of a
* particular type. CTools invokes this same hook for all plugins, using the
* two passed parameters to indicate the specific type of plugin for which it
* is searching.
*
* The $plugin_type parameter is self-explanatory - it is the string name of the
* plugin type (e.g., Panels' 'layouts' or 'styles'). The $owner parameter is
* necessary because CTools internally namespaces plugins by the module that
* owns them. This is an extension of Drupal best practices on avoiding global
* namespace pollution by prepending your module name to all its functions.
* Consequently, it is possible for two different modules to create a plugin
* type with exactly the same name and have them operate in harmony. In fact,
* this system renders it impossible for modules to encroach on other modules'
* plugin namespaces.
*
* Given this namespacing, it is important that implementations of this hook
* check BOTH the $owner and $plugin_type parameters before returning a path.
* If your module does not implement plugins for the requested module/plugin
* combination, it is safe to return nothing at all (or NULL). As a convenience,
* it is also safe to return a path that does not exist for plugins your module
* does not implement - see form 2 for a use case.
*
* Note that modules implementing a plugin also must implement this hook to
* instruct CTools as to the location of the plugins. See form 3 for a use case.
*
* The conventional structure to return is "plugins/$plugin_type" - that is, a
* 'plugins' subdirectory in your main module directory, with individual
* directories contained therein named for the plugin type they contain.
*
* @param string $owner
* The system name of the module owning the plugin type for which a base
* directory location is being requested.
* @param string $plugin_type
* The name of the plugin type for which a base directory is being requested.
* @return string
* The path where CTools' plugin system should search for plugin files,
* relative to your module's root. Omit leading and trailing slashes.
*/
function hook_ctools_plugin_directory($owner, $plugin_type) {
// Form 1 - for a module implementing only the 'content_types' plugin owned
// by CTools, this would cause the plugin system to search the
// <moduleroot>/plugins/content_types directory for .inc plugin files.
if ($owner == 'ctools' && $plugin_type == 'content_types') {
return 'plugins/content_types';
}
// Form 2 - if your module implements only Panels plugins, and has 'layouts'
// and 'styles' plugins but no 'cache' or 'display_renderers', it is OK to be
// lazy and return a directory for a plugin you don't actually implement (so
// long as that directory doesn't exist). This lets you avoid ugly in_array()
// logic in your conditional, and also makes it easy to add plugins of those
// types later without having to change this hook implementation.
if ($owner == 'panels') {
return "plugins/$plugin_type";
}
// Form 3 - CTools makes no assumptions about where your plugins are located,
// so you still have to implement this hook even for plugins created by your
// own module.
if ($owner == 'mymodule') {
// Yes, this is exactly like Form 2 - just a different reasoning for it.
return "plugins/$plugin_type";
}
// Finally, if nothing matches, it's safe to return nothing at all (or NULL).
}
/**
* Alter a plugin before it has been processed.
*
* This hook is useful for altering flags or other information that will be
* used or possibly overriden by the process hook if defined.
*
* @param $plugin
* An associative array defining a plugin.
* @param $info
* An associative array of plugin type info.
*/
function hook_ctools_plugin_pre_alter(&$plugin, &$info) {
// Override a function defined by the plugin.
if ($info['type'] == 'my_type') {
$plugin['my_flag'] = 'new_value';
}
}
/**
* Alter a plugin after it has been processed.
*
* This hook is useful for overriding the final values for a plugin after it
* has been processed.
*
* @param $plugin
* An associative array defining a plugin.
* @param $info
* An associative array of plugin type info.
*/
function hook_ctools_plugin_post_alter(&$plugin, &$info) {
// Override a function defined by the plugin.
if ($info['type'] == 'my_type') {
$plugin['my_function'] = 'new_function';
}
}
/**
* Alter the list of modules/themes which implement a certain api.
*
* The hook named here is just an example, as the real existing hooks are named
* for example 'hook_views_api_alter'.
*
* @param array $list
* An array of informations about the implementors of a certain api.
* The key of this array are the module names/theme names.
*/
function hook_ctools_api_hook_alter(&$list) {
// Alter the path of the node implementation.
$list['node']['path'] = drupal_get_path('module', 'node');
}
/**
* Alter the available functions to be used in ctools math expression api.
*
* One usecase would be to create your own function in your module and
* allow to use it in the math expression api.
*
* @param $functions
* An array which has the functions as value.
*/
function hook_ctools_math_expression_functions_alter(&$functions) {
// Allow to convert from degrees to radiant.
$functions[] = 'deg2rad';
}
/**
* Alter everything.
*
* @param $info
* An associative array containing the following keys:
* - content: The rendered content.
* - title: The content's title.
* - no_blocks: A boolean to decide if blocks should be displayed.
* @param $page
* If TRUE then this renderer owns the page and can use theme('page')
* for no blocks; if false, output is returned regardless of any no
* blocks settings.
* @param $context
* An associative array containing the following keys:
* - args: The raw arguments behind the contexts.
* - contexts: The context objects in use.
* - task: The task object in use.
* - subtask: The subtask object in use.
* - handler: The handler object in use.
*/
function hook_ctools_render_alter(&$info, &$page, &$context) {
if ($context['handler']->name == 'my_handler') {
ctools_add_css('my_module.theme', 'my_module');
}
}
/**
* Alter a content plugin subtype.
*
* While content types can be altered via hook_ctools_plugin_pre_alter() or
* hook_ctools_plugin_post_alter(), the subtypes that content types rely on
* are special and require their own hook.
*
* This hook can be used to add things like 'render last' or change icons
* or categories or to rename content on specific sites.
*/
function hook_ctools_content_subtype_alter($subtype, $plugin) {
$subtype['render last'] = TRUE;
}
/**
* Alter the definition of an entity context plugin.
*
* @param array $plugin
* An associative array defining a plugin.
* @param array $entity
* The entity info array of a specific entity type.
* @param string $plugin_id
* The plugin ID, in the format NAME:KEY.
*/
function hook_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
ctools_include('context');
switch ($plugin_id) {
case 'entity_id:taxonomy_term':
$plugin['no ui'] = TRUE;
case 'entity:user':
$plugin = ctools_get_context('user');
unset($plugin['no ui']);
unset($plugin['no required context ui']);
break;
}
}
/**
* Alter the definition of entity context plugins.
*
* @param array $plugins
* An associative array of plugin definitions, keyed by plugin ID.
*
* @see hook_ctools_entity_context_alter()
*/
function hook_ctools_entity_contexts_alter(&$plugins) {
$plugins['entity_id:taxonomy_term']['no ui'] = TRUE;
}
/**
* Change cleanstring settings.
*
* @param array $settings
* An associative array of cleanstring settings.
*
* @see ctools_cleanstring()
*/
function hook_ctools_cleanstring_alter(&$settings) {
// Convert all strings to lower case.
$settings['lower case'] = TRUE;
}
/**
* Change cleanstring settings for a specific clean ID.
*
* @param array $settings
* An associative array of cleanstring settings.
*
* @see ctools_cleanstring()
*/
function hook_ctools_cleanstring_CLEAN_ID_alter(&$settings) {
// Convert all strings to lower case.
$settings['lower case'] = TRUE;
}
/**
* @} End of "addtogroup hooks".
*/
@@ -0,0 +1,17 @@
name = Chaos tools
description = A library of helpful tools by Merlin of Chaos.
core = 7.x
package = Chaos tool suite
version = CTOOLS_MODULE_VERSION
files[] = includes/context.inc
files[] = includes/css-cache.inc
files[] = includes/math-expr.inc
files[] = includes/stylizer.inc
files[] = tests/css_cache.test
; Information added by Drupal.org packaging script on 2015-01-28
version = "7.x-1.6"
core = "7.x"
project = "ctools"
datestamp = "1422471484"
@@ -0,0 +1,265 @@
<?php
/**
* @file
* Contains install and update functions for ctools.
*/
/**
* Use requirements to ensure that the CTools CSS cache directory can be
* created and that the PHP version requirement is met.
*/
function ctools_requirements($phase) {
$requirements = array();
if ($phase == 'runtime') {
$requirements['ctools_css_cache'] = array(
'title' => t('CTools CSS Cache'),
'severity' => REQUIREMENT_OK,
'value' => t('Exists'),
);
$path = 'public://ctools/css';
if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
$requirements['ctools_css_cache']['description'] = t('The CTools CSS cache directory, %path could not be created due to a misconfigured files directory. Please ensure that the files directory is correctly configured and that the webserver has permission to create directories.', array('%path' => file_uri_target($path)));
$requirements['ctools_css_cache']['severity'] = REQUIREMENT_ERROR;
$requirements['ctools_css_cache']['value'] = t('Unable to create');
}
if (!function_exists('error_get_last')) {
$requirements['ctools_php_52']['title'] = t('CTools PHP requirements');
$requirements['ctools_php_52']['description'] = t('CTools requires certain features only available in PHP 5.2.0 or higher.');
$requirements['ctools_php_52']['severity'] = REQUIREMENT_WARNING;
$requirements['ctools_php_52']['value'] = t('PHP !version', array('!version' => phpversion()));
}
}
return $requirements;
}
/**
* Implements hook_schema().
*/
function ctools_schema() {
return ctools_schema_3();
}
/**
* Version 3 of the CTools schema.
*/
function ctools_schema_3() {
$schema = ctools_schema_2();
// update the 'obj' field to be 128 bytes long:
$schema['ctools_object_cache']['fields']['obj']['length'] = 128;
return $schema;
}
/**
* Version 2 of the CTools schema.
*/
function ctools_schema_2() {
$schema = ctools_schema_1();
// update the 'name' field to be 128 bytes long:
$schema['ctools_object_cache']['fields']['name']['length'] = 128;
// Update the 'data' field to be type 'blob'.
$schema['ctools_object_cache']['fields']['data'] = array(
'type' => 'blob',
'size' => 'big',
'description' => 'Serialized data being stored.',
'serialize' => TRUE,
);
// DO NOT MODIFY THIS TABLE -- this definition is used to create the table.
// Changes to this table must be made in schema_3 or higher.
$schema['ctools_css_cache'] = array(
'description' => 'A special cache used to store CSS that must be non-volatile.',
'fields' => array(
'cid' => array(
'type' => 'varchar',
'length' => '128',
'description' => 'The CSS ID this cache object belongs to.',
'not null' => TRUE,
),
'filename' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'The filename this CSS is stored in.',
),
'css' => array(
'type' => 'text',
'size' => 'big',
'description' => 'CSS being stored.',
'serialize' => TRUE,
),
'filter' => array(
'type' => 'int',
'size' => 'tiny',
'description' => 'Whether or not this CSS needs to be filtered.',
),
),
'primary key' => array('cid'),
);
return $schema;
}
/**
* CTools' initial schema; separated for the purposes of updates.
*
* DO NOT MAKE CHANGES HERE. This schema version is locked.
*/
function ctools_schema_1() {
$schema['ctools_object_cache'] = array(
'description' => t('A special cache used to store objects that are being edited; it serves to save state in an ordinarily stateless environment.'),
'fields' => array(
'sid' => array(
'type' => 'varchar',
'length' => '64',
'not null' => TRUE,
'description' => 'The session ID this cache object belongs to.',
),
'name' => array(
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'description' => 'The name of the object this cache is attached to.',
),
'obj' => array(
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.',
),
'updated' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The time this cache was created or updated.',
),
'data' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Serialized data being stored.',
'serialize' => TRUE,
),
),
'primary key' => array('sid', 'obj', 'name'),
'indexes' => array('updated' => array('updated')),
);
return $schema;
}
/**
* Implements hook_install().
*/
function ctools_install() {
// Activate our custom cache handler for the CSS cache.
variable_set('cache_class_cache_ctools_css', 'CToolsCssCache');
}
/**
* Implements hook_uninstall().
*/
function ctools_uninstall() {
variable_del('cache_class_cache_ctools_css');
}
/**
* Enlarge the ctools_object_cache.name column to prevent truncation and weird
* errors.
*/
function ctools_update_6001() {
// Perform updates like this to reduce code duplication.
$schema = ctools_schema_2();
db_change_field('ctools_object_cache', 'name', 'name', $schema['ctools_object_cache']['fields']['name']);
}
/**
* Add the new css cache table.
*/
function ctools_update_6002() {
// Schema 2 is locked and should not be changed.
$schema = ctools_schema_2();
db_create_table('ctools_css_cache', $schema['ctools_css_cache']);
}
/**
* Take over for the panels_views module if it was on.
*/
function ctools_update_6003() {
$result = db_query('SELECT status FROM {system} WHERE name = :name', array(':name' => 'panels_views'))->fetchField();
if ($result) {
db_delete('system')->condition('name', 'panels_views')->execute();
module_enable(array('views_content'), TRUE);
}
}
/**
* Add primary key to the ctools_object_cache table.
*/
function ctools_update_6004() {
db_add_primary_key('ctools_object_cache', array('sid', 'obj', 'name'));
db_drop_index('ctools_object_cache', 'sid_obj_name');
}
/**
* Removed update.
*/
function ctools_update_6005() {
return array();
}
/**
* ctools_custom_content table was originally here, but is now moved to
* its own module.
*/
function ctools_update_6007() {
$ret = array();
if (db_table_exists('ctools_custom_content')) {
// Enable the module to make everything as seamless as possible.
module_enable(array('ctools_custom_content'), TRUE);
}
return $ret;
}
/**
* ctools_object_cache needs to be defined as a blob.
*/
function ctools_update_6008() {
db_delete('ctools_object_cache')
->execute();
db_change_field('ctools_object_cache', 'data', 'data', array(
'type' => 'blob',
'size' => 'big',
'description' => 'Serialized data being stored.',
'serialize' => TRUE,
)
);
}
/**
* Enable the custom CSS cache handler.
*/
function ctools_update_7000() {
variable_set('cache_class_cache_ctools_css', 'CToolsCssCache');
}
/**
* Increase the length of the ctools_object_cache.obj column.
*/
function ctools_update_7001() {
db_change_field('ctools_object_cache', 'obj', 'obj', array(
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.',
));
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
name = Custom rulesets
description = Create custom, exportable, reusable access rulesets for applications like Panels.
core = 7.x
package = Chaos tool suite
version = CTOOLS_MODULE_VERSION
dependencies[] = ctools
; Information added by Drupal.org packaging script on 2015-01-28
version = "7.x-1.6"
core = "7.x"
project = "ctools"
datestamp = "1422471484"
@@ -0,0 +1,82 @@
<?php
/**
* Schema for customizable access rulesets.
*/
function ctools_access_ruleset_schema() {
return ctools_access_ruleset_schema_1();
}
function ctools_access_ruleset_schema_1() {
$schema = array();
$schema['ctools_access_ruleset'] = array(
'description' => 'Contains exportable customized access rulesets.',
'export' => array(
'identifier' => 'ruleset',
'bulk export' => TRUE,
'primary key' => 'rsid',
'api' => array(
'owner' => 'ctools_access_ruleset',
'api' => 'ctools_rulesets',
'minimum_version' => 1,
'current_version' => 1,
),
),
'fields' => array(
'rsid' => array(
'type' => 'serial',
'description' => 'A database primary key to ensure uniqueness',
'not null' => TRUE,
'no export' => TRUE,
),
'name' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Unique ID for this ruleset. Used to identify it programmatically.',
),
'admin_title' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative title for this ruleset.',
),
'admin_description' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Administrative description for this ruleset.',
'object default' => '',
),
'requiredcontexts' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Any required contexts for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
'contexts' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Any embedded contexts for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
'relationships' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Any relationships for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
'access' => array(
'type' => 'text',
'size' => 'big',
'description' => 'The actual group of access plugins for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
),
'primary key' => array('rsid'),
);
return $schema;
}
@@ -0,0 +1,85 @@
<?php
/**
* @file
* ctools_access_ruleset module
*
* This module allows styles to be created and managed on behalf of modules
* that implement styles.
*
* The ctools_access_ruleset tool allows recolorable styles to be created via a miniature
* scripting language. Panels utilizes this to allow administrators to add
* styles directly to any panel display.
*/
/**
* Implementation of hook_permission()
*/
function ctools_access_ruleset_permission() {
return array(
'administer ctools access ruleset' => array(
'title' => t('Administer access rulesets'),
'description' => t('Add, delete and edit custom access rulesets.'),
),
);
}
/**
* Implementation of hook_ctools_plugin_directory() to let the system know
* we implement task and task_handler plugins.
*/
function ctools_access_ruleset_ctools_plugin_directory($module, $plugin) {
// Most of this module is implemented as an export ui plugin, and the
// rest is in ctools/includes/ctools_access_ruleset.inc
if ($module == 'ctools' && ($plugin == 'export_ui' || $plugin == 'access')) {
return 'plugins/' . $plugin;
}
}
/**
* Implementation of hook_panels_dashboard_blocks().
*
* Adds page information to the Panels dashboard.
*/
function ctools_access_ruleset_panels_dashboard_blocks(&$vars) {
$vars['links']['ctools_access_ruleset'] = array(
'title' => l(t('Custom ruleset'), 'admin/structure/ctools-rulesets/add'),
'description' => t('Custom rulesets are combinations of access plugins you can use for access control, selection criteria and pane visibility.'),
);
// Load all mini panels and their displays.
ctools_include('export');
$items = ctools_export_crud_load_all('ctools_access_ruleset');
$count = 0;
$rows = array();
foreach ($items as $item) {
$rows[] = array(
check_plain($item->admin_title),
array(
'data' => l(t('Edit'), "admin/structure/ctools-rulesets/list/$item->name/edit"),
'class' => 'links',
),
);
// Only show 10.
if (++$count >= 10) {
break;
}
}
if ($rows) {
$content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
}
else {
$content = '<p>' . t('There are no custom rulesets.') . '</p>';
}
$vars['blocks']['ctools_access_ruleset'] = array(
'title' => t('Manage custom rulesets'),
'link' => l(t('Go to list'), 'admin/structure/ctools-rulesets'),
'content' => $content,
'class' => 'dashboard-ruleset',
'section' => 'right',
);
}
@@ -0,0 +1,109 @@
<?php
/**
* @file
* Plugin to provide access control based on user rulesetission strings.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => '',
'description' => '',
'callback' => 'ctools_ruleset_ctools_access_check',
'settings form' => 'ctools_ruleset_ctools_access_settings',
'summary' => 'ctools_ruleset_ctools_access_summary',
// This access plugin actually just contains child plugins that are
// exportable, UI configured rulesets.
'get child' => 'ctools_ruleset_ctools_access_get_child',
'get children' => 'ctools_ruleset_ctools_access_get_children',
);
/**
* Merge the main access plugin with a loaded ruleset to form a child plugin.
*/
function ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item) {
$plugin['name'] = $parent . ':' . $item->name;
$plugin['title'] = check_plain($item->admin_title);
$plugin['description'] = check_plain($item->admin_description);
// TODO: Generalize this in CTools.
if (!empty($item->requiredcontexts)) {
$plugin['required context'] = array();
foreach ($item->requiredcontexts as $context) {
$info = ctools_get_context($context['name']);
// TODO: allow an optional setting
$plugin['required context'][] = new ctools_context_required($context['identifier'], $info['context name']);
}
}
// Store the loaded ruleset in the plugin.
$plugin['ruleset'] = $item;
return $plugin;
}
/**
* Get a single child access plugin.
*/
function ctools_ruleset_ctools_access_get_child($plugin, $parent, $child) {
ctools_include('export');
$item = ctools_export_crud_load('ctools_access_ruleset', $child);
if ($item) {
return ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item);
}
}
/**
* Get all child access plugins.
*/
function ctools_ruleset_ctools_access_get_children($plugin, $parent) {
$plugins = array();
ctools_include('export');
$items = ctools_export_crud_load_all('ctools_access_ruleset');
foreach ($items as $name => $item) {
$child = ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item);
$plugins[$child['name']] = $child;
}
return $plugins;
}
/**
* Settings form for the 'by ruleset' access plugin
*/
function ctools_ruleset_ctools_access_settings(&$form, &$form_state, $conf) {
if (!empty($form_state['plugin']['ruleset']->admin_description)) {
$form['markup'] = array(
'#markup' => '<div class="description">' . check_plain($form_state['plugin']['ruleset']->admin_description) . '</div>',
);
}
return $form;
}
/**
* Check for access.
*/
function ctools_ruleset_ctools_access_check($conf, $context, $plugin) {
// Load up any contexts we might be using.
$contexts = ctools_context_match_required_contexts($plugin['ruleset']->requiredcontexts, $context);
$contexts = ctools_context_load_contexts($plugin['ruleset'], FALSE, $contexts);
return ctools_access($plugin['ruleset']->access, $contexts);
}
/**
* Provide a summary description based upon the checked roles.
*/
function ctools_ruleset_ctools_access_summary($conf, $context, $plugin) {
if (!empty($plugin['ruleset']->admin_description)) {
return check_plain($plugin['ruleset']->admin_description);
}
else {
return check_plain($plugin['ruleset']->admin_title);
}
}
@@ -0,0 +1,29 @@
<?php
$plugin = array(
'schema' => 'ctools_access_ruleset',
'access' => 'administer ctools access ruleset',
'menu' => array(
'menu item' => 'ctools-rulesets',
'menu title' => 'Custom access rulesets',
'menu description' => 'Add, edit or delete custom access rulesets for use with Panels and other systems that utilize CTools content plugins.',
),
'title singular' => t('ruleset'),
'title singular proper' => t('Ruleset'),
'title plural' => t('rulesets'),
'title plural proper' => t('Rulesets'),
'handler' => 'ctools_access_ruleset_ui',
'use wizard' => TRUE,
'form info' => array(
'order' => array(
'basic' => t('Basic information'),
'context' => t('Contexts'),
'rules' => t('Rules'),
),
),
);
@@ -0,0 +1,53 @@
<?php
class ctools_access_ruleset_ui extends ctools_export_ui {
function edit_form_context(&$form, &$form_state) {
ctools_include('context-admin');
ctools_context_admin_includes();
ctools_add_css('ruleset');
$form['right'] = array(
'#prefix' => '<div class="ctools-right-container">',
'#suffix' => '</div>',
);
$form['left'] = array(
'#prefix' => '<div class="ctools-left-container clearfix">',
'#suffix' => '</div>',
);
// Set this up and we can use CTools' Export UI's built in wizard caching,
// which already has callbacks for the context cache under this name.
$module = 'export_ui::' . $this->plugin['name'];
$name = $this->edit_cache_get_key($form_state['item'], $form_state['form type']);
ctools_context_add_context_form($module, $form, $form_state, $form['right']['contexts_table'], $form_state['item'], $name);
ctools_context_add_required_context_form($module, $form, $form_state, $form['left']['required_contexts_table'], $form_state['item'], $name);
ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $form_state['item'], $name);
}
function edit_form_rules(&$form, &$form_state) {
// The 'access' UI passes everything via $form_state, unlike the 'context' UI.
// The main difference is that one is about 3 years newer than the other.
ctools_include('context');
ctools_include('context-access-admin');
$form_state['access'] = $form_state['item']->access;
$form_state['contexts'] = ctools_context_load_contexts($form_state['item']);
$form_state['module'] = 'ctools_export_ui';
$form_state['callback argument'] = $form_state['object']->plugin['name'] . ':' . $form_state['object']->edit_cache_get_key($form_state['item'], $form_state['form type']);
$form_state['no buttons'] = TRUE;
$form = ctools_access_admin_form($form, $form_state);
}
function edit_form_rules_submit(&$form, &$form_state) {
$form_state['item']->access['logic'] = $form_state['values']['logic'];
}
function edit_form_submit(&$form, &$form_state) {
parent::edit_form_submit($form, $form_state);
}
}
@@ -0,0 +1,134 @@
div.ctools-sample-modal-content {
background:none;
border:0;
color:#000000;
margin:0;
padding:0;
text-align:left;
}
div.ctools-sample-modal-content .modal-scroll{
overflow:hidden;
overflow-y:auto;
}
div.ctools-sample-modal-content #popups-overlay {
background-color:transparent;
}
div.ctools-sample-modal-content #popups-loading {
width:248px;
position:absolute;
display:none;
opacity:1;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
z-index:99;
}
div.ctools-sample-modal-content #popups-loading span.popups-loading-message {
background:#FFF url(../images/loading-large.gif) no-repeat 8px center;
display:block;
color:#444444;
font-family:Arial;
font-size:22px;
font-weight:bold;
height:36px;
line-height:36px;
padding:0 40px;
}
div.ctools-sample-modal-content #popups-loading table,
div.ctools-sample-modal-content .popups-box table {
margin:0px;
}
div.ctools-sample-modal-content #popups-loading tbody,
div.ctools-sample-modal-content .popups-box tbody {
border:none;
}
div.ctools-sample-modal-content .popups-box tr {
background-color:transparent;
}
div.ctools-sample-modal-content td.popups-border {
background: url(../images/popups-border.png);
background-color:transparent;
border: none;
}
div.ctools-sample-modal-content td.popups-tl,
div.ctools-sample-modal-content td.popups-tr,
div.ctools-sample-modal-content td.popups-bl,
div.ctools-sample-modal-content td.popups-br {
background-repeat: no-repeat;
height:10px;
padding:0px;
}
div.ctools-sample-modal-content td.popups-tl { background-position: 0px 0px; }
div.ctools-sample-modal-content td.popups-t,
div.ctools-sample-modal-content td.popups-b {
background-position: 0px -40px;
background-repeat: repeat-x;
}
div.ctools-sample-modal-content td.popups-tr { background-position: 0px -10px; width: 10px; }
div.ctools-sample-modal-content td.popups-cl,
div.ctools-sample-modal-content td.popups-cr {
background-position: -10px 0;
background-repeat: repeat-y;
width:10px;
}
div.ctools-sample-modal-content td.popups-cl,
div.ctools-sample-modal-content td.popups-cr,
div.ctools-sample-modal-content td.popups-c { padding:0; border: none; }
div.ctools-sample-modal-content td.popups-c { background:#fff; }
div.ctools-sample-modal-content td.popups-bl { background-position: 0px -20px; }
div.ctools-sample-modal-content td.popups-br { background-position: 0px -30px; width: 10px; }
div.ctools-sample-modal-content .popups-box,
div.ctools-sample-modal-content #popups-loading {
border: 0px solid #454545;
opacity:1;
overflow:hidden;
padding:0;
background-color:transparent;
}
div.ctools-sample-modal-content .popups-container {
overflow:hidden;
height:100%;
background-color:#fff;
}
div.ctools-sample-modal-content div.popups-title {
-moz-border-radius-topleft: 0px;
-webkit-border-radius-topleft: 0px;
margin-bottom:0px;
background-color:#ff7200;
border:1px solid #ce5c00;
padding:4px 10px 5px;
color:white;
font-size:1em;
font-weight:bold;
}
div.ctools-sample-modal-content .popups-body {
background-color:#fff;
padding:8px;
}
div.ctools-sample-modal-content .popups-box .popups-buttons,
div.ctools-sample-modal-content .popups-box .popups-footer {
background-color:#fff;
}
div.ctools-sample-modal-content .popups-title a.close {
color: #fff;
text-decoration:none;
}
div.ctools-sample-modal-content .popups-close {
font-size:120%;
float:right;
text-align:right;
}
div.ctools-sample-modal-content .modal-loading-wrapper {
width:220px;
height:19px;
margin:0 auto;
margin-top:2%;
}
div.ctools-sample-modal-content tbody{
border:none;
}
div.ctools-sample-modal-content .modal-content .modal-throbber-wrapper img {
margin-top: 100px;
}
@@ -0,0 +1,13 @@
name = Chaos Tools (CTools) AJAX Example
description = Shows how to use the power of Chaos AJAX.
package = Chaos tool suite
version = CTOOLS_MODULE_VERSION
dependencies[] = ctools
core = 7.x
; Information added by Drupal.org packaging script on 2015-01-28
version = "7.x-1.6"
core = "7.x"
project = "ctools"
datestamp = "1422471484"
@@ -0,0 +1,19 @@
<?php
/**
* @file
*/
/**
* Implementation of hook_install()
*/
function ctools_ajax_sample_install() {
}
/**
* Implementation of hook_uninstall()
*/
function ctools_ajax_sample_uninstall() {
}
@@ -0,0 +1,756 @@
<?php
/**
* @file
* Sample AJAX functionality so people can see some of the CTools AJAX
* features in use.
*/
// ---------------------------------------------------------------------------
// Drupal hooks.
/**
* Implementation of hook_menu()
*/
function ctools_ajax_sample_menu() {
$items['ctools_ajax_sample'] = array(
'title' => 'Chaos Tools AJAX Demo',
'page callback' => 'ctools_ajax_sample_page',
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
);
$items['ctools_ajax_sample/simple_form'] = array(
'title' => 'Simple Form',
'page callback' => 'ctools_ajax_simple_form',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/hello'] = array(
'title' => 'Hello World',
'page callback' => 'ctools_ajax_sample_hello',
'page arguments' => array(1),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/tablenix/%'] = array(
'title' => 'Hello World',
'page callback' => 'ctools_ajax_sample_tablenix',
'page arguments' => array(1, 3),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/login'] = array(
'title' => 'Login',
'page callback' => 'ctools_ajax_sample_login',
'page arguments' => array(1),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/animal'] = array(
'title' => 'Animal',
'page callback' => 'ctools_ajax_sample_animal',
'page arguments' => array(1),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/login/%'] = array(
'title' => 'Post-Login Action',
'page callback' => 'ctools_ajax_sample_login_success',
'page arguments' => array(1, 3),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/jumped'] = array(
'title' => 'Successful Jumping',
'page callback' => 'ctools_ajax_sample_jump_menu_page',
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
function ctools_ajax_simple_form() {
ctools_include('content');
ctools_include('context');
$node = node_load(1);
$context = ctools_context_create('node', $node);
$context = array('context_node_1' => $context);
return ctools_content_render('node_comment_form', 'node_comment_form', ctools_ajax_simple_form_pane(), array(), array(), $context);
}
function ctools_ajax_simple_form_pane() {
$configuration = array(
'anon_links' => 0,
'context' => 'context_node_1',
'override_title' => 0,
'override_title_text' => '',
);
return $configuration;
}
/**
* Implementation of hook_theme()
*
* Render some basic output for this module.
*/
function ctools_ajax_sample_theme() {
return array(
// Sample theme functions.
'ctools_ajax_sample_container' => array(
'arguments' => array('content' => NULL),
),
);
}
// ---------------------------------------------------------------------------
// Page callbacks
/**
* Page callback to display links and render a container for AJAX stuff.
*/
function ctools_ajax_sample_page() {
global $user;
// Include the CTools tools that we need.
ctools_include('ajax');
ctools_include('modal');
// Add CTools' javascript to the page.
ctools_modal_add_js();
// Create our own javascript that will be used to theme a modal.
$sample_style = array(
'ctools-sample-style' => array(
'modalSize' => array(
'type' => 'fixed',
'width' => 500,
'height' => 300,
'addWidth' => 20,
'addHeight' => 15,
),
'modalOptions' => array(
'opacity' => .5,
'background-color' => '#000',
),
'animation' => 'fadeIn',
'modalTheme' => 'CToolsSampleModal',
'throbber' => theme('image', array('path' => ctools_image_path('ajax-loader.gif', 'ctools_ajax_sample'), 'alt' => t('Loading...'), 'title' => t('Loading'))),
),
);
drupal_add_js($sample_style, 'setting');
// Since we have our js, css and images in well-known named directories,
// CTools makes it easy for us to just use them without worrying about
// using drupal_get_path() and all that ugliness.
ctools_add_js('ctools-ajax-sample', 'ctools_ajax_sample');
ctools_add_css('ctools-ajax-sample', 'ctools_ajax_sample');
// Create a list of clickable links.
$links = array();
// Only show login links to the anonymous user.
if ($user->uid == 0) {
$links[] = ctools_modal_text_button(t('Modal Login (default style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'));
// The extra class points to the info in ctools-sample-style which we added
// to the settings, prefixed with 'ctools-modal'.
$links[] = ctools_modal_text_button(t('Modal Login (custom style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'), 'ctools-modal-ctools-sample-style');
}
// Four ways to do our animal picking wizard.
$button_form = ctools_ajax_sample_ajax_button_form();
$links[] = l(t('Wizard (no modal)'), 'ctools_ajax_sample/nojs/animal');
$links[] = ctools_modal_text_button(t('Wizard (default modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'));
$links[] = ctools_modal_text_button(t('Wizard (custom modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'), 'ctools-modal-ctools-sample-style');
$links[] = drupal_render($button_form);
$links[] = ctools_ajax_text_button(t('Hello world!'), "ctools_ajax_sample/nojs/hello", t('Replace text with "hello world"'));
$output = theme('item_list', array('items' => $links, 'title' => t('Actions')));
// This container will have data AJAXed into it.
$output .= theme('ctools_ajax_sample_container', array('content' => '<h1>' . t('Sample Content') . '</h1>'));
// Create a table that we can have data removed from via AJAX.
$header = array(t('Row'), t('Content'), t('Actions'));
$rows = array();
for($i = 1; $i < 11; $i++) {
$rows[] = array(
'class' => array('ajax-sample-row-'. $i),
'data' => array(
$i,
md5($i),
ctools_ajax_text_button("remove", "ctools_ajax_sample/nojs/tablenix/$i", t('Delete this row')),
),
);
}
$output .= theme('table', array('header' => $header, 'rows' => $rows, array('class' => array('ajax-sample-table'))));
// Show examples of ctools javascript widgets
$output .= '<h2>'. t('CTools Javascript Widgets') .'</h2>';
// Create a drop down menu
$links = array();
$links[] = array('title' => t('Link 1'), 'href' => $_GET['q']);
$links[] = array('title' => t('Link 2'), 'href' => $_GET['q']);
$links[] = array('title' => t('Link 3'), 'href' => $_GET['q']);
$output .= '<h3>' . t('Drop Down Menu') . '</h3>';
$output .= theme('ctools_dropdown', array('title' => t('Click to Drop Down'), 'links' => $links));
// Create a collapsible div
$handle = t('Click to Collapse');
$content = 'Nulla ligula ante, aliquam at adipiscing egestas, varius vel arcu. Etiam laoreet elementum mi vel consequat. Etiam scelerisque lorem vel neque consequat quis bibendum libero congue. Nulla facilisi. Mauris a elit a leo feugiat porta. Phasellus placerat cursus est vitae elementum.';
$output .= '<h3>'. t('Collapsible Div') .'</h3>';
$output .= theme('ctools_collapsible', array('handle' => $handle, 'content' => $content, 'collapsed' => FALSE));
// Create a jump menu
ctools_include('jump-menu');
$form = drupal_get_form('ctools_ajax_sample_jump_menu_form');
$output .= '<h3>'. t('Jump Menu') .'</h3>';
$output .= drupal_render($form);
return array('markup' => array('#markup' => $output));
}
/**
* Returns a "take it all over" hello world style request.
*/
function ctools_ajax_sample_hello($js = NULL) {
$output = '<h1>' . t('Hello World') . '</h1>';
if ($js) {
ctools_include('ajax');
$commands = array();
$commands[] = ajax_command_html('#ctools-sample', $output);
print ajax_render($commands); // this function exits.
exit;
}
else {
return $output;
}
}
/**
* Nix a row from a table and restripe.
*/
function ctools_ajax_sample_tablenix($js, $row) {
if (!$js) {
// We don't support degrading this from js because we're not
// using the server to remember the state of the table.
return MENU_ACCESS_DENIED;
}
ctools_include('ajax');
$commands = array();
$commands[] = ajax_command_remove("tr.ajax-sample-row-$row");
$commands[] = ajax_command_restripe("table.ajax-sample-table");
print ajax_render($commands);
exit;
}
/**
* A modal login callback.
*/
function ctools_ajax_sample_login($js = NULL) {
// Fall back if $js is not set.
if (!$js) {
return drupal_get_form('user_login');
}
ctools_include('modal');
ctools_include('ajax');
$form_state = array(
'title' => t('Login'),
'ajax' => TRUE,
);
$output = ctools_modal_form_wrapper('user_login', $form_state);
if (!empty($form_state['executed'])) {
// We'll just overwrite the form output if it was successful.
$output = array();
$inplace = ctools_ajax_text_button(t('remain here'), 'ctools_ajax_sample/nojs/login/inplace', t('Go to your account'));
$account = ctools_ajax_text_button(t('your account'), 'ctools_ajax_sample/nojs/login/user', t('Go to your account'));
$output[] = ctools_modal_command_display(t('Login Success'), '<div class="modal-message">Login successful. You can now choose whether to '. $inplace .', or go to '. $account.'.</div>');
}
print ajax_render($output);
exit;
}
/**
* Post-login processor: should we go to the user account or stay in place?
*/
function ctools_ajax_sample_login_success($js, $action) {
if (!$js) {
// we should never be here out of ajax context
return MENU_NOT_FOUND;
}
ctools_include('ajax');
ctools_add_js('ajax-responder');
$commands = array();
if ($action == 'inplace') {
// stay here
$commands[] = ctools_ajax_command_reload();
}
else {
// bounce bounce
$commands[] = ctools_ajax_command_redirect('user');
}
print ajax_render($commands);
exit;
}
/**
* A modal login callback.
*/
function ctools_ajax_sample_animal($js = NULL, $step = NULL) {
if ($js) {
ctools_include('modal');
ctools_include('ajax');
}
$form_info = array(
'id' => 'animals',
'path' => "ctools_ajax_sample/" . ($js ? 'ajax' : 'nojs') . "/animal/%step",
'show trail' => TRUE,
'show back' => TRUE,
'show cancel' => TRUE,
'show return' => FALSE,
'next callback' => 'ctools_ajax_sample_wizard_next',
'finish callback' => 'ctools_ajax_sample_wizard_finish',
'cancel callback' => 'ctools_ajax_sample_wizard_cancel',
// this controls order, as well as form labels
'order' => array(
'start' => t('Choose animal'),
),
// here we map a step to a form id.
'forms' => array(
// e.g. this for the step at wombat/create
'start' => array(
'form id' => 'ctools_ajax_sample_start'
),
),
);
// We're not using any real storage here, so we're going to set our
// object_id to 1. When using wizard forms, id management turns
// out to be one of the hardest parts. Editing an object with an id
// is easy, but new objects don't usually have ids until somewhere
// in creation.
//
// We skip all this here by just using an id of 1.
$object_id = 1;
if (empty($step)) {
// We reset the form when $step is NULL because that means they have
// for whatever reason started over.
ctools_ajax_sample_cache_clear($object_id);
$step = 'start';
}
// This automatically gets defaults if there wasn't anything saved.
$object = ctools_ajax_sample_cache_get($object_id);
$animals = ctools_ajax_sample_animals();
// Make sure we can't somehow accidentally go to an invalid animal.
if (empty($animals[$object->type])) {
$object->type = 'unknown';
}
// Now that we have our object, dynamically add the animal's form.
if ($object->type == 'unknown') {
// If they haven't selected a type, add a form that doesn't exist yet.
$form_info['order']['unknown'] = t('Configure animal');
$form_info['forms']['unknown'] = array('form id' => 'nothing');
}
else {
// Add the selected animal to the order so that it shows up properly in the trail.
$form_info['order'][$object->type] = $animals[$object->type]['config title'];
}
// Make sure all animals forms are represented so that the next stuff can
// work correctly:
foreach ($animals as $id => $animal) {
$form_info['forms'][$id] = array('form id' => $animals[$id]['form']);
}
$form_state = array(
'ajax' => $js,
// Put our object and ID into the form state cache so we can easily find
// it.
'object_id' => $object_id,
'object' => &$object,
);
// Send this all off to our form. This is like drupal_get_form only wizardy.
ctools_include('wizard');
$form = ctools_wizard_multistep_form($form_info, $step, $form_state);
$output = drupal_render($form);
if ($output === FALSE || !empty($form_state['complete'])) {
// This creates a string based upon the animal and its setting using
// function indirection.
$animal = $animals[$object->type]['output']($object);
}
// If $output is FALSE, there was no actual form.
if ($js) {
// If javascript is active, we have to use a render array.
$commands = array();
if ($output === FALSE || !empty($form_state['complete'])) {
// Dismiss the modal.
$commands[] = ajax_command_html('#ctools-sample', $animal);
$commands[] = ctools_modal_command_dismiss();
}
else if (!empty($form_state['cancel'])) {
// If cancelling, return to the activity.
$commands[] = ctools_modal_command_dismiss();
}
else {
$commands = ctools_modal_form_render($form_state, $output);
}
print ajax_render($commands);
exit;
}
else {
if ($output === FALSE || !empty($form_state['complete'])) {
return $animal;
}
else if (!empty($form_state['cancel'])) {
drupal_goto('ctools_ajax_sample');
}
else {
return $output;
}
}
}
// ---------------------------------------------------------------------------
// Themes
/**
* Theme function for main rendered output.
*/
function theme_ctools_ajax_sample_container($vars) {
$output = '<div id="ctools-sample">';
$output .= $vars['content'];
$output .= '</div>';
return $output;
}
// ---------------------------------------------------------------------------
// Stuff needed for our little wizard.
/**
* Get a list of our animals and associated forms.
*
* What we're doing is making it easy to add more animals in just one place,
* which is often how it will work in the real world. If using CTools, what
* you would probably really have, here, is a set of plugins for each animal.
*/
function ctools_ajax_sample_animals() {
return array(
'sheep' => array(
'title' => t('Sheep'),
'config title' => t('Configure sheep'),
'form' => 'ctools_ajax_sample_configure_sheep',
'output' => 'ctools_ajax_sample_show_sheep',
),
'lizard' => array(
'title' => t('Lizard'),
'config title' => t('Configure lizard'),
'form' => 'ctools_ajax_sample_configure_lizard',
'output' => 'ctools_ajax_sample_show_lizard',
),
'raptor' => array(
'title' => t('Raptor'),
'config title' => t('Configure raptor'),
'form' => 'ctools_ajax_sample_configure_raptor',
'output' => 'ctools_ajax_sample_show_raptor',
),
);
}
// ---------------------------------------------------------------------------
// Wizard caching helpers.
/**
* Store our little cache so that we can retain data from form to form.
*/
function ctools_ajax_sample_cache_set($id, $object) {
ctools_include('object-cache');
ctools_object_cache_set('ctools_ajax_sample', $id, $object);
}
/**
* Get the current object from the cache, or default.
*/
function ctools_ajax_sample_cache_get($id) {
ctools_include('object-cache');
$object = ctools_object_cache_get('ctools_ajax_sample', $id);
if (!$object) {
// Create a default object.
$object = new stdClass;
$object->type = 'unknown';
$object->name = '';
}
return $object;
}
/**
* Clear the wizard cache.
*/
function ctools_ajax_sample_cache_clear($id) {
ctools_include('object-cache');
ctools_object_cache_clear('ctools_ajax_sample', $id);
}
// ---------------------------------------------------------------------------
// Wizard in-between helpers; what to do between or after forms.
/**
* Handle the 'next' click on the add/edit pane form wizard.
*
* All we need to do is store the updated pane in the cache.
*/
function ctools_ajax_sample_wizard_next(&$form_state) {
ctools_ajax_sample_cache_set($form_state['object_id'], $form_state['object']);
}
/**
* Handle the 'finish' click on teh add/edit pane form wizard.
*
* All we need to do is set a flag so the return can handle adding
* the pane.
*/
function ctools_ajax_sample_wizard_finish(&$form_state) {
$form_state['complete'] = TRUE;
}
/**
* Handle the 'cancel' click on the add/edit pane form wizard.
*/
function ctools_ajax_sample_wizard_cancel(&$form_state) {
$form_state['cancel'] = TRUE;
}
// ---------------------------------------------------------------------------
// Wizard forms for our simple info collection wizard.
/**
* Wizard start form. Choose an animal.
*/
function ctools_ajax_sample_start($form, &$form_state) {
$form_state['title'] = t('Choose animal');
$animals = ctools_ajax_sample_animals();
foreach ($animals as $id => $animal) {
$options[$id] = $animal['title'];
}
$form['type'] = array(
'#title' => t('Choose your animal'),
'#type' => 'radios',
'#options' => $options,
'#default_value' => $form_state['object']->type,
'#required' => TRUE,
);
return $form;
}
/**
* They have selected a sheep. Set it.
*/
function ctools_ajax_sample_start_submit(&$form, &$form_state) {
$form_state['object']->type = $form_state['values']['type'];
// Override where to go next based on the animal selected.
$form_state['clicked_button']['#next'] = $form_state['values']['type'];
}
/**
* Wizard form to configure your sheep.
*/
function ctools_ajax_sample_configure_sheep($form, &$form_state) {
$form_state['title'] = t('Configure sheep');
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name your sheep'),
'#default_value' => $form_state['object']->name,
'#required' => TRUE,
);
$form['sheep'] = array(
'#title' => t('What kind of sheep'),
'#type' => 'radios',
'#options' => array(
t('Wensleydale') => t('Wensleydale'),
t('Merino') => t('Merino'),
t('Corriedale') => t('Coriedale'),
),
'#default_value' => !empty($form_state['object']->sheep) ? $form_state['object']->sheep : '',
'#required' => TRUE,
);
return $form;
}
/**
* Submit the sheep and store the values from the form.
*/
function ctools_ajax_sample_configure_sheep_submit(&$form, &$form_state) {
$form_state['object']->name = $form_state['values']['name'];
$form_state['object']->sheep = $form_state['values']['sheep'];
}
/**
* Provide some output for our sheep.
*/
function ctools_ajax_sample_show_sheep($object) {
return t('You have a @type sheep named "@name".', array(
'@type' => $object->sheep,
'@name' => $object->name,
));
}
/**
* Wizard form to configure your lizard.
*/
function ctools_ajax_sample_configure_lizard($form, &$form_state) {
$form_state['title'] = t('Configure lizard');
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name your lizard'),
'#default_value' => $form_state['object']->name,
'#required' => TRUE,
);
$form['lizard'] = array(
'#title' => t('Venomous'),
'#type' => 'checkbox',
'#default_value' => !empty($form_state['object']->lizard),
);
return $form;
}
/**
* Submit the lizard and store the values from the form.
*/
function ctools_ajax_sample_configure_lizard_submit(&$form, &$form_state) {
$form_state['object']->name = $form_state['values']['name'];
$form_state['object']->lizard = $form_state['values']['lizard'];
}
/**
* Provide some output for our raptor.
*/
function ctools_ajax_sample_show_lizard($object) {
return t('You have a @type lizard named "@name".', array(
'@type' => empty($object->lizard) ? t('non-venomous') : t('venomous'),
'@name' => $object->name,
));
}
/**
* Wizard form to configure your raptor.
*/
function ctools_ajax_sample_configure_raptor($form, &$form_state) {
$form_state['title'] = t('Configure raptor');
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name your raptor'),
'#default_value' => $form_state['object']->name,
'#required' => TRUE,
);
$form['raptor'] = array(
'#title' => t('What kind of raptor'),
'#type' => 'radios',
'#options' => array(
t('Eagle') => t('Eagle'),
t('Hawk') => t('Hawk'),
t('Owl') => t('Owl'),
t('Buzzard') => t('Buzzard'),
),
'#default_value' => !empty($form_state['object']->raptor) ? $form_state['object']->raptor : '',
'#required' => TRUE,
);
$form['domesticated'] = array(
'#title' => t('Domesticated'),
'#type' => 'checkbox',
'#default_value' => !empty($form_state['object']->domesticated),
);
return $form;
}
/**
* Submit the raptor and store the values from the form.
*/
function ctools_ajax_sample_configure_raptor_submit(&$form, &$form_state) {
$form_state['object']->name = $form_state['values']['name'];
$form_state['object']->raptor = $form_state['values']['raptor'];
$form_state['object']->domesticated = $form_state['values']['domesticated'];
}
/**
* Provide some output for our raptor.
*/
function ctools_ajax_sample_show_raptor($object) {
return t('You have a @type @raptor named "@name".', array(
'@type' => empty($object->domesticated) ? t('wild') : t('domesticated'),
'@raptor' => $object->raptor,
'@name' => $object->name,
));
}
/**
* Helper function to provide a sample jump menu form
*/
function ctools_ajax_sample_jump_menu_form() {
$url = url('ctools_ajax_sample/jumped');
$form_state = array();
$form = ctools_jump_menu(array(), $form_state, array($url => t('Jump!')), array());
return $form;
}
/**
* Provide a message to the user that the jump menu worked
*/
function ctools_ajax_sample_jump_menu_page() {
$return_link = l(t('Return to the examples page.'), 'ctools_ajax_sample');
$output = t('You successfully jumped! !return_link', array('!return_link' => $return_link));
return $output;
}
/**
* Provide a form for an example ajax modal button
*/
function ctools_ajax_sample_ajax_button_form() {
$form = array();
$form['url'] = array(
'#type' => 'hidden',
// The name of the class is the #id of $form['ajax_button'] with "-url"
// suffix.
'#attributes' => array('class' => array('ctools-ajax-sample-button-url')),
'#value' => url('ctools_ajax_sample/nojs/animal'),
);
$form['ajax_button'] = array(
'#type' => 'button',
'#value' => 'Wizard (button modal)',
'#attributes' => array('class' => array('ctools-use-modal')),
'#id' => 'ctools-ajax-sample-button',
);
return $form;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

@@ -0,0 +1,42 @@
/**
* Provide the HTML to create the modal dialog.
*/
Drupal.theme.prototype.CToolsSampleModal = function () {
var html = '';
html += '<div id="ctools-modal" class="popups-box">';
html += ' <div class="ctools-modal-content ctools-sample-modal-content">';
html += ' <table cellpadding="0" cellspacing="0" id="ctools-face-table">';
html += ' <tr>';
html += ' <td class="popups-tl popups-border"></td>';
html += ' <td class="popups-t popups-border"></td>';
html += ' <td class="popups-tr popups-border"></td>';
html += ' </tr>';
html += ' <tr>';
html += ' <td class="popups-cl popups-border"></td>';
html += ' <td class="popups-c" valign="top">';
html += ' <div class="popups-container">';
html += ' <div class="modal-header popups-title">';
html += ' <span id="modal-title" class="modal-title"></span>';
html += ' <span class="popups-close"><a class="close" href="#">' + Drupal.CTools.Modal.currentSettings.closeText + '</a></span>';
html += ' <div class="clear-block"></div>';
html += ' </div>';
html += ' <div class="modal-scroll"><div id="modal-content" class="modal-content popups-body"></div></div>';
html += ' <div class="popups-buttons"></div>'; //Maybe someday add the option for some specific buttons.
html += ' <div class="popups-footer"></div>'; //Maybe someday add some footer.
html += ' </div>';
html += ' </td>';
html += ' <td class="popups-cr popups-border"></td>';
html += ' </tr>';
html += ' <tr>';
html += ' <td class="popups-bl popups-border"></td>';
html += ' <td class="popups-b popups-border"></td>';
html += ' <td class="popups-br popups-border"></td>';
html += ' </tr>';
html += ' </table>';
html += ' </div>';
html += '</div>';
return html;
}
@@ -0,0 +1,13 @@
name = Custom content panes
description = Create custom, exportable, reusable content panes for applications like Panels.
core = 7.x
package = Chaos tool suite
version = CTOOLS_MODULE_VERSION
dependencies[] = ctools
; Information added by Drupal.org packaging script on 2015-01-28
version = "7.x-1.6"
core = "7.x"
project = "ctools"
datestamp = "1422471484"
@@ -0,0 +1,67 @@
<?php
/**
* Schema for CTools custom content.
*/
function ctools_custom_content_schema() {
return ctools_custom_content_schema_1();
}
function ctools_custom_content_schema_1() {
$schema = array();
$schema['ctools_custom_content'] = array(
'description' => 'Contains exportable customized content for this site.',
'export' => array(
'identifier' => 'content',
'bulk export' => TRUE,
'primary key' => 'cid',
'api' => array(
'owner' => 'ctools_custom_content',
'api' => 'ctools_content',
'minimum_version' => 1,
'current_version' => 1,
),
'create callback' => 'ctools_content_type_new',
),
'fields' => array(
'cid' => array(
'type' => 'serial',
'description' => 'A database primary key to ensure uniqueness',
'not null' => TRUE,
'no export' => TRUE,
),
'name' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Unique ID for this content. Used to identify it programmatically.',
),
'admin_title' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative title for this content.',
),
'admin_description' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Administrative description for this content.',
'object default' => '',
),
'category' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative category for this content.',
),
'settings' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Serialized settings for the actual content to be used',
'serialize' => TRUE,
'object default' => array(),
),
),
'primary key' => array('cid'),
);
return $schema;
}
@@ -0,0 +1,118 @@
<?php
/**
* @file
* ctools_custom_content module
*
* This module allows styles to be created and managed on behalf of modules
* that implement styles.
*
* The ctools_custom_content tool allows recolorable styles to be created via a miniature
* scripting language. Panels utilizes this to allow administrators to add
* styles directly to any panel display.
*/
/**
* Implementation of hook_permission()
*/
function ctools_custom_content_permission() {
return array(
'administer custom content' => array(
'title' => t('Administer custom content'),
'description' => t('Add, edit and delete CTools custom stored custom content'),
),
);
}
/**
* Implementation of hook_ctools_plugin_directory() to let the system know
* we implement task and task_handler plugins.
*/
function ctools_custom_content_ctools_plugin_directory($module, $plugin) {
// Most of this module is implemented as an export ui plugin, and the
// rest is in ctools/includes/ctools_custom_content.inc
if ($module == 'ctools' && $plugin == 'export_ui') {
return 'plugins/' . $plugin;
}
}
/**
* Implements hook_get_pane_links_alter().
*/
function ctools_custom_content_get_pane_links_alter(&$links, $pane, $content_type) {
if ($pane->type == 'custom') {
if(!isset($pane->configuration['name'])) {
$name_of_pane = $pane->subtype;
}
else {
$name_of_pane = $pane->configuration['name'];
}
$links['top']['edit_custom_content'] = array(
'title' => t('Edit custom content pane'),
'href' => url('admin/structure/ctools-content/list/' . $name_of_pane . '/edit', array('absolute' => TRUE)),
'attributes' => array('target' => array('_blank')),
);
}
}
/**
* Create callback for creating a new CTools custom content type.
*
* This ensures we get proper defaults from the plugin for its settings.
*/
function ctools_content_type_new($set_defaults) {
$item = ctools_export_new_object('ctools_custom_content', $set_defaults);
ctools_include('content');
$plugin = ctools_get_content_type('custom');
$item->settings = ctools_content_get_defaults($plugin, array());
return $item;
}
/**
* Implementation of hook_panels_dashboard_blocks().
*
* Adds page information to the Panels dashboard.
*/
function ctools_custom_content_panels_dashboard_blocks(&$vars) {
$vars['links']['ctools_custom_content'] = array(
'title' => l(t('Custom content'), 'admin/structure/ctools-content/add'),
'description' => t('Custom content panes are basic HTML you enter that can be reused in all of your panels.'),
);
// Load all mini panels and their displays.
ctools_include('export');
$items = ctools_export_crud_load_all('ctools_custom_content');
$count = 0;
$rows = array();
foreach ($items as $item) {
$rows[] = array(
check_plain($item->admin_title),
array(
'data' => l(t('Edit'), "admin/structure/ctools-content/list/$item->name/edit"),
'class' => 'links',
),
);
// Only show 10.
if (++$count >= 10) {
break;
}
}
if ($rows) {
$content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
}
else {
$content = '<p>' . t('There are no custom content panes.') . '</p>';
}
$vars['blocks']['ctools_custom_content'] = array(
'title' => t('Manage custom content'),
'link' => l(t('Go to list'), 'admin/structure/ctools-content'),
'content' => $content,
'class' => 'dashboard-content',
'section' => 'right',
);
}
@@ -0,0 +1,20 @@
<?php
$plugin = array(
'schema' => 'ctools_custom_content',
'access' => 'administer custom content',
'menu' => array(
'menu item' => 'ctools-content',
'menu title' => 'Custom content panes',
'menu description' => 'Add, edit or delete custom content panes.',
),
'title singular' => t('content pane'),
'title singular proper' => t('Content pane'),
'title plural' => t('content panes'),
'title plural proper' => t('Content panes'),
'handler' => 'ctools_custom_content_ui',
);
@@ -0,0 +1,129 @@
<?php
class ctools_custom_content_ui extends ctools_export_ui {
function edit_form(&$form, &$form_state) {
// Correct for an error that came in because filter format changed.
if (is_array($form_state['item']->settings['body'])) {
$form_state['item']->settings['format'] = $form_state['item']->settings['body']['format'];
$form_state['item']->settings['body'] = $form_state['item']->settings['body']['value'];
}
parent::edit_form($form, $form_state);
$form['category'] = array(
'#type' => 'textfield',
'#title' => t('Category'),
'#description' => t('What category this content should appear in. If left blank the category will be "Miscellaneous".'),
'#default_value' => $form_state['item']->category,
);
$form['title'] = array(
'#type' => 'textfield',
'#default_value' => $form_state['item']->settings['title'],
'#title' => t('Title'),
);
$form['body'] = array(
'#type' => 'text_format',
'#title' => t('Body'),
'#default_value' => $form_state['item']->settings['body'],
'#format' => $form_state['item']->settings['format'],
);
$form['substitute'] = array(
'#type' => 'checkbox',
'#title' => t('Use context keywords'),
'#description' => t('If checked, context keywords will be substituted in this content.'),
'#default_value' => !empty($form_state['item']->settings['substitute']),
);
}
function edit_form_submit(&$form, &$form_state) {
parent::edit_form_submit($form, $form_state);
// Since items in our settings are not in the schema, we have to do these manually:
$form_state['item']->settings['title'] = $form_state['values']['title'];
$form_state['item']->settings['body'] = $form_state['values']['body']['value'];
$form_state['item']->settings['format'] = $form_state['values']['body']['format'];
$form_state['item']->settings['substitute'] = $form_state['values']['substitute'];
}
function list_form(&$form, &$form_state) {
parent::list_form($form, $form_state);
$options = array('all' => t('- All -'));
foreach ($this->items as $item) {
$options[$item->category] = $item->category;
}
$form['top row']['category'] = array(
'#type' => 'select',
'#title' => t('Category'),
'#options' => $options,
'#default_value' => 'all',
'#weight' => -10,
);
}
function list_filter($form_state, $item) {
if ($form_state['values']['category'] != 'all' && $form_state['values']['category'] != $item->category) {
return TRUE;
}
return parent::list_filter($form_state, $item);
}
function list_sort_options() {
return array(
'disabled' => t('Enabled, title'),
'title' => t('Title'),
'name' => t('Name'),
'category' => t('Category'),
'storage' => t('Storage'),
);
}
function list_build_row($item, &$form_state, $operations) {
// Set up sorting
switch ($form_state['values']['order']) {
case 'disabled':
$this->sorts[$item->name] = empty($item->disabled) . $item->admin_title;
break;
case 'title':
$this->sorts[$item->name] = $item->admin_title;
break;
case 'name':
$this->sorts[$item->name] = $item->name;
break;
case 'category':
$this->sorts[$item->name] = $item->category;
break;
case 'storage':
$this->sorts[$item->name] = $item->type . $item->admin_title;
break;
}
$ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
$this->rows[$item->name] = array(
'data' => array(
array('data' => check_plain($item->name), 'class' => array('ctools-export-ui-name')),
array('data' => check_plain($item->admin_title), 'class' => array('ctools-export-ui-title')),
array('data' => check_plain($item->category), 'class' => array('ctools-export-ui-category')),
array('data' => $ops, 'class' => array('ctools-export-ui-operations')),
),
'title' => check_plain($item->admin_description),
'class' => array(!empty($item->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'),
);
}
function list_table_header() {
return array(
array('data' => t('Name'), 'class' => array('ctools-export-ui-name')),
array('data' => t('Title'), 'class' => array('ctools-export-ui-title')),
array('data' => t('Category'), 'class' => array('ctools-export-ui-category')),
array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations')),
);
}
}
@@ -0,0 +1,14 @@
The CTools Plugin Example is an example for developers of how to CTools
access, argument, content type, context, and relationship plugins.
There are a number of ways to profit from this:
1. The code itself intends to be as simple and self-explanatory as possible.
Nothing fancy is attempted: It's just trying to use the plugin API to show
how it can be used.
2. There is a sample panel. You can access it at /ctools_plugin_example/xxxx
to see how it works.
3. There is Advanced Help at admin/advanced_help/ctools_plugin_example.
@@ -0,0 +1,16 @@
name = Chaos Tools (CTools) Plugin Example
description = Shows how an external module can provide ctools plugins (for Panels, etc.).
package = Chaos tool suite
version = CTOOLS_MODULE_VERSION
dependencies[] = ctools
dependencies[] = panels
dependencies[] = page_manager
dependencies[] = advanced_help
core = 7.x
; Information added by Drupal.org packaging script on 2015-01-28
version = "7.x-1.6"
core = "7.x"
project = "ctools"
datestamp = "1422471484"
@@ -0,0 +1,94 @@
<?php
/*
* @file
*
* Working sample module to demonstrate CTools 3 plugins
*
* This sample module is only intended to demonstrate how external modules can
* provide ctools plugins. There is no useful functionality, and it's only
* intended for developers or for educational use.
*
* As far as possible, everything is kept very simple, not exercising all of
* the capabilities of CTools or Panels.
*
* Although the ctools documentation suggests that strict naming conventions
* be followed, this code attempts to follow only the conventions which are
* required (the hooks), in order to demonstrate the difference. You can
* certainly use the conventions, but it's important to know the difference
* between a convention and a requirement.
*
* The advanced_help module is required, because both CTools and this module
* provide help that way.
*
* There is a demonstration panel provided at /ctools_plugin_example/123
*/
/**
* Implements hook_menu
*/
function ctools_plugin_example_menu() {
$items = array();
$items["admin/settings/ctools_plugin_example"] = array(
'title' => 'CTools plugin example',
'description' => t("Demonstration code, advanced help, and a demo panel to show how to build ctools plugins."),
'page callback' => 'ctools_plugin_example_explanation_page',
'access arguments' => array('administer site configuration'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implements hook_ctools_plugin_directory().
*
* It simply tells panels where to find the .inc files that define various
* args, contexts, content_types. In this case the subdirectories of
* ctools_plugin_example/panels are used.
*/
function ctools_plugin_example_ctools_plugin_directory($module, $plugin) {
if ($module == 'ctools' && !empty($plugin)) {
return "plugins/$plugin";
}
}
/**
* Implement hook_ctools_plugin_api().
*
* If you do this, CTools will pick up default panels pages in
* <modulename>.pages_default.inc
*/
function ctools_plugin_example_ctools_plugin_api($module, $api) {
// @todo -- this example should explain how to put it in a different file.
if ($module == 'panels_mini' && $api == 'panels_default') {
return array('version' => 1);
}
if ($module == 'page_manager' && $api == 'pages_default') {
return array('version' => 1);
}
}
/**
* Just provide an explanation page for the admin section
* @return unknown_type
*/
function ctools_plugin_example_explanation_page() {
$content = '<p>' . t("The CTools Plugin Example is simply a developer's demo of how to create plugins for CTools. It provides no useful functionality for an ordinary user.") . '</p>';
$content .= '<p>' . t(
'There is a demo panel demonstrating much of the functionality provided at
<a href="@demo_url">CTools demo panel</a>, and you can find documentation on the examples at
!ctools_plugin_example_help.
CTools itself provides documentation at !ctools_help. Mostly, though, the code itself is intended to be the teacher.
You can find it in %path.',
array(
'@demo_url' => url('ctools_plugin_example/xxxxx'),
'!ctools_plugin_example_help' => theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')),
'!ctools_help' => theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')),
'%path' => drupal_get_path('module', 'ctools_plugin_example'),
)) . '</p>';
return $content;
}
@@ -0,0 +1,451 @@
<?php
/**
* @file
* This module provides default panels to demonstrate the behavior of the plugins.
*/
/**
* Default panels pages for CTools Plugin Example
*
* To pick up this file, your module needs to implement
* hook_ctools_plugin_api() - See ctools_plugin_example_ctools_plugin_api() in
* ctools_plugin_example.module.
*
*
* Note the naming of the file: <modulename>.pages_default.inc
* With this naming, no additional code needs to be provided. CTools will just find the file.
* The name of the hook is <modulename>_default_page_manager_pages()
*
* This example provides two pages, but the returned array could
* have several pages.
*
* @return
* Array of pages, normally exported from Panels.
*/
function ctools_plugin_example_default_page_manager_pages() {
// begin exported panel.
$page = new stdClass;
$page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */
$page->api_version = 1;
$page->name = 'ctools_plugin_example';
$page->task = 'page';
$page->admin_title = 'CTools plugin example';
$page->admin_description = 'This panel provides no functionality to a working Drupal system. It\'s intended to display the various sample plugins provided by the CTools Plugin Example module. ';
$page->path = 'ctools_plugin_example/%sc';
$page->access = array(
'logic' => 'and',
);
$page->menu = array(
'type' => 'normal',
'title' => 'CTools plugin example',
'name' => 'navigation',
'weight' => '0',
'parent' => array(
'type' => 'none',
'title' => '',
'name' => 'navigation',
'weight' => '0',
),
);
$page->arguments = array(
'sc' => array(
'id' => 2,
'identifier' => 'simplecontext-arg',
'name' => 'simplecontext_arg',
'settings' => array(),
),
);
$page->conf = array();
$page->default_handlers = array();
$handler = new stdClass;
$handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */
$handler->api_version = 1;
$handler->name = 'page_ctools_panel_context';
$handler->task = 'page';
$handler->subtask = 'ctools_plugin_example';
$handler->handler = 'panel_context';
$handler->weight = 0;
$handler->conf = array(
'title' => 'Panel',
'no_blocks' => FALSE,
'css_id' => '',
'css' => '',
'contexts' => array(
'0' => array(
'name' => 'simplecontext',
'id' => 1,
'identifier' => 'Configured simplecontext (not from argument)',
'keyword' => 'configured_simplecontext',
'context_settings' => array(
'sample_simplecontext_setting' => 'default simplecontext setting',
),
),
),
'relationships' => array(
'0' => array(
'context' => 'argument_simplecontext_arg_2',
'name' => 'relcontext_from_simplecontext',
'id' => 1,
'identifier' => 'Relcontext from simplecontext (from relationship)',
'keyword' => 'relcontext',
),
),
'access' => array(
'logic' => 'and',
),
);
$display = new panels_display;
$display->layout = 'threecol_33_34_33_stacked';
$display->layout_settings = array();
$display->panel_settings = array(
'style' => 'rounded_corners',
'style_settings' => array(
'default' => array(
'corner_location' => 'pane',
),
),
);
$display->cache = array();
$display->title = 'CTools plugin example panel';
$display->hide_title = FALSE;
$display->title_pane = 1;
$display->content = array();
$display->panels = array();
$pane = new stdClass;
$pane->pid = 'new-1';
$pane->panel = 'left';
$pane->type = 'no_context_content_type';
$pane->subtype = 'no_context_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'item1' => 'contents of config item 1',
'item2' => 'contents of config item 2',
'override_title' => 0,
'override_title_text' => '',
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-1'] = $pane;
$display->panels['left'][0] = 'new-1';
$pane = new stdClass;
$pane->pid = 'new-2';
$pane->panel = 'left';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array(
'plugins' => array(
'0' => array(
'name' => 'arg_length',
'settings' => array(
'greater_than' => '1',
'arg_length' => '4',
),
'context' => 'argument_simplecontext_arg_2',
),
),
);
$pane->configuration = array(
'title' => 'Long Arg Visibility Block',
'body' => 'This block will be here when the argument is longer than configured arg length. It uses the \'arg_length\' access plugin to test against the length of the argument used for Simplecontext.',
'format' => '1',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 1;
$display->content['new-2'] = $pane;
$display->panels['left'][1] = 'new-2';
$pane = new stdClass;
$pane->pid = 'new-3';
$pane->panel = 'left';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array(
'plugins' => array(
'0' => array(
'name' => 'arg_length',
'settings' => array(
'greater_than' => '0',
'arg_length' => '4',
),
'context' => 'argument_simplecontext_arg_2',
),
),
);
$pane->configuration = array(
'title' => 'Short Arg Visibility',
'body' => 'This block appears when the simplecontext argument is <i>less than</i> the configured length.',
'format' => '1',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 2;
$display->content['new-3'] = $pane;
$display->panels['left'][2] = 'new-3';
$pane = new stdClass;
$pane->pid = 'new-4';
$pane->panel = 'middle';
$pane->type = 'simplecontext_content_type';
$pane->subtype = 'simplecontext_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'buttons' => NULL,
'#validate' => NULL,
'#submit' => NULL,
'#action' => NULL,
'context' => 'argument_simplecontext_arg_2',
'aligner_start' => NULL,
'override_title' => 1,
'override_title_text' => 'Simplecontext (with an arg)',
'aligner_stop' => NULL,
'override_title_markup' => NULL,
'config_item_1' => 'Config item 1 contents',
'#build_id' => NULL,
'#type' => NULL,
'#programmed' => NULL,
'form_build_id' => 'form-19c4ae6cb54fad8f096da46e95694e5a',
'#token' => NULL,
'form_token' => '17141d3531eaa7b609da78afa6f3b560',
'form_id' => 'simplecontext_content_type_edit_form',
'#id' => NULL,
'#description' => NULL,
'#attributes' => NULL,
'#required' => NULL,
'#tree' => NULL,
'#parents' => NULL,
'#method' => NULL,
'#post' => NULL,
'#processed' => NULL,
'#defaults_loaded' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-4'] = $pane;
$display->panels['middle'][0] = 'new-4';
$pane = new stdClass;
$pane->pid = 'new-5';
$pane->panel = 'middle';
$pane->type = 'simplecontext_content_type';
$pane->subtype = 'simplecontext_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'buttons' => NULL,
'#validate' => NULL,
'#submit' => NULL,
'#action' => NULL,
'context' => 'context_simplecontext_1',
'aligner_start' => NULL,
'override_title' => 1,
'override_title_text' => 'Configured simplecontext content type (not from arg)',
'aligner_stop' => NULL,
'override_title_markup' => NULL,
'config_item_1' => '(configuration for simplecontext)',
'#build_id' => NULL,
'#type' => NULL,
'#programmed' => NULL,
'form_build_id' => 'form-d016200490abd015dc5b8a7e366d76ea',
'#token' => NULL,
'form_token' => '17141d3531eaa7b609da78afa6f3b560',
'form_id' => 'simplecontext_content_type_edit_form',
'#id' => NULL,
'#description' => NULL,
'#attributes' => NULL,
'#required' => NULL,
'#tree' => NULL,
'#parents' => NULL,
'#method' => NULL,
'#post' => NULL,
'#processed' => NULL,
'#defaults_loaded' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 1;
$display->content['new-5'] = $pane;
$display->panels['middle'][1] = 'new-5';
$pane = new stdClass;
$pane->pid = 'new-6';
$pane->panel = 'middle';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'admin_title' => 'Simplecontext keyword usage',
'title' => 'Simplecontext keyword usage',
'body' => 'Demonstrating context keyword usage:
item1 is %sc:item1
item2 is %sc:item2
description is %sc:description',
'format' => '1',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 2;
$display->content['new-6'] = $pane;
$display->panels['middle'][2] = 'new-6';
$pane = new stdClass;
$pane->pid = 'new-7';
$pane->panel = 'right';
$pane->type = 'relcontext_content_type';
$pane->subtype = 'relcontext_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'buttons' => NULL,
'#validate' => NULL,
'#submit' => NULL,
'#action' => NULL,
'context' => 'relationship_relcontext_from_simplecontext_1',
'aligner_start' => NULL,
'override_title' => 0,
'override_title_text' => '',
'aligner_stop' => NULL,
'override_title_markup' => NULL,
'config_item_1' => 'some stuff in this one',
'#build_id' => NULL,
'#type' => NULL,
'#programmed' => NULL,
'form_build_id' => 'form-8485f84511bd06e51b4a48e998448054',
'#token' => NULL,
'form_token' => '1c3356396374d51d7d2531a10fd25310',
'form_id' => 'relcontext_edit_form',
'#id' => NULL,
'#description' => NULL,
'#attributes' => NULL,
'#required' => NULL,
'#tree' => NULL,
'#parents' => NULL,
'#method' => NULL,
'#post' => NULL,
'#processed' => NULL,
'#defaults_loaded' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-7'] = $pane;
$display->panels['right'][0] = 'new-7';
$pane = new stdClass;
$pane->pid = 'new-8';
$pane->panel = 'top';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'title' => 'Demonstrating ctools plugins',
'body' => 'The CTools Plugin Example module (and this panel page) are just here to demonstrate how to build CTools plugins.
',
'format' => '2',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-8'] = $pane;
$display->panels['top'][0] = 'new-8';
$handler->conf['display'] = $display;
$page->default_handlers[$handler->name] = $handler;
// end of exported panel.
$pages['ctools_plugin_example_demo_page'] = $page;
// begin exported panel
$page = new stdClass;
$page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */
$page->api_version = 1;
$page->name = 'ctools_plugin_example_base';
$page->task = 'page';
$page->admin_title = 'CTools Plugin Example base page';
$page->admin_description = 'This panel is for when people hit /ctools_plugin_example without an argument. We can use it to tell people to move on.';
$page->path = 'ctools_plugin_example';
$page->access = array();
$page->menu = array();
$page->arguments = array();
$page->conf = array();
$page->default_handlers = array();
$handler = new stdClass;
$handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */
$handler->api_version = 1;
$handler->name = 'page_ctools_plugin_example_base_panel_context';
$handler->task = 'page';
$handler->subtask = 'ctools_plugin_example_base';
$handler->handler = 'panel_context';
$handler->weight = 0;
$handler->conf = array(
'title' => 'Panel',
'no_blocks' => FALSE,
'css_id' => '',
'css' => '',
'contexts' => array(),
'relationships' => array(),
);
$display = new panels_display;
$display->layout = 'onecol';
$display->layout_settings = array();
$display->panel_settings = array();
$display->cache = array();
$display->title = '';
$display->hide_title = FALSE;
$display->content = array();
$display->panels = array();
$pane = new stdClass;
$pane->pid = 'new-1';
$pane->panel = 'middle';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'title' => 'Use this page with an argument',
'body' => 'This demo page works if you use an argument, like <a href="ctools_plugin_example/xxxxx">ctools_plugin_example/xxxxx</a>.',
'format' => '1',
'substitute' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-1'] = $pane;
$display->panels['middle'][0] = 'new-1';
$handler->conf['display'] = $display;
$page->default_handlers[$handler->name] = $handler;
// end exported panel.
$pages['base_page'] = $page;
return $pages;
}
@@ -0,0 +1,17 @@
<div id="node-16" class="node">
<div class="content clear-block">
<p>We can use access plugins to determine access to a page or visibility of the panes in a page. Basically, we just determine access based on configuration settings and the various contexts that are available to us.</p>
<p>The arbitrary example in plugins/access/arg_length.inc determines access based on the length of the simplecontext argument. You can configure whether access should be granted if the simplecontext argument is greater or less than some number.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>
@@ -0,0 +1,20 @@
<div id="node-12" class="node">
<div class="content clear-block">
<p>Contexts are fundamental to CTools, and they almost always start with an argument to a panels page, so we'll start there too.</p>
<p>We first need to process an argument.</p>
<p>We're going to work with a "Simplecontext" context type and argument, and then with a content type that displays it. So we'll start by with the Simplecontext argument plugin in plugins/arguments/simplecontext_arg.inc.</p>
<p>Note that the name of the file (simplecontext_arg.inc) is built from the machine name of our plugin (simplecontext_arg). And note also that the primary function that we use to provide our argument (ctools_plugin_example_simplecontext_arg_ctools_arguments()) is also built from the machine name. This magic is most of the naming magic that you have to know.</p>
<p>You can browse plugins/arguments/simplecontext_arg.inc and see the little that it does.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>
@@ -0,0 +1,19 @@
<div id="node-10" class="node">
<div class="content clear-block">
<p>This demonstration module is intended for developers to look at and play with. CTools plugins are not terribly difficult to do, but it can be hard to sort through the various arguments and required functions. The idea here is that you should have a starting point for most anything you want to do. Just work through the example, and then start experimenting with changing it.</p>
<p>There are two parts to this demo: </p>
<p>First, there is a sample panel provided that uses all the various plugins. It's at <a href="/ctools_plugin_example/12345">ctools_example/12345</a>. You can edit the panel and configure all the panes on it.</p>
<p>Second, the code is there for you to experiment with and change as you see fit. Sometimes starting with simple code and working with it can take you places that it's hard to go when you're looking at more complex examples.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>
@@ -0,0 +1,17 @@
<div id="node-14" class="node">
<div class="content clear-block">
<p>Now we get to the heart of the matter: Building a content type plugin. A content type plugin uses the contexts available to it to display something. plugins/content_types/simplecontext_content_type.inc does this work for us.</p>
<p>Note that our content type also has an edit form which can be used to configure its behavior. This settings form is accessed through the panels interface, and it's up to you what the settings mean to the code and the generation of content in the display rendering.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>
@@ -0,0 +1,21 @@
<div id="node-13" class="node">
<div class="content clear-block">
<p>Now that we have a plugin for a simplecontext argument, we can create a plugin for a simplecontext context. </p>
<p>Normally, a context would take an argument which is a key like a node ID (nid) and retrieve a more complex object from a database or whatever. In our example, we'll artificially transform the argument into an arbitrary "context" data object. </p>
<p>plugins/contexts/simplecontext.inc implements our context.</p>
<p>Note that there are actually two ways to create a context. The normal one, which we've been referring to, is to create a context from an argument. However, it is also possible to configure a context in a panel using the panels interface. This is quite inflexible, but might be useful for configuring single page. However, it means that we have a settings form for exactly that purpose. Our context would have to know how to create itself from a settings form as well as from an argument. Simplecontext can do that.</p>
<p>A context plugin can also provide keywords that expose parts of its context using keywords like masterkeyword:dataitem. The node plugin for ctools has node:nid and node:title, for example. The simplecontext plugin here provides the simplest of keywords.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>
@@ -0,0 +1,20 @@
<div id="node-11" class="node">
<div class="content clear-block">
<p>Your module must provide a few things so that your plugins can be found.</p>
<p>First, you need to implement hook_ctools_plugin_directory(). Here we're telling CTools that our plugins will be found in the module's directory in the plugins/&lt;plugintype&gt; directory. Context plugins will be in ctools_plugin_example/plugins/contexts, Content-type plugins will be in ctools_plugin_example/plugins/content_types.</p>
<p><div class="codeblock"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">ctools_plugin_example_ctools_plugin_directory</span><span style="color: #007700">(</span><span style="color: #0000BB">$module</span><span style="color: #007700">, </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">) {<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'ctools' </span><span style="color: #007700">&amp;&amp; !empty(</span><span style="color: #0000BB">$plugin</span><span style="color: #007700">)) {<br />&nbsp;&nbsp;&nbsp; return </span><span style="color: #DD0000">"plugins/$plugin"</span><span style="color: #007700">;<br />&nbsp; }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></p>
<p>Second, if you module wants to provide default panels pages, you can implement hook_ctools_plugin_api(). CTools will then pick up your panels pages in the file named &lt;modulename&gt;.pages_default.inc.</p>
<p><div class="codeblock"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">ctools_plugin_example_ctools_plugin_api</span><span style="color: #007700">(</span><span style="color: #0000BB">$module</span><span style="color: #007700">, </span><span style="color: #0000BB">$api</span><span style="color: #007700">) {<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'panels_mini' </span><span style="color: #007700">&amp;&amp; </span><span style="color: #0000BB">$api </span><span style="color: #007700">== </span><span style="color: #DD0000">'panels_default'</span><span style="color: #007700">) {<br />&nbsp;&nbsp;&nbsp; return array(</span><span style="color: #DD0000">'version' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">1</span><span style="color: #007700">);<br />&nbsp; }<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'page_manager' </span><span style="color: #007700">&amp;&amp; </span><span style="color: #0000BB">$api </span><span style="color: #007700">== </span><span style="color: #DD0000">'pages_default'</span><span style="color: #007700">) {<br />&nbsp;&nbsp;&nbsp; return array(</span><span style="color: #DD0000">'version' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">1</span><span style="color: #007700">);<br />&nbsp; }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>
@@ -0,0 +1,18 @@
<div id="node-15" class="node">
<div class="content clear-block">
<p>Often a single data type can lead us to other data types. For example, a node has a user (the author) and the user has data associated with it.</p>
<p>A relationship plugin allows this kind of data to be accessed. </p>
<p>An example relationship plugin is provided in plugins/relationships/relcontext_from_simplecontext.inc. It looks at a simplecontext (which we got from an argument) and builds an (artificial) "relcontext" from that.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>
@@ -0,0 +1,42 @@
[Chaos-Tools--CTools--Plugin-Examples]
title = CTools Plugin Examples
file = Chaos-Tools--CTools--Plugin-Examples
weight = 0
parent =
[Module-setup-and-hooks]
title = Module setup and hooks
file = Module-setup-and-hooks
weight = -15
parent = Chaos-Tools--CTools--Plugin-Examples
[Argument-Plugins--Starting-at-the-beginning]
title = Argument Plugins: Starting at the beginning
file = Argument-Plugins--Starting-at-the-beginning
weight = -14
parent = Chaos-Tools--CTools--Plugin-Examples
[Context-plugins--Creating-a--context--from-an-argument]
title = Context plugins: Creating a context from an argument
file = Context-plugins--Creating-a--context--from-an-argument
weight = -13
parent = Chaos-Tools--CTools--Plugin-Examples
[Content-Type-Plugins--Displaying-content-using-a-context]
title = Content Type Plugins: Displaying content using a context
file = Content-Type-Plugins--Displaying-content-using-a-context
weight = -12
parent = Chaos-Tools--CTools--Plugin-Examples
[Access-Plugins--Determining-access-and-visibility]
title = Access Plugins: Determining access and visibility
file = Access-Plugins--Determining-access-and-visibility
weight = -11
parent = Chaos-Tools--CTools--Plugin-Examples
[Relationships--Letting-one-context-take-us-to-another]
title = Relationships: Letting one context take us to another
file = Relationships--Letting-one-context-take-us-to-another
weight = -10
parent = Chaos-Tools--CTools--Plugin-Examples
@@ -0,0 +1,65 @@
<?php
/**
* @file
* Plugin to provide access control/visibility based on length of
* simplecontext argument (in URL).
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t("Arg length"),
'description' => t('Control access by length of simplecontext argument.'),
'callback' => 'ctools_plugin_example_arg_length_ctools_access_check',
'settings form' => 'ctools_plugin_example_arg_length_ctools_access_settings',
'summary' => 'ctools_plugin_example_arg_length_ctools_access_summary',
'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'),
);
/**
* Settings form for the 'by role' access plugin.
*/
function ctools_plugin_example_arg_length_ctools_access_settings(&$form, &$form_state, $conf) {
$form['settings']['greater_than'] = array(
'#type' => 'radios',
'#title' => t('Grant access if simplecontext argument length is'),
'#options' => array(1 => t('Greater than'), 0 => t('Less than or equal to')),
'#default_value' => $conf['greater_than'],
);
$form['settings']['arg_length'] = array(
'#type' => 'textfield',
'#title' => t('Length of simplecontext argument'),
'#size' => 3,
'#default_value' => $conf['arg_length'],
'#description' => t('Access/visibility will be granted based on arg length.'),
);
}
/**
* Check for access.
*/
function ctools_plugin_example_arg_length_ctools_access_check($conf, $context) {
// As far as I know there should always be a context at this point, but this
// is safe.
if (empty($context) || empty($context->data)) {
return FALSE;
}
$compare = ($context->arg_length > $conf['arg_length']);
if (($compare && $conf['greater_than']) || (!$compare && !$conf['greater_than'])) {
return TRUE;
}
return FALSE;
}
/**
* Provide a summary description based upon the checked roles.
*/
function ctools_plugin_example_arg_length_ctools_access_summary($conf, $context) {
return t('Simpletext argument must be !comp @length characters',
array('!comp' => $conf['greater_than'] ? 'greater than' : 'less than or equal to',
'@length' => $conf['arg_length']));
}
@@ -0,0 +1,76 @@
<?php
/**
* @file
* Plugin to provide access control based upon role membership.
* This is directly from the ctools module, but serves as a good
* example of an access plugin
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t("CTools example: role"),
'description' => t('Control access by role.'),
'callback' => 'ctools_plugin_example_example_role_ctools_access_check',
'default' => array('rids' => array()),
'settings form' => 'ctools_plugin_example_example_role_ctools_access_settings',
'summary' => 'ctools_plugin_example_example_role_ctools_access_summary',
'required context' => new ctools_context_required(t('User'), 'user'),
);
/**
* Settings form for the 'by role' access plugin.
*/
function ctools_plugin_example_example_role_ctools_access_settings(&$form, &$form_state, $conf) {
$form['settings']['rids'] = array(
'#type' => 'checkboxes',
'#title' => t('Role'),
'#default_value' => $conf['rids'],
'#options' => ctools_get_roles(),
'#description' => t('Only the checked roles will be granted access.'),
);
}
/**
* Compress the roles allowed to the minimum.
*/
function ctools_plugin_example_example_role_ctools_access_settings_submit(&$form, &$form_state) {
$form_state['values']['settings']['rids'] = array_keys(array_filter($form_state['values']['settings']['rids']));
}
/**
* Check for access.
*/
function ctools_plugin_example_example_role_ctools_access_check($conf, $context) {
// As far as I know there should always be a context at this point, but this
// is safe.
if (empty($context) || empty($context->data) || !isset($context->data->roles)) {
return FALSE;
}
$roles = array_keys($context->data->roles);
$roles[] = $context->data->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
return (bool) array_intersect($conf['rids'], $roles);
}
/**
* Provide a summary description based upon the checked roles.
*/
function ctools_plugin_example_example_role_ctools_access_summary($conf, $context) {
if (!isset($conf['rids'])) {
$conf['rids'] = array();
}
$roles = ctools_get_roles();
$names = array();
foreach (array_filter($conf['rids']) as $rid) {
$names[] = check_plain($roles[$rid]);
}
if (empty($names)) {
return t('@identifier can have any role', array('@identifier' => $context->identifier));
}
return format_plural(count($names), '@identifier must have role "@roles"', '@identifier can be one of "@roles"', array('@roles' => implode(', ', $names), '@identifier' => $context->identifier));
}
@@ -0,0 +1,52 @@
<?php
/**
* @file
*
* Sample plugin to provide an argument handler for a simplecontext.
*
* Given any argument to the page, simplecontext will get it
* and turn it into a piece of data (a "context") just by adding some text to it.
* Normally, the argument would be a key into some database (like the node
* database, for example, and the result of using the argument would be to load
* a specific "context" or data item that we can use elsewhere.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t("Simplecontext arg"),
// keyword to use for %substitution
'keyword' => 'simplecontext',
'description' => t('Creates a "simplecontext" from the arg.'),
'context' => 'simplecontext_arg_context',
// 'settings form' => 'simplecontext_arg_settings_form',
// placeholder_form is used in panels preview, for example, so we can
// preview without getting the arg from a URL
'placeholder form' => array(
'#type' => 'textfield',
'#description' => t('Enter the simplecontext arg'),
),
);
/**
* Get the simplecontext context using the arg. In this case we're just going
* to manufacture the context from the data in the arg, but normally it would
* be an API call, db lookup, etc.
*/
function simplecontext_arg_context($arg = NULL, $conf = NULL, $empty = FALSE) {
// If $empty == TRUE it wants a generic, unfilled context.
if ($empty) {
return ctools_context_create_empty('simplecontext');
}
// Do whatever error checking is required, returning FALSE if it fails the test
// Normally you'd check
// for a missing object, one you couldn't create, etc.
if (empty($arg)) {
return FALSE;
}
return ctools_context_create('simplecontext', $arg);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

@@ -0,0 +1,116 @@
<?php
/**
* @file
* "No context" sample content type. It operates with no context at all. It would
* be basically the same as a 'custom content' block, but it's not even that
* sophisticated.
*
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t('CTools example no context content type'),
'description' => t('No context content type - requires and uses no context.'),
// 'single' => TRUE means has no subtypes.
'single' => TRUE,
// Constructor.
'content_types' => array('no_context_content_type'),
// Name of a function which will render the block.
'render callback' => 'no_context_content_type_render',
// The default context.
'defaults' => array(),
// This explicitly declares the config form. Without this line, the func would be
// ctools_plugin_example_no_context_content_type_edit_form.
'edit form' => 'no_context_content_type_edit_form',
// Icon goes in the directory with the content type.
'icon' => 'icon_example.png',
'category' => array(t('CTools Examples'), -9),
// this example does not provide 'admin info', which would populate the
// panels builder page preview.
);
/**
* Run-time rendering of the body of the block.
*
* @param $subtype
* @param $conf
* Configuration as done at admin time.
* @param $args
* @param $context
* Context - in this case we don't have any.
*
* @return
* An object with at least title and content members.
*/
function no_context_content_type_render($subtype, $conf, $args, $context) {
$block = new stdClass();
$ctools_help = theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title'));
$ctools_plugin_example_help = theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title'));
// The title actually used in rendering
$block->title = check_plain("No-context content type");
$block->content = t("
<div>Welcome to the CTools Plugin Example demonstration content type.
This block is a content type which requires no context at all. It's like a custom block,
but not even that sophisticated.
For more information on the example plugins, please see the advanced help for
{$ctools_help} and {$ctools_plugin_example_help}
</div>
");
if (!empty($conf)) {
$block->content .= '<div>The only information that can be displayed in this block comes from the code and its settings form: </div>';
$block->content .= '<div style="border: 1px solid red;">' . var_export($conf, TRUE) . '</div>';
}
return $block;
}
/**
* 'Edit form' callback for the content type.
* This example just returns a form; validation and submission are standard drupal
* Note that if we had not provided an entry for this in hook_content_types,
* this could have had the default name
* ctools_plugin_example_no_context_content_type_edit_form.
*
*/
function no_context_content_type_edit_form($form, &$form_state) {
$conf = $form_state['conf'];
$form['item1'] = array(
'#type' => 'textfield',
'#title' => t('Item1'),
'#size' => 50,
'#description' => t('The setting for item 1.'),
'#default_value' => !empty($conf['item1']) ? $conf['item1'] : '',
'#prefix' => '<div class="clear-block no-float">',
'#suffix' => '</div>',
);
$form['item2'] = array(
'#type' => 'textfield',
'#title' => t('Item2'),
'#size' => 50,
'#description' => t('The setting for item 2'),
'#default_value' => !empty($conf['item2']) ? $conf['item2'] : '',
'#prefix' => '<div class="clear-block no-float">',
'#suffix' => '</div>',
);
return $form;
}
function no_context_content_type_edit_form_submit($form, &$form_state) {
foreach (array('item1', 'item2') as $key) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
@@ -0,0 +1,103 @@
<?php
/**
* @file
* Content type that displays the relcontext context type.
*
* This example is for use with the relcontext relationship to show
* how we can get a relationship-context into a data type.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
// Used in add content dialogs.
'title' => t('CTools example relcontext content type'),
'admin info' => 'ctools_plugin_example_relcontext_content_type_admin_info',
'content_types' => 'relcontext_content_type',
'single' => TRUE,
'render callback' => 'relcontext_content_type_render',
// Icon goes in the directory with the content type. Here, in plugins/content_types.
'icon' => 'icon_example.png',
'description' => t('Relcontext content type - works with relcontext context.'),
'required context' => new ctools_context_required(t('Relcontext'), 'relcontext'),
'category' => array(t('CTools Examples'), -9),
'edit form' => 'relcontext_edit_form',
// this example does not provide 'admin info', which would populate the
// panels builder page preview.
);
/**
* Run-time rendering of the body of the block.
*
* @param $subtype
* @param $conf
* Configuration as done at admin time
* @param $args
* @param $context
* Context - in this case we don't have any
*
* @return
* An object with at least title and content members
*/
function relcontext_content_type_render($subtype, $conf, $args, $context) {
$data = $context->data;
$block = new stdClass();
// Don't forget to check this data if it's untrusted.
// The title actually used in rendering.
$block->title = "Relcontext content type";
$block->content = t("
This is a block of data created by the Relcontent content type.
Data in the block may be assembled from static text (like this) or from the
content type settings form (\$conf) for the content type, or from the context
that is passed in. <br />
In our case, the configuration form (\$conf) has just one field, 'config_item_1;
and it's configured with:
");
if (!empty($conf)) {
$block->content .= '<div style="border: 1px solid red;">' . var_export($conf['config_item_1'], TRUE) . '</div>';
}
if (!empty($context)) {
$block->content .= '<br />The args ($args) were <div style="border: 1px solid yellow;" >' .
var_export($args, TRUE) . '</div>';
}
$block->content .= '<br />And the relcontext context ($context->data->description)
(which was created from a
simplecontext context) was <div style="border: 1px solid green;" >' .
print_r($context->data->description, TRUE) . '</div>';
return $block;
}
/**
* 'Edit' callback for the content type.
* This example just returns a form.
*
*/
function relcontext_edit_form($form, &$form_state) {
$conf = $form_state['conf'];
$form['config_item_1'] = array(
'#type' => 'textfield',
'#title' => t('Config Item 1 (relcontext)'),
'#size' => 50,
'#description' => t('Setting for relcontext.'),
'#default_value' => !empty($conf['config_item_1']) ? $conf['config_item_1'] : '',
'#prefix' => '<div class="clear-block no-float">',
'#suffix' => '</div>',
);
return $form;
}
function relcontext_edit_form_submit($form, &$form_state) {
foreach (element_children($form) as $key) {
if (!empty($form_state['values'][$key])) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
}
@@ -0,0 +1,129 @@
<?php
/**
* @file
* Sample ctools content type that takes advantage of context.
*
* This example uses the context it gets (simplecontext) to demo how a
* ctools content type can access and use context. Note that the simplecontext
* can be either configured manually into a panel or it can be retrieved via
* an argument.
*
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t('Simplecontext content type'),
'content_types' => 'simplecontext_content_type',
// 'single' means not to be subtyped.
'single' => TRUE,
// Name of a function which will render the block.
'render callback' => 'simplecontext_content_type_render',
// Icon goes in the directory with the content type.
'icon' => 'icon_example.png',
'description' => t('Simplecontext content type - works with a simplecontext context.'),
'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'),
'edit form' => 'simplecontext_content_type_edit_form',
'admin title' => 'ctools_plugin_example_simplecontext_content_type_admin_title',
// presents a block which is used in the preview of the data.
// Pn Panels this is the preview pane shown on the panels building page.
'admin info' => 'ctools_plugin_example_simplecontext_content_type_admin_info',
'category' => array(t('CTools Examples'), -9),
);
function ctools_plugin_example_simplecontext_content_type_admin_title($subtype, $conf, $context = NULL) {
$output = t('Simplecontext');
if ($conf['override_title'] && !empty($conf['override_title_text'])) {
$output = filter_xss_admin($conf['override_title_text']);
}
return $output;
}
/**
* Callback to provide administrative info (the preview in panels when building
* a panel).
*
* In this case we'll render the content with a dummy argument and
* a dummy context.
*/
function ctools_plugin_example_simplecontext_content_type_admin_info($subtype, $conf, $context = NULL) {
$context = new stdClass();
$context->data = new stdClass();
$context->data->description = t("no real context");
$block = simplecontext_content_type_render($subtype, $conf, array("Example"), $context);
return $block;
}
/**
* Run-time rendering of the body of the block (content type)
*
* @param $subtype
* @param $conf
* Configuration as done at admin time
* @param $args
* @param $context
* Context - in this case we don't have any
*
* @return
* An object with at least title and content members
*/
function simplecontext_content_type_render($subtype, $conf, $args, $context) {
$data = $context->data;
$block = new stdClass();
// Don't forget to check this data if it's untrusted.
// The title actually used in rendering.
$block->title = "Simplecontext content type";
$block->content = t("
This is a block of data created by the Simplecontext content type.
Data in the block may be assembled from static text (like this) or from the
content type settings form (\$conf) for the content type, or from the context
that is passed in. <br />
In our case, the configuration form (\$conf) has just one field, 'config_item_1;
and it's configured with:
");
if (!empty($conf)) {
$block->content .= '<div style="border: 1px solid red;">' . print_r(filter_xss_admin($conf['config_item_1']), TRUE) . '</div>';
}
if (!empty($context)) {
$block->content .= '<br />The args ($args) were <div style="border: 1px solid yellow;" >' .
var_export($args, TRUE) . '</div>';
}
$block->content .= '<br />And the simplecontext context ($context->data->description) was <div style="border: 1px solid green;" >' .
print_r($context->data->description, TRUE) . '</div>';
return $block;
}
/**
* 'Edit' callback for the content type.
* This example just returns a form.
*
*/
function simplecontext_content_type_edit_form($form, &$form_state) {
$conf = $form_state['conf'];
$form['config_item_1'] = array(
'#type' => 'textfield',
'#title' => t('Config Item 1 for simplecontext content type'),
'#size' => 50,
'#description' => t('The stuff for item 1.'),
'#default_value' => !empty($conf['config_item_1']) ? $conf['config_item_1'] : '',
'#prefix' => '<div class="clear-block no-float">',
'#suffix' => '</div>',
);
return $form;
}
function simplecontext_content_type_edit_form_submit($form, &$form_state) {
foreach (element_children($form) as $key) {
if (!empty($form_state['values'][$key])) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
}
@@ -0,0 +1,83 @@
<?php
/**
* @file
* Sample ctools context type plugin that
* is used in this demo to create a relcontext from an existing simplecontext.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t("Relcontext"),
'description' => t('A relcontext object.'),
// Function to create the relcontext.
'context' => 'ctools_plugin_example_context_create_relcontext',
// Function that does the settings.
'settings form' => 'relcontext_settings_form',
'keyword' => 'relcontext',
'context name' => 'relcontext',
);
/**
* Create a context, either from manual configuration (form) or from an argument on the URL.
*
* @param $empty
* If true, just return an empty context.
* @param $data
* If from settings form, an array as from a form. If from argument, a string.
* @param $conf
* TRUE if the $data is coming from admin configuration, FALSE if it's from a URL arg.
*
* @return
* a Context object.
*/
function ctools_plugin_example_context_create_relcontext($empty, $data = NULL, $conf = FALSE) {
$context = new ctools_context('relcontext');
$context->plugin = 'relcontext';
if ($empty) {
return $context;
}
if ($conf) {
if (!empty($data)) {
$context->data = new stdClass();
// For this simple item we'll just create our data by stripping non-alpha and
// adding 'sample_relcontext_setting' to it.
$context->data->description = 'relcontext_from__' . preg_replace('/[^a-z]/i', '', $data['sample_relcontext_setting']);
$context->data->description .= '_from_configuration_sample_simplecontext_setting';
$context->title = t("Relcontext context from simplecontext");
return $context;
}
}
else {
// $data is coming from an arg - it's just a string.
// This is used for keyword.
$context->title = "relcontext_" . $data->data->description;
$context->argument = $data->argument;
// Make up a bogus context.
$context->data = new stdClass();
// For this simple item we'll just create our data by stripping non-alpha and
// prepend 'relcontext_' and adding '_created_from_from_simplecontext' to it.
$context->data->description = 'relcontext_' . preg_replace('/[^a-z]/i', '', $data->data->description);
$context->data->description .= '_created_from_simplecontext';
return $context;
}
}
function relcontext_settings_form($conf, $external = FALSE) {
$form = array();
$form['sample_relcontext_setting'] = array(
'#type' => 'textfield',
'#title' => t('Relcontext setting'),
'#size' => 50,
'#description' => t('Just an example setting.'),
'#default_value' => !empty($conf['sample_relcontext_setting']) ? $conf['sample_relcontext_setting'] : '',
'#prefix' => '<div class="clear-block no-float">',
'#suffix' => '</div>',
);
return $form;
}
@@ -0,0 +1,134 @@
<?php
/**
* @file
* Sample ctools context type plugin that shows how to create a context from an arg.
*
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t("Simplecontext"),
'description' => t('A single "simplecontext" context, or data element.'),
'context' => 'ctools_plugin_example_context_create_simplecontext', // func to create context
'context name' => 'simplecontext',
'settings form' => 'simplecontext_settings_form',
'keyword' => 'simplecontext',
// Provides a list of items which are exposed as keywords.
'convert list' => 'simplecontext_convert_list',
// Convert keywords into data.
'convert' => 'simplecontext_convert',
'placeholder form' => array(
'#type' => 'textfield',
'#description' => t('Enter some data to represent this "simplecontext".'),
),
);
/**
* Create a context, either from manual configuration or from an argument on the URL.
*
* @param $empty
* If true, just return an empty context.
* @param $data
* If from settings form, an array as from a form. If from argument, a string.
* @param $conf
* TRUE if the $data is coming from admin configuration, FALSE if it's from a URL arg.
*
* @return
* a Context object/
*/
function ctools_plugin_example_context_create_simplecontext($empty, $data = NULL, $conf = FALSE) {
$context = new ctools_context('simplecontext');
$context->plugin = 'simplecontext';
if ($empty) {
return $context;
}
if ($conf) {
if (!empty($data)) {
$context->data = new stdClass();
// For this simple item we'll just create our data by stripping non-alpha and
// adding '_from_configuration_item_1' to it.
$context->data->item1 = t("Item1");
$context->data->item2 = t("Item2");
$context->data->description = preg_replace('/[^a-z]/i', '', $data['sample_simplecontext_setting']);
$context->data->description .= '_from_configuration_sample_simplecontext_setting';
$context->title = t("Simplecontext context from config");
return $context;
}
}
else {
// $data is coming from an arg - it's just a string.
// This is used for keyword.
$context->title = $data;
$context->argument = $data;
// Make up a bogus context
$context->data = new stdClass();
$context->data->item1 = t("Item1");
$context->data->item2 = t("Item2");
// For this simple item we'll just create our data by stripping non-alpha and
// adding '_from_simplecontext_argument' to it.
$context->data->description = preg_replace('/[^a-z]/i', '', $data);
$context->data->description .= '_from_simplecontext_argument';
$context->arg_length = strlen($context->argument);
return $context;
}
}
function simplecontext_settings_form($conf, $external = FALSE) {
if (empty($conf)) {
$conf = array(
'sample_simplecontext_setting' => 'default simplecontext setting',
);
}
$form = array();
$form['sample_simplecontext_setting'] = array(
'#type' => 'textfield',
'#title' => t('Setting for simplecontext'),
'#size' => 50,
'#description' => t('An example setting that could be used to configure a context'),
'#default_value' => $conf['sample_simplecontext_setting'],
'#prefix' => '<div class="clear-block no-float">',
'#suffix' => '</div>',
);
return $form;
}
/**
* Provide a list of sub-keywords.
*
* This is used to provide keywords from the context for use in a content type,
* pane, etc.
*/
function simplecontext_convert_list() {
return array(
'item1' => t('Item1'),
'item2' => t('Item2'),
'description' => t('Description'),
);
}
/**
* Convert a context into a string to be used as a keyword by content types, etc.
*/
function simplecontext_convert($context, $type) {
switch ($type) {
case 'item1':
return $context->data->item1;
case 'item2':
return $context->data->item2;
case 'description':
return $context->data->description;
}
}
@@ -0,0 +1,214 @@
<?php
/**
* @file
* Holds the panels pages export.
*/
/**
* Implements hook_default_panel_pages()
*/
function ctools_plugin_example_default_panel_pages() {
$page = new stdClass();
$page->pid = 'new';
$page->did = 'new';
$page->name = 'ctools_plugin_example_demo_panel';
$page->title = 'Panels Plugin Example Demo Panel';
$page->access = array();
$page->path = 'demo_panel';
$page->load_flags = 1;
$page->css_id = '';
$page->arguments = array(
0 =>
array(
'name' => 'simplecontext_arg',
'id' => 1,
'default' => '404',
'title' => '',
'identifier' => 'Simplecontext arg',
'keyword' => 'simplecontext',
),
);
$page->relationships = array(
0 =>
array(
'context' => 'argument_simplecontext_arg_1',
'name' => 'relcontext_from_simplecontext',
'id' => 1,
'identifier' => 'Relcontext from Simplecontext',
'keyword' => 'relcontext',
),
);
$page->no_blocks = '0';
$page->switcher_options = array();
$page->menu = '0';
$page->contexts = array();
$display = new ctools_display();
$display->did = 'new';
$display->layout = 'threecol_33_34_33_stacked';
$display->layout_settings = array();
$display->panel_settings = array();
$display->content = array();
$display->panels = array();
$pane = new stdClass();
$pane->pid = 'new-1';
$pane->panel = 'left';
$pane->type = 'custom';
$pane->shown = '1';
$pane->subtype = 'custom';
$pane->access = array();
$pane->configuration = array(
'style' => 'default',
'override_title' => 0,
'override_title_text' => '',
'css_id' => '',
'css_class' => '',
'title' => '"No Context Item"',
'body' => 'The "no context item" content type is here to demonstrate that you can create a content_type that does not require a context. This is probably the same as just creating a custom php block on the fly, and might serve the same purpose.',
'format' => '1',
);
$pane->cache = array();
$display->content['new-1'] = $pane;
$display->panels['left'][0] = 'new-1';
$pane = new stdClass();
$pane->pid = 'new-2';
$pane->panel = 'left';
$pane->type = 'no_context_item';
$pane->shown = '1';
$pane->subtype = 'description';
$pane->access = array();
$pane->configuration = array(
'style' => 'default',
'override_title' => 0,
'override_title_text' => '',
'css_id' => '',
'css_class' => '',
'item1' => 'one',
'item2' => 'two',
'item3' => 'three',
);
$pane->cache = array();
$display->content['new-2'] = $pane;
$display->panels['left'][1] = 'new-2';
$pane = new stdClass();
$pane->pid = 'new-3';
$pane->panel = 'middle';
$pane->type = 'custom';
$pane->shown = '1';
$pane->subtype = 'custom';
$pane->access = array();
$pane->configuration = array(
'style' => 'default',
'override_title' => 0,
'override_title_text' => '',
'css_id' => '',
'css_class' => '',
'title' => 'Simplecontext',
'body' => 'The "Simplecontext" content and content type demonstrate a very basic context and how to display it.
Simplecontext includes configuration, so it can get info from the config. It can also get its information to run from a simplecontext context, generated either from an arg to the panels page or via explicitly adding a context to the page.',
'format' => '1',
);
$pane->cache = array();
$display->content['new-3'] = $pane;
$display->panels['middle'][0] = 'new-3';
$pane = new stdClass();
$pane->pid = 'new-4';
$pane->panel = 'middle';
$pane->type = 'simplecontext_item';
$pane->shown = '1';
$pane->subtype = 'description';
$pane->access = array(
0 => '2',
1 => '4',
);
$pane->configuration = array(
'context' => 'argument_simplecontext_arg_1',
'style' => 'default',
'override_title' => 0,
'override_title_text' => '',
'css_id' => '',
'css_class' => '',
'config_item_1' => 'simplecontext called from arg',
);
$pane->cache = array();
$display->content['new-4'] = $pane;
$display->panels['middle'][1] = 'new-4';
$pane = new stdClass();
$pane->pid = 'new-5';
$pane->panel = 'right';
$pane->type = 'custom';
$pane->shown = '1';
$pane->subtype = 'custom';
$pane->access = array();
$pane->configuration = array(
'style' => 'default',
'override_title' => 0,
'override_title_text' => '',
'css_id' => '',
'css_class' => '',
'title' => 'Relcontext',
'body' => 'The relcontext content_type gets its data from a relcontext, which is an example of a relationship. This panel should be run with an argument like "/xxx", which allows the simplecontext to get its context, and then the relcontext is configured in this panel to get (create) its data from the simplecontext.',
'format' => '1',
);
$pane->cache = array();
$display->content['new-5'] = $pane;
$display->panels['right'][0] = 'new-5';
$pane = new stdClass();
$pane->pid = 'new-6';
$pane->panel = 'right';
$pane->type = 'relcontext_item';
$pane->shown = '1';
$pane->subtype = 'description';
$pane->access = array();
$pane->configuration = array(
'context' => 'relationship_relcontext_from_simplecontext_1',
'style' => 'default',
'override_title' => 0,
'override_title_text' => '',
'css_id' => '',
'css_class' => '',
'config_item_1' => 'default1',
);
$pane->cache = array();
$display->content['new-6'] = $pane;
$display->panels['right'][1] = 'new-6';
$pane = new stdClass();
$pane->pid = 'new-7';
$pane->panel = 'top';
$pane->type = 'custom_php';
$pane->shown = '1';
$pane->subtype = 'custom_php';
$pane->access = array();
$pane->configuration = array(
'style' => 'default',
'override_title' => 0,
'override_title_text' => '',
'css_id' => '',
'css_class' => '',
'title' => '',
'body' => '$arg = arg(1);
$arg0 = arg(0);
if (!$arg) {
$block->content = <<<END
<em>This page is intended to run with an arg and you don\'t have one.</em>
<br />
Without an arg, the page doesn\'t have any context.
<br />Please try something like "/$arg0/xxx"
END;
$block->title = "This is intended to run with an argument";
} else {
$block->content = "The arg for this page is \'$arg\'";
}',
);
$pane->cache = array();
$display->content['new-7'] = $pane;
$display->panels['top'][0] = 'new-7';
$page->display = $display;
$page->displays = array();
$pages['ctools_plugin_example'] = $page;
return $pages;
}
@@ -0,0 +1,50 @@
<?php
/**
* @file
*
* Sample relationship plugin.
*
* We take a simplecontext, look in it for what we need to make a relcontext, and make it.
* In the real world, this might be getting a taxonomy id from a node, for example.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t("Relcontext from simplecontext"),
'keyword' => 'relcontext',
'description' => t('Adds a relcontext from existing simplecontext.'),
'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'),
'context' => 'ctools_relcontext_from_simplecontext_context',
'settings form' => 'ctools_relcontext_from_simplecontext_settings_form',
);
/**
* Return a new context based on an existing context.
*/
function ctools_relcontext_from_simplecontext_context($context = NULL, $conf) {
// If unset it wants a generic, unfilled context, which is just NULL.
if (empty($context->data)) {
return ctools_context_create_empty('relcontext', NULL);
}
// You should do error-checking here.
// Create the new context from some element of the parent context.
// In this case, we'll pass in the whole context so it can be used to
// create the relcontext.
return ctools_context_create('relcontext', $context);
}
/**
* Settings form for the relationship.
*/
function ctools_relcontext_from_simplecontext_settings_form($conf) {
// We won't configure it in this case.
return array();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
<p>The Chaos Tool Suite is a series of tools for developers to make code that I've found to be very useful to Views and Panels more readily available. Certain methods of doing things, particularly with AJAX, exportable objects and a plugin system, are proving to be ideas that are useful outside of just Views and Panels. This module does not offer much directly to the end user, but instead, creates a library for other modules to use. If you are an end user and some module asked you to install the CTools suite, then this is far as you really need to go. If you're a developer and are interested in these tools, read on!</p>
<h2>Tools provided by CTools</h2>
<dl>
<dt><a href="&topic:ctools/plugins&">Plugins</a></dt>
<dd>The plugins tool allows a module to allow <b>other</b> modules (and themes!) to provide plugins which provide some kind of functionality or some kind of task. For example, in Panels there are several types of plugins: Content types (which are like blocks), layouts (which are page layouts) and styles (which can be used to style a panel). Each plugin is represented by a .inc file, and the functionality they offer can differ wildly.</dd>
<dt><a href="&topic:ctools/context&">Context</a></dt>
<dd>Context is the idea that the objects that are used in page generation have more value than simply creating a single piece of output. Instead, contexts can be used to create multiple pieces of content that can all be put onto the page. Additionally, contexts can be used to derive other contexts via relationships, such as determining the node author and displaying data about the new context.</dd>
<dt><a href="&topic:ctools/ajax&">AJAX Tools</a></dt>
<dd>AJAX (also known as AHAH) is a method of allowing the browser and the server to communicate without requiring a page refresh. It can be used to create complicated interactive forms, but it is somewhat difficult to integrate into Drupal's Form API. These tools make it easier to accomplish this goal. In addition, CTools provides a few other javascript helpers, such as a modal dialog, a collapsible div, a simple dropdown and dependent checkboxes.</dd>
<dt><a href="&topic:ctools/css&">CSS scrubbing and caching</a></dt>
<dd>Drupal comes with a fantastic array of tools to ensure HTML is safe to output but does not contain any similar tools for CSS. CTools provides a small tool to sanitize CSS, so user-input CSS code can still be safely used. It also provides a method for caching CSS for better performance.</dd>
<dt><a href="&topic:ctools/export&">Exportable objects</a></dt>
<dd>Views and Panels both use objects that can either be in code or in the database, and the objects can be exported into a piece of PHP code, so they can be moved from site to site or out of the database entirely. This library abstracts that functionality, so other modules can use this same concept for their data.</dd>
<dt><a href="&topic:ctools/form&">Form tools</a></dt>
<dd>Drupal 6's FAPI really improved over Drupal 5, and made a lot of things possible. Still, it missed a few items that were needed to make form wizards and truly dynamic AJAX forms possible. CTools includes a replacement for drupal_get_form() that has a few more options and allows the caller to examine the $form_state once the form has completed.</dd>
<dt><a href="&topic:ctools/wizard&">Form wizards</a></dt>
<dd>Finally! An easy way to have form wizards, which is any 'form' that is actually a string of forms that build up to a final conclusion. The form wizard supports a single entry point, the ability to choose whether or not the user can go forward/back/up on the form and easy callbacks to handle the difficult job of dealing with data in between forms.</dd>
<dt><a href="&topic:ctools/object-cache&">Temporary object cache</a></dt>
<dd>For normal forms, all of the data needed for an object is stored in the form so that the browser handles a lot of the work. For multi-step and ajax forms, however, this is impractical, and letting the browser store data can be insecure. The object cache provides a non-volatile location to store temporary data while the form is being worked on. This is much safer than the standard Drupal caching mechanism, which is volatile, meaning it can be cleared at any time and any system using it must be capable of recreating the data that was there. This system also allows for object locking, since any object which has an item in the cache from another person can be assumed to be 'locked for editing'.</dd>
</dl>
@@ -0,0 +1 @@
<p>To be written.</p>
@@ -0,0 +1,12 @@
<p>Access plugins allow context based access control to pages.</p>
<pre> 'title' => Title of the plugin
'description' => Description of the plugin
'callback' => callback to see if there is access is available. params: $conf, $contexts, $account
'required context' => zero or more required contexts for this access plugin
'default' => an array of defaults or a callback giving defaults
'settings form' => settings form. params: &$form, &$form_state, $conf
settings form validate
settings form submit
</pre>
<p><strong>Warning:</strong> your settings array will be stored <strong>in the meny system</strong> to reduce loads, so be <strong>trim</strong>.</p>
@@ -0,0 +1,14 @@
<p>Arguments create a context from external input, which is assumed to be a string as though it came from a URL element.</p>
<pre>'title' => title
'description' => Description
'keyword' => Default keyword for the context
'context' => Callback to create the context. Params: $arg = NULL, $conf = NULL, $empty = FALSE
'default' => either an array of default settings or a string which is a callback or null to not use.
'settings form' => params: $form, $form_state, $conf -- gets the whole form. Should put anything it wants to keep automatically in $form['settings']
'settings form validate' => params: $form, $form_state
'settings form submit' => params: $form, $form_state
'criteria form' => params: $form, &$form_state, $conf, $argument, $id -- gets the whole argument. It should only put form widgets in $form[$id]. $conf may not be properly initialized so always guard against this due to arguments being changed and handlers not being updated to match.
+ submit + validate
'criteria select' => returns true if the selected criteria matches the context. params: $context, $conf
</pre>
@@ -0,0 +1,157 @@
<p>The CTools pluggable content system provides various pieces of content as discrete bits of data that can be added to other applications, such as Panels or Dashboard via the UI. Whatever the content is added to stores the configuration for that individual piece of content, and provides this to the content.</p>
<p>Each content type plugin will be contained in a .inc file, with subsidiary files, if necessary, in or near the same directory. Each content type consists of some information and one or more subtypes, which all use the same renderer. Subtypes are considered to be instances of the type. For example, the 'views' content type would have each view in the system as a subtype. Many content types will have exactly one subtype.</p>
<p>Because the content and forms can be provided via ajax, the plugin also provides a list of CSS and JavaScript information that should be available on whatever page the content or forms may be AJAXed onto.</p>
<p>For the purposes of selecting content from the UI, each content subtype will have the following information:</p>
<ul>
<li>A title</li>
<li>A short description</li>
<li>A category [Do we want to add hierarchy categories? Do we want category to be more than just a string?]</li>
<li>An icon [do we want multiple icons? This becomes a hefty requirement]</li>
</ul>
<p>Each piece of content provides one or more configuration forms, if necessary, and the system that includes the content will handle the data storage. These forms can be provided in sequence as wizards or as extra forms that can be accessed through advanced administration.</p>
<p>The plugin for a content type should contain:</p>
<dl>
<dt>title</dt>
<dd>For use on the content permissions screen.</dd>
<dt>content types</dt>
<dd>Either an array of content type definitions, or a callback that will return content type definitions. This callback will get the plugin definition as an argument.</dd>
<dt>content type</dt>
<dd>[Optional] Provide a single content type definition. This is only necessary if content types might be intensive.</dd>
<dt>render callback</dt>
<dd>The callback to render the content. Parameters:
<dl>
<dt>$subtype</dt>
<dd>The name of the subtype being rendered. NOT the loaded subtype data.</dd>
<dt>$conf</dt>
<dd>The stored configuration for the content.</dd>
<dt>$args</dt>
<dd>Any arguments passed.</dd>
<dt>$context</dt>
<dd>An array of contexts requested by the required contexts and assigned by the configuration step.</dd>
<dt>$incoming_content</dt>
<dd>Any 'incoming content' if this is a wrapper.</dd>
</dl>
</dd>
<dt>admin title</dt>
<dd>A callback to provide the administrative title. If it is not a function, then it will be counted as a string to use as the admin title.</dd>
<dt>admin info</dt>
<dd>A callback to provide administrative information about the content, to be displayed when manipulating the content. It should contain a summary of configuration.</dd>
<dt>edit form</dt>
<dd>Either a single form ID or an array of forms *keyed* by form ID with the value to be used as the title of the form. %title me be used as a placeholder for the administrative title if necessary.
Example:
<pre>array(
'ctools_example_content_form_second' =&gt; t('Configure first form'),
'ctools_example_content_form_first' =&gt; t('Configure second form'),
),
</pre>
The first form will always have required configuration added to it. These forms are normal FAPI forms, but you do not need to provide buttons, these will be added by the form wizard.
</dd>
<dt>add form</dt>
<dd>[Optional] If different from the edit forms, provide them here in the same manner. Also may be set to FALSE to not have an add form.</dd>
<dt>css</dt>
<dd>A file or array of CSS files that are necessary for the content.</dd>
<dt>js</dt>
<dd>A file or array of javascript files that are necessary for the content to be displayed.</dd>
<dt>admin css</dt>
<dd>A file or array of CSS files that are necessary for the forms.</dd>
<dt>admin js</dt>
<dd>A file or array of JavaScript files that are necessary for the forms.</dd>
<dt>extra forms</dt>
<dd>An array of form information to handle extra administrative forms.</dd>
<dt>no title override</dt>
<dd>Set to TRUE if the title cannot be overridden.</dd>
<dt>single</dt>
<dd>Set to TRUE if this content provides exactly one subtype.</dd>
<dt>render last</dt>
<dd>Set to true if for some reason this content needs to render after other content. This is primarily used for forms to ensure that render order is correct.</dd>
</dl>
<p>TODO: many of the above callbacks can be assumed based upon patterns: modulename + '_' + name + '_' + function. i.e, render, admin_title, admin_info, etc.</p>
<p>TODO: Some kind of simple access control to easily filter out content.</p>
<p>The subtype definition should contain:</p>
<dl>
<dt>title</dt>
<dd>The title of the subtype.</dd>
<dt>icon</dt>
<dd>The icon to display for the subtype.</dd>
<dt>path</dt>
<dd>The path for the icon if it is not in the same directory as the plugin.</dd>
<dt>description</dt>
<dd>The short description of the subtype, to be used when selecting it in the UI.</dd>
<dt>category</dt>
<dd>Either a text string for the category, or an array of the text string followed by the category weight.</dd>
<dt>required context [Optional]</dt>
<dd>Either a ctools_context_required or ctools_context_optional or array of contexts for this content. If omitted, no contexts are used.</dd>
<dt>create content access [Optional]</dt>
<dd>An optional callback to determine if a user can access this subtype. The callback will receive two arguments, the type and subtype.</dd>
</dl>
<h2>Rendered content</h2>
<p>Rendered content is a little more than just HTML.</p>
<dl>
<dt>title</dt>
<dd>The safe to render title of the content.</dd>
<dt>content</dt>
<dd>The safe to render HTML content.</dd>
<dt>links</dt>
<dd>An array of links associated with the content suitable for theme('links').</dd>
<dt>more</dt>
<dd>An optional 'more' link (destination only)</dd>
<dt>admin_links</dt>
<dd>Administrative links associated with the content, suitable for theme('links').</dd>
<dt>feeds</dt>
<dd>An array of feed icons or links associated with the content. Each member of the array is rendered HTML.</dd>
<dt>type</dt>
<dd>The content type.</dd>
<dt>subtype</dt>
<dd>The content subtype. These two may be used together as module-delta for block style rendering.</dd>
</dl>
<h2>Todo: example</h2>
<p>Todo after implementations are updated to new version.</p>
@@ -0,0 +1,13 @@
<p>Context plugin data:</p>
<pre>
'title' => Visible title
'description' => Description of context
'context' => Callback to create a context. Params: $empty, $data = NULL, $conf = FALSE
'settings form' => Callback to show a context setting form. Params: ($conf, $external = FALSE)
'settings form validate' => params: ($form, &$form_values, &$form_state)
'settings form submit' => params: 'ctools_context_node_settings_form_submit',
'keyword' => The default keyword to use.
'context name' => The unique identifier for this context for use by required context checks.
'no ui' => if TRUE this context cannot be selected.
</pre>
@@ -0,0 +1,13 @@
<p>Relationship plugin data:</p>
<pre>
'title' => The title to display.
'description' => Description to display.
'keyword' => Default keyword for the context created by this relationship.
'required context' => One or more ctools_context_required/optional objects
describing the context input.
new panels_required_context(t('Node'), 'node'),
'context' => The callback to create the context. Params: ($context = NULL, $conf)
'settings form' => Settings form. Params: $conf
'settings form validate' => Validate.
</pre>
@@ -0,0 +1 @@
<p>To be written.</p>
@@ -0,0 +1,97 @@
[advanced help settings]
line break = TRUE
[about]
title = About Chaos Tool Suite
weight = -100
[context]
title = Context tool
weight = -40
[context-access]
title = Context based access control plugins
parent = context
[context-context]
title = Context plugins
parent = context
[context-arguments]
title = Argument plugins
parent = context
[context-relationships]
title = Relationship plugins
parent = context
[context-content]
title = Content plugins
parent = context
[css]
title = CSS scrubbing and caching tool
[menu]
title = Miscellaneous menu helper tool
[plugins]
title = Plugins and APIs tool
weight = -50
[plugins-api]
title = Implementing APIs
parent = plugins
[plugins-creating]
title = Creating plugins
parent = plugins
[plugins-implementing]
title = Implementing plugins
parent = plugins
[export]
title = Exportable objects tool
[export-ui]
title = Exportable objects UI creator
[form]
title = Form tools
[wizard]
title = Form wizard tool
[ajax]
title = AJAX and Javascript helper tools
weight = -30
[modal]
title = Javascript modal tool
parent = ajax
[collapsible-div]
title = Javascript collapsible DIV
parent = ajax
[dropdown]
title = Javascript dropdown
parent = ajax
[dropbutton]
title = Javascript dropbutton
parent = ajax
[dependent]
title = Dependent checkboxes and radio buttons
parent = ajax
[object-cache]
title = Temporary object caching
; A bunch of this stuff we'll put in panels.
[plugins-content]
title = Creating content type plugins
parent = panels%api

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