| 1 |
<?php
|
| 2 |
// $Id: panels.module,v 1.27.2.5 2008/11/17 20:46:01 merlinofchaos Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file panels.module
|
| 6 |
*
|
| 7 |
* Core functionality for the Panels engine.
|
| 8 |
*/
|
| 9 |
|
| 10 |
/**
|
| 11 |
* Error code bitmask used for identifying argument behavior at runtime.
|
| 12 |
*/
|
| 13 |
define('PANELS_ARG_IS_BAD', 1);
|
| 14 |
define('PANELS_ARG_USE_FALLBACK', 1 << 1);
|
| 15 |
|
| 16 |
/**
|
| 17 |
* Returns the API version of Panels. This didn't exist in 1.
|
| 18 |
*
|
| 19 |
* @return An array with the major and minor versions
|
| 20 |
*/
|
| 21 |
function panels_api_version() {
|
| 22 |
return array(2, 0);
|
| 23 |
}
|
| 24 |
|
| 25 |
function panels_theme() {
|
| 26 |
$theme = array();
|
| 27 |
$theme['panels_layout_link'] = array(
|
| 28 |
'arguments' => array('title', 'id', 'image', 'link'),
|
| 29 |
);
|
| 30 |
$theme['panels_layout_icon'] = array(
|
| 31 |
'arguments' => array('id', 'image', 'title' => NULL),
|
| 32 |
);
|
| 33 |
$theme['panels_imagebutton'] = array(
|
| 34 |
'arguments' => array('element'),
|
| 35 |
);
|
| 36 |
$theme['panels_edit_display_form'] = array(
|
| 37 |
'arguments' => array('form'),
|
| 38 |
'file' => 'includes/display-edit.inc',
|
| 39 |
);
|
| 40 |
$theme['panels_edit_layout_form_choose'] = array(
|
| 41 |
'arguments' => array('form'),
|
| 42 |
'file' => 'includes/display-edit.inc',
|
| 43 |
);
|
| 44 |
$theme['panels_pane'] = array(
|
| 45 |
'arguments' => array('content', 'pane', 'display'),
|
| 46 |
'file' => 'includes/display-render.inc',
|
| 47 |
);
|
| 48 |
$theme['panels_common_content_list'] = array(
|
| 49 |
'arguments' => array('display'),
|
| 50 |
'file' => 'includes/common.inc',
|
| 51 |
);
|
| 52 |
$theme['panels_common_context_list'] = array(
|
| 53 |
'arguments' => array('object'),
|
| 54 |
'file' => 'includes/common.inc',
|
| 55 |
);
|
| 56 |
$theme['panels_common_context_item_form'] = array(
|
| 57 |
'arguments' => array('form'),
|
| 58 |
'file' => 'includes/common.inc',
|
| 59 |
);
|
| 60 |
$theme['panels_common_context_item_row'] = array(
|
| 61 |
'arguments' => array('type', 'form', 'position', 'count', 'with_tr' => TRUE),
|
| 62 |
'file' => 'includes/common.inc',
|
| 63 |
);
|
| 64 |
$theme['panels_dnd'] = array(
|
| 65 |
'arguments' => array('content'),
|
| 66 |
'file' => 'includes/display-edit.inc',
|
| 67 |
'function' => 'theme_panels_dnd',
|
| 68 |
);
|
| 69 |
$theme['panels_panel_dnd'] = array(
|
| 70 |
'arguments' => array('content', 'area', 'label', 'footer'),
|
| 71 |
'file' => 'includes/display-edit.inc',
|
| 72 |
'function' => 'theme_panels_panel_dnd',
|
| 73 |
);
|
| 74 |
$theme['panels_pane_dnd'] = array(
|
| 75 |
'arguments' => array('block', 'id', 'label', 'left_buttons' => NULL, 'buttons' => NULL),
|
| 76 |
'file' => 'includes/display-edit.inc',
|
| 77 |
);
|
| 78 |
$theme['panels_pane_collapsible'] = array(
|
| 79 |
'arguments' => array('block'),
|
| 80 |
'file' => 'includes/display-edit.inc',
|
| 81 |
);
|
| 82 |
$theme['profile_fields_pane'] = array(
|
| 83 |
'arguments' => array('category' => NULL, 'vars' => NULL),
|
| 84 |
'template' => 'content_types/profile_fields_pane',
|
| 85 |
);
|
| 86 |
|
| 87 |
// Register layout and style themes on behalf of all of these items.
|
| 88 |
panels_load_include('plugins');
|
| 89 |
|
| 90 |
// No need to worry about files; the plugin has to already be loaded for us
|
| 91 |
// to even know what the theme function is, so files will be auto included.
|
| 92 |
$layouts = panels_get_layouts();
|
| 93 |
foreach ($layouts as $name => $data) {
|
| 94 |
if (!empty($data['theme'])) {
|
| 95 |
$theme[$data['theme']] = array(
|
| 96 |
'arguments' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL),
|
| 97 |
'path' => $data['path'],
|
| 98 |
);
|
| 99 |
|
| 100 |
// if no theme function exists, assume template.
|
| 101 |
if (!function_exists("theme_$data[theme]")) {
|
| 102 |
$theme[$data['theme']]['template'] = str_replace('_', '-', $data['theme']);
|
| 103 |
}
|
| 104 |
}
|
| 105 |
}
|
| 106 |
|
| 107 |
$styles = panels_get_styles();
|
| 108 |
foreach ($styles as $name => $data) {
|
| 109 |
if (!empty($data['render pane'])) {
|
| 110 |
$theme[$data['render pane']] = array(
|
| 111 |
'arguments' => array('content' => NULL, 'pane' => NULL, 'display' => NULL),
|
| 112 |
);
|
| 113 |
}
|
| 114 |
if (!empty($data['render panel'])) {
|
| 115 |
$theme[$data['render panel']] = array(
|
| 116 |
'arguments' => array('display' => NULL, 'panel_id' => NULL, 'panes' => NULL, 'settings' => NULL),
|
| 117 |
);
|
| 118 |
}
|
| 119 |
|
| 120 |
}
|
| 121 |
|
| 122 |
return $theme;
|
| 123 |
}
|
| 124 |
|
| 125 |
/**
|
| 126 |
* Implementation of hook_menu
|
| 127 |
*/
|
| 128 |
function panels_menu() {
|
| 129 |
$items = array();
|
| 130 |
|
| 131 |
// Provide some common options to reduce code repetition.
|
| 132 |
// By using array addition and making sure these are the rightmost
|
| 133 |
// value, they won't override anything already set.
|
| 134 |
$base = array(
|
| 135 |
'access arguments' => array('access content'),
|
| 136 |
'type' => MENU_CALLBACK,
|
| 137 |
'file' => 'includes/display-edit.inc',
|
| 138 |
);
|
| 139 |
|
| 140 |
$items['panels/ajax/add-pane'] = array(
|
| 141 |
'page callback' => 'panels_ajax_add_pane_choose',
|
| 142 |
) + $base;
|
| 143 |
$items['panels/ajax/add-pane-config'] = array(
|
| 144 |
'page callback' => 'panels_ajax_add_pane_config',
|
| 145 |
) + $base;
|
| 146 |
$items['panels/ajax/configure'] = array(
|
| 147 |
'page callback' => 'panels_ajax_configure_pane',
|
| 148 |
) + $base;
|
| 149 |
$items['panels/ajax/show'] = array(
|
| 150 |
'page callback' => 'panels_ajax_toggle_shown',
|
| 151 |
'page arguments' => array('show'),
|
| 152 |
) + $base;
|
| 153 |
$items['panels/ajax/hide'] = array(
|
| 154 |
'page callback' => 'panels_ajax_toggle_shown',
|
| 155 |
'page arguments' => array('hide'),
|
| 156 |
) + $base;
|
| 157 |
$items['panels/ajax/cache-method'] = array(
|
| 158 |
'page callback' => 'panels_ajax_cache_method',
|
| 159 |
) + $base;
|
| 160 |
$items['panels/ajax/cache-settings'] = array(
|
| 161 |
'page callback' => 'panels_ajax_cache_settings',
|
| 162 |
) + $base;
|
| 163 |
|
| 164 |
// For panel settings on the edit layout settings page
|
| 165 |
$items['panels/ajax/style-settings'] = array(
|
| 166 |
'page callback' => 'panels_ajax_style_settings',
|
| 167 |
'file' => 'includes/display-layout-settings.inc',
|
| 168 |
) + $base;
|
| 169 |
|
| 170 |
// Non-display editor callbacks
|
| 171 |
$items['panels/node/autocomplete'] = array(
|
| 172 |
'title' => 'Autocomplete node',
|
| 173 |
'page callback' => 'panels_node_autocomplete',
|
| 174 |
'file' => 'includes/callbacks.inc',
|
| 175 |
) + $base;
|
| 176 |
|
| 177 |
// For context add/configure calls in common-context.inc
|
| 178 |
$items['panels/ajax/context-add'] = array(
|
| 179 |
'page callback' => 'panels_ajax_context_item_add',
|
| 180 |
'file' => 'includes/common-context.inc',
|
| 181 |
) + $base;
|
| 182 |
$items['panels/ajax/context-configure'] = array(
|
| 183 |
'page callback' => 'panels_ajax_context_item_edit',
|
| 184 |
'file' => 'includes/common-context.inc',
|
| 185 |
) + $base;
|
| 186 |
$items['panels/ajax/context-delete'] = array(
|
| 187 |
'page callback' => 'panels_ajax_context_item_delete',
|
| 188 |
'file' => 'includes/common-context.inc',
|
| 189 |
) + $base;
|
| 190 |
|
| 191 |
// Provide a nice location for a panels admin panel.
|
| 192 |
$items['admin/panels'] = array(
|
| 193 |
'title' => 'Panels',
|
| 194 |
'access arguments' => array('access administration pages'),
|
| 195 |
'page callback' => 'panels_admin_page',
|
| 196 |
'file' => 'includes/callbacks.inc',
|
| 197 |
'description' => 'Administer items related to the Panels module.',
|
| 198 |
);
|
| 199 |
|
| 200 |
return $items;
|
| 201 |
}
|
| 202 |
|
| 203 |
/**
|
| 204 |
* Implementation of hook_init()
|
| 205 |
*/
|
| 206 |
function panels_init() {
|
| 207 |
drupal_add_css(panels_get_path('css/panels.css'));
|
| 208 |
drupal_add_js(panels_get_path('js/panels.js'));
|
| 209 |
}
|
| 210 |
|
| 211 |
/**
|
| 212 |
* Load a panels include file.
|
| 213 |
*/
|
| 214 |
function panels_load_include($include, $path = 'includes/') {
|
| 215 |
static $loaded = array();
|
| 216 |
if (empty($loaded["$path$include.inc"])) {
|
| 217 |
require_once './' . panels_get_path("$path$include.inc");
|
| 218 |
$loaded["$path$include.inc"] = TRUE;
|
| 219 |
}
|
| 220 |
}
|
| 221 |
|
| 222 |
/**
|
| 223 |
* panels path helper function
|
| 224 |
*/
|
| 225 |
function panels_get_path($file, $base_path = FALSE, $module = 'panels') {
|
| 226 |
$output = $base_path ? base_path() : '';
|
| 227 |
return $output . drupal_get_path('module', $module) . '/' . $file;
|
| 228 |
}
|
| 229 |
|
| 230 |
/**
|
| 231 |
* Implementation of hook_perm
|
| 232 |
*/
|
| 233 |
function panels_perm() {
|
| 234 |
return array(
|
| 235 |
'view all panes',
|
| 236 |
'view pane admin links',
|
| 237 |
'administer pane visibility',
|
| 238 |
'administer pane access',
|
| 239 |
'administer advanced pane settings',
|
| 240 |
'use panels caching features'
|
| 241 |
);
|
| 242 |
}
|
| 243 |
|
| 244 |
/**
|
| 245 |
* Get an object from cache.
|
| 246 |
*/
|
| 247 |
function panels_cache_get($obj, $did, $skip_cache = FALSE) {
|
| 248 |
static $cache = array();
|
| 249 |
$key = "$obj:$did";
|
| 250 |
if ($skip_cache) {
|
| 251 |
unset($cache[$key]);
|
| 252 |
}
|
| 253 |
|
| 254 |
if (!array_key_exists($key, $cache)) {
|
| 255 |
$data = db_fetch_object(db_query("SELECT * FROM {panels_object_cache} WHERE sid = '%s' AND obj = '%s' AND did = %d", session_id(), $obj, $did));
|
| 256 |
if ($data) {
|
| 257 |
$cache[$key] = unserialize($data->data);
|
| 258 |
}
|
| 259 |
}
|
| 260 |
return isset($cache[$key]) ? $cache[$key] : NULL;
|
| 261 |
}
|
| 262 |
|
| 263 |
/**
|
| 264 |
* Save the edited object into the cache.
|
| 265 |
*/
|
| 266 |
function panels_cache_set($obj, $did, $cache) {
|
| 267 |
panels_cache_clear($obj, $did);
|
| 268 |
db_query("INSERT INTO {panels_object_cache} (sid, obj, did, data, timestamp) VALUES ('%s', '%s', %d, '%s', %d)", session_id(), $obj, $did, serialize($cache), time());
|
| 269 |
}
|
| 270 |
|
| 271 |
/**
|
| 272 |
* Clear a object from the cache; used if the editing is aborted.
|
| 273 |
*/
|
| 274 |
function panels_cache_clear($obj, $did) {
|
| 275 |
db_query("DELETE FROM {panels_object_cache} WHERE sid = '%s' AND obj = '%s' AND did = %d", session_id(), $obj, $did);
|
| 276 |
}
|
| 277 |
|
| 278 |
/**
|
| 279 |
* Implementation of hook_cron. Clean up old caches.
|
| 280 |
*/
|
| 281 |
function panels_cron() {
|
| 282 |
// delete anything 7 days old or more.
|
| 283 |
db_query("DELETE FROM {panels_object_cache} WHERE timestamp < %d", time() - (86400 * 7));
|
| 284 |
}
|
| 285 |
|
| 286 |
// ---------------------------------------------------------------------------
|
| 287 |
// panels display editing
|
| 288 |
|
| 289 |
/**
|
| 290 |
* @defgroup mainapi Functions comprising the main panels API
|
| 291 |
* @{
|
| 292 |
*/
|
| 293 |
|
| 294 |
/**
|
| 295 |
* Main API entry point to edit a panel display.
|
| 296 |
*
|
| 297 |
* Sample implementations utiltizing the the complex $destination behavior can be found
|
| 298 |
* in panels_page_edit_content() and, in a separate contrib module, OG Blueprints
|
| 299 |
* (http://drupal.org/project/og_blueprints), og_blueprints_blueprint_edit().
|
| 300 |
*
|
| 301 |
* @ingroup mainapi
|
| 302 |
*
|
| 303 |
* @param object $display instanceof panels_display \n
|
| 304 |
* A fully loaded panels $display object, as returned from panels_load_display().
|
| 305 |
* Merely passing a did is NOT sufficient. \n
|
| 306 |
* Note that 'fully loaded' means the $display must already be loaded with any contexts
|
| 307 |
* the caller wishes to have set for the display.
|
| 308 |
* @param mixed $destination \n
|
| 309 |
* The redirect destination that the user should be taken to on form submission or
|
| 310 |
* cancellation. With panels_edit, $destination has complex effects on the return
|
| 311 |
* values of panels_edit() once the form has been submitted. See the explanation of
|
| 312 |
* the return value below to understand the different types of values returned by panels_edit()
|
| 313 |
* at different stages of FAPI. Under most circumstances, simply passing in
|
| 314 |
* drupal_get_destination() is all that's necessary.
|
| 315 |
* @param array $content_types \n
|
| 316 |
* An associative array of allowed content types, typically as returned from
|
| 317 |
* panels_common_get_allowed_types(). Note that context partially governs available content types,
|
| 318 |
* so you will want to create any relevant contexts using panels_create_context() or
|
| 319 |
* panels_create_context_empty() to make sure all the appropriate content types are available.
|
| 320 |
*
|
| 321 |
* @return
|
| 322 |
* Because the functions called by panels_edit() invoke the form API, this function
|
| 323 |
* returns different values depending on the stage of form submission we're at. In Drupal 5,
|
| 324 |
* the phase of form submission is indicated by the contents of $_POST['op']. Here's what you'll
|
| 325 |
* get at different stages:
|
| 326 |
* -# If !$_POST['op']: then we're on on the initial passthrough and the form is being
|
| 327 |
* rendered, so it's the $form itself that's being returned. Because negative margins,
|
| 328 |
* a common CSS technique, bork the display editor's ajax drag-and-drop, it's important
|
| 329 |
* that the $output be printed, not returned. Use this syntax in the caller function: \n
|
| 330 |
* print theme('page', panels_edit($display, $destination, $content_types), FALSE); \n
|
| 331 |
* -# If $_POST['op'] == t('Cancel'): form submission has been cancelled. If empty($destination) == FALSE,
|
| 332 |
* then there is no return value and the panels API takes care of redirecting to $destination.
|
| 333 |
* If empty($destination) == TRUE, then there's still no return value, but the caller function
|
| 334 |
* has to take care of form redirection.
|
| 335 |
* -# If $_POST['op'] == ('Save'): the form has been submitted successfully and has run through
|
| 336 |
* panels_edit_display_submit(). $output depends on the value of $destination:
|
| 337 |
* - If empty($destination) == TRUE: $output contains the modified $display
|
| 338 |
* object, and no redirection will occur. This option is useful if the caller
|
| 339 |
* needs to perform additional operations on or with the modified $display before
|
| 340 |
* the page request is complete. Using hook_form_alter() to add an additional submit
|
| 341 |
* handler is typically the preferred method for something like this, but there
|
| 342 |
* are certain use cases where that is infeasible and $destination = NULL should
|
| 343 |
* be used instead. If this method is employed, the caller will need to handle form
|
| 344 |
* redirection. Note that having $_REQUEST['destination'] set, whether via
|
| 345 |
* drupal_get_destination() or some other method, will NOT interfere with this
|
| 346 |
* functionality; consequently, you can use drupal_get_destination() to safely store
|
| 347 |
* your desired redirect in the caller function, then simply use drupal_goto() once
|
| 348 |
* panels_edit() has done its business.
|
| 349 |
* - If empty($destination) == FALSE: the form will redirect to the URL string
|
| 350 |
* given in $destination and NO value will be returned.
|
| 351 |
*/
|
| 352 |
function panels_edit($display, $destination = NULL, $content_types = NULL) {
|
| 353 |
panels_load_include('display-edit');
|
| 354 |
panels_load_include('ajax');
|
| 355 |
panels_load_include('plugins');
|
| 356 |
return _panels_edit($display, $destination, $content_types);
|
| 357 |
}
|
| 358 |
|
| 359 |
/**
|
| 360 |
* API entry point for selecting a layout for a given display.
|
| 361 |
*
|
| 362 |
* Layout selection is nothing more than a list of radio items encompassing the available
|
| 363 |
* layouts for this display, as defined by .inc files in the panels/layouts subdirectory.
|
| 364 |
* The only real complexity occurs when a user attempts to change the layout of a display
|
| 365 |
* that has some content in it.
|
| 366 |
*
|
| 367 |
* @param object $display instanceof panels_display \n
|
| 368 |
* A fully loaded panels $display object, as returned from panels_load_display().
|
| 369 |
* Merely passing a did is NOT sufficient.
|
| 370 |
* @param string $finish
|
| 371 |
* A string that will be used for the text of the form submission button. If no value is provided,
|
| 372 |
* then the form submission button will default to t('Save').
|
| 373 |
* @param mixed $destination
|
| 374 |
* Basic usage is a string containing the URL that the form should redirect to upon submission.
|
| 375 |
* For a discussion of advanced usages, see panels_edit().
|
| 376 |
* @param mixed $allowed_layouts
|
| 377 |
* Allowed layouts has three different behaviors that depend on which of three value types
|
| 378 |
* are passed in by the caller:
|
| 379 |
* #- if $allowed_layouts instanceof panels_allowed_layouts (includes subclasses): the most
|
| 380 |
* complex use of the API. The caller is passing in a loaded panels_allowed_layouts object
|
| 381 |
* that the client module previously created and stored somewhere using a custom storage
|
| 382 |
* mechanism.
|
| 383 |
* #- if is_string($allowed_layouts): the string will be used in a call to variable_get() which
|
| 384 |
* will call the $allowed_layouts . '_allowed_layouts' var. If the data was stored properly
|
| 385 |
* in the system var, the $allowed_layouts object will be unserialized and recreated.
|
| 386 |
* @see panels_common_set_allowed_layouts()
|
| 387 |
* #- if is_null($allowed_layouts): the default behavior, which also provides backwards
|
| 388 |
* compatibility for implementations of the Panels2 API written before beta4. In this case,
|
| 389 |
* a dummy panels_allowed_layouts object is created which does not restrict any layouts.
|
| 390 |
* Subsequent behavior is indistinguishable from pre-beta4 behavior.
|
| 391 |
*
|
| 392 |
* @return
|
| 393 |
* Can return nothing, or a modified $display object, or a redirection string; return values for the
|
| 394 |
* panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
|
| 395 |
* @see panels_edit()
|
| 396 |
*/
|
| 397 |
function panels_edit_layout($display, $finish, $destination = NULL, $allowed_layouts = NULL) {
|
| 398 |
panels_load_include('display-layout');
|
| 399 |
panels_load_include('plugins');
|
| 400 |
return _panels_edit_layout($display, $finish, $destination, $allowed_layouts);
|
| 401 |
}
|
| 402 |
|
| 403 |
/**
|
| 404 |
* API entry point for configuring the layout settings for a given display.
|
| 405 |
*
|
| 406 |
* For all layouts except Flexible, the layout settings form allows the user to select styles,
|
| 407 |
* as defined by .inc files in the panels/styles subdirectory, for the panels in their display.
|
| 408 |
* For the Flexible layout, the layout settings form allows the user to provide dimensions
|
| 409 |
* for their flexible layout in addition to applying styles to panels.
|
| 410 |
*
|
| 411 |
* @param object $display instanceof panels_display \n
|
| 412 |
* A fully loaded panels $display object, as returned from panels_load_display().
|
| 413 |
* Merely passing a did is NOT sufficient.
|
| 414 |
* @param string $finish
|
| 415 |
* A string that will be used for the text of (one of) the form submission button(s). Note that
|
| 416 |
* panels will NOT wrap $finish in t() for you, so your caller should make sure to do so. \n
|
| 417 |
* The submit behavior of the form is primarily governed by the value of $destination (see
|
| 418 |
* below), but is secondarily governed by $finish as follows:
|
| 419 |
* -# If $finish != t('Save'), then two #submit buttons will be present: one with the button
|
| 420 |
* text t('Save'), and the other with the button text $finish. .
|
| 421 |
* - Clicking the 'Save' button will save any changes on the form to the $display object and
|
| 422 |
* keep the user on the same editing page.
|
| 423 |
* - Clicking the $finish button will also save the $display object, but the user will be
|
| 424 |
* redirected to the URL specified in $destination.
|
| 425 |
* -# If $finish == t('Save'), then there is only one button, still called t('Save'), but it
|
| 426 |
* mimics the behavior of the $finish button above by redirecting the user away from the form.
|
| 427 |
* @param mixed $destination
|
| 428 |
* Basic usage is a string containing the URL that the form should redirect to upon submission.
|
| 429 |
* For a discussion of advanced usages that rely on NULL values for $destination, see the
|
| 430 |
* panels_edit() documentation.
|
| 431 |
* @param mixed $title
|
| 432 |
* The $title variable has three modes of operation:
|
| 433 |
* -# If $title == FALSE (the default), then no widget will appear on the panels_edit_layout_settings form
|
| 434 |
* allowing the user to select a title, and other means for setting page titles will take precedent. If
|
| 435 |
* no other means are used to provide a title, then the title will be hidden when rendering the $display.
|
| 436 |
* -# If $title == TRUE, then two widgets will appear on the panels_edit_layout_settings form allowing the
|
| 437 |
* user to input a title specific to this $display, as well as a checkbox enabling the user to disable
|
| 438 |
* page titles entirely for this $display object.
|
| 439 |
* -# If $title == (string), then the behavior is very similar to mode 2, but the widget description
|
| 440 |
* on the title textfield will indicate that the $title string will be used as the default page title
|
| 441 |
* if none is provided on this form. When utilizing this option, note that the panels API can only
|
| 442 |
* provide the data for these values; you must implement the appropriate conditionals to make it true.
|
| 443 |
*
|
| 444 |
* @return
|
| 445 |
* Can return nothing, or a modified $display object, or a redirection string; return values for the
|
| 446 |
* panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
|
| 447 |
* @see panels_edit()
|
| 448 |
*/
|
| 449 |
function panels_edit_layout_settings($display, $finish, $destination = NULL, $title = FALSE) {
|
| 450 |
panels_load_include('display-layout-settings');
|
| 451 |
panels_load_include('ajax');
|
| 452 |
panels_load_include('plugins');
|
| 453 |
return _panels_edit_layout_settings($display, $finish, $destination, $title);
|
| 454 |
}
|
| 455 |
|
| 456 |
|
| 457 |
// ---------------------------------------------------------------------------
|
| 458 |
// panels database functions
|
| 459 |
|
| 460 |
/**
|
| 461 |
* Forms the basis of a panel display
|
| 462 |
*
|
| 463 |
*/
|
| 464 |
class panels_display {
|
| 465 |
var $args = array();
|
| 466 |
var $content = array();
|
| 467 |
var $panels = array();
|
| 468 |
var $incoming_content = NULL;
|
| 469 |
var $css_id = NULL;
|
| 470 |
var $context = array();
|
| 471 |
var $layout_settings = array();
|
| 472 |
var $panel_settings = array();
|
| 473 |
var $cache = array();
|
| 474 |
var $title = '';
|
| 475 |
var $hide_title = 0;
|
| 476 |
var $layout = '';
|
| 477 |
|
| 478 |
function add_pane($pane, $location = FALSE) {
|
| 479 |
$pane->pid = $this->next_new_pid();
|
| 480 |
if (!$location || !isset($this->panels[$location])) {
|
| 481 |
foreach ($this->panels as $panel_name => $panel) {
|
| 482 |
if (array_key_exists($pane->pid, $panel)) {
|
| 483 |
$this->panels[$panel_name][] = $pane->pid;
|
| 484 |
}
|
| 485 |
}
|
| 486 |
}
|
| 487 |
else {
|
| 488 |
$this->panels[$location][] = $pane->pid;
|
| 489 |
}
|
| 490 |
}
|
| 491 |
|
| 492 |
function duplicate_pane($pid, $location = FALSE) {
|
| 493 |
$pane = $this->clone_pane($pid);
|
| 494 |
$this->add_pane($pane, $location);
|
| 495 |
}
|
| 496 |
|
| 497 |
function clone_pane($pid) {
|
| 498 |
$pane = drupal_clone($this->content[$pid]);
|
| 499 |
foreach (array_keys($this->content) as $pidcheck) {
|
| 500 |
// necessary?
|
| 501 |
unset($pane->position);
|
| 502 |
}
|
| 503 |
return $pane;
|
| 504 |
}
|
| 505 |
|
| 506 |
function next_new_pid() {
|
| 507 |
// necessary if/until we use this method and ONLY this method for adding temporary pids.
|
| 508 |
// then we can do it with a nice static var.
|
| 509 |
$id = array(0);
|
| 510 |
foreach (array_keys($this->content) as $pid) {
|
| 511 |
if (!is_numeric($pid)) {
|
| 512 |
$id[] = substr($pid, 4);
|
| 513 |
}
|
| 514 |
}
|
| 515 |
$next_id = end($id);
|
| 516 |
return ++$next_id;
|
| 517 |
}
|
| 518 |
}
|
| 519 |
|
| 520 |
/**
|
| 521 |
* }@ End of 'defgroup mainapi', although other functions are specifically added later
|
| 522 |
*/
|
| 523 |
|
| 524 |
function panels_export_pane_across_displays($source_display, &$target_display, $pid, $location = FALSE) {
|
| 525 |
$pane = $source_display->clone_pane($pid);
|
| 526 |
$target_display->add_pane($pane, $location);
|
| 527 |
}
|
| 528 |
|
| 529 |
/**
|
| 530 |
* Clean up a display object and add some required information, if missing.
|
| 531 |
*
|
| 532 |
* Currently a display object needs 'args', 'incoming content', 'context'
|
| 533 |
* and a 'css_id'.
|
| 534 |
*
|
| 535 |
* @param &$display
|
| 536 |
* The display object to be sanitized.
|
| 537 |
* @return
|
| 538 |
* The sanitized display object.
|
| 539 |
*/
|
| 540 |
function panels_sanitize_display(&$display) {
|
| 541 |
if (!isset($display->args)) {
|
| 542 |
$display->args = array();
|
| 543 |
}
|
| 544 |
|
| 545 |
if (!isset($display->incoming_content)) {
|
| 546 |
$display->incoming_content = NULL;
|
| 547 |
}
|
| 548 |
|
| 549 |
if (!isset($display->context)) {
|
| 550 |
$display->context = array();
|
| 551 |
}
|
| 552 |
|
| 553 |
if (!isset($display->css_id)) {
|
| 554 |
$display->css_id = NULL;
|
| 555 |
}
|
| 556 |
}
|
| 557 |
|
| 558 |
/**
|
| 559 |
* Creates a new display, setting the ID to our magic new id.
|
| 560 |
*/
|
| 561 |
function panels_new_display() {
|
| 562 |
$display = new panels_display();
|
| 563 |
$display->did = 'new';
|
| 564 |
return $display;
|
| 565 |
}
|
| 566 |
|
| 567 |
function panels_new_pane($type, $subtype) {
|
| 568 |
$pane = new stdClass();
|
| 569 |
$pane->pid = 'new';
|
| 570 |
$pane->type = $type;
|
| 571 |
$pane->subtype = $subtype;
|
| 572 |
$pane->configuration = array();
|
| 573 |
$pane->access = array();
|
| 574 |
$pane->shown = TRUE;
|
| 575 |
$pane->visibility = '';
|
| 576 |
return $pane;
|
| 577 |
}
|
| 578 |
|
| 579 |
/**
|
| 580 |
* Load and fill the requested $display object(s).
|
| 581 |
*
|
| 582 |
* Helper function primarily for for panels_load_display().
|
| 583 |
*
|
| 584 |
* @param array $dids
|
| 585 |
* An indexed array of dids to be loaded from the database.
|
| 586 |
*
|
| 587 |
* @return $displays
|
| 588 |
* An array of displays, keyed by their display dids.
|
| 589 |
*/
|
| 590 |
function panels_load_displays($dids) {
|
| 591 |
$displays = array();
|
| 592 |
if (empty($dids) || !is_array($dids)) {
|
| 593 |
return $displays;
|
| 594 |
}
|
| 595 |
|
| 596 |
$result = db_query("SELECT * FROM {panels_display} WHERE did IN (". db_placeholders($dids) .")", $dids);
|
| 597 |
|
| 598 |
while ($obj = db_fetch_array($result)) {
|
| 599 |
$display = new panels_display();
|
| 600 |
|
| 601 |
foreach ($obj as $key => $value) {
|
| 602 |
$display->$key = $value;
|
| 603 |
// unserialize important bits
|
| 604 |
if (in_array($key, array('layout_settings', 'panel_settings', 'cache'))) {
|
| 605 |
$display->$key = empty($display->$key) ? array() : unserialize($display->$key);
|
| 606 |
}
|
| 607 |
}
|
| 608 |
|
| 609 |
$display->panels = $display->content = array();
|
| 610 |
|
| 611 |
$displays[$display->did] = $display;
|
| 612 |
}
|
| 613 |
|
| 614 |
foreach (module_implements('panels_layout_content_alter') as $module) {
|
| 615 |
$function = $module . '_panels_layout_content_alter';
|
| 616 |
$function($content, $layout, $settings);
|
| 617 |
}
|
| 618 |
|
| 619 |
$result = db_query("SELECT * FROM {panels_pane} WHERE did IN (". db_placeholders($dids) .") ORDER BY did, panel, position", $dids);
|
| 620 |
|
| 621 |
while ($pane = db_fetch_object($result)) {
|
| 622 |
$pane->configuration = unserialize($pane->configuration);
|
| 623 |
$pane->cache = empty($pane->cache) ? array() : unserialize($pane->cache);
|
| 624 |
$pane->access = ($pane->access ? explode(', ', $pane->access) : array());
|
| 625 |
// Old panels may not have shown property, so enable by default when loading.
|
| 626 |
$pane->shown = isset($pane->shown) ? $pane->shown : TRUE;
|
| 627 |
|
| 628 |
$displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
|
| 629 |
$displays[$pane->did]->content[$pane->pid] = $pane;
|
| 630 |
}
|
| 631 |
return $displays;
|
| 632 |
}
|
| 633 |
|
| 634 |
/**
|
| 635 |
* Load a single display.
|
| 636 |
*
|
| 637 |
* @ingroup mainapi
|
| 638 |
*
|
| 639 |
* @param int $did
|
| 640 |
* The display id (did) of the display to be loaded.
|
| 641 |
*
|
| 642 |
* @return object $display instanceof panels_display \n
|
| 643 |
* Returns a partially-loaded panels_display object. $display objects returned from
|
| 644 |
* from this function have only the following data:
|
| 645 |
* - $display->did (the display id)
|
| 646 |
* - $display->name (the 'name' of the display, where applicable - it often isn't)
|
| 647 |
* - $display->layout (a string with the system name of the display's layout)
|
| 648 |
* - $display->panel_settings (custom layout style settings contained in an associative array; NULL if none)
|
| 649 |
* - $display->layout_settings (panel size and configuration settings for Flexible layouts; NULL if none)
|
| 650 |
* - $display->css_id (the special css_id that has been assigned to this display, if any; NULL if none)
|
| 651 |
* - $display->content (an array of pane objects, keyed by pane id (pid))
|
| 652 |
* - $display->panels (an associative array of panel regions, each an indexed array of pids in the order they appear in that region)
|
| 653 |
* - $display->cache (any relevant data from panels_simple_cache)
|
| 654 |
* - $display->args
|
| 655 |
* - $display->incoming_content
|
| 656 |
*
|
| 657 |
* While all of these members are defined, $display->context is NEVER defined in the returned $display;
|
| 658 |
* it must be set using one of the panels_context_create() functions.
|
| 659 |
*/
|
| 660 |
function panels_load_display($did) {
|
| 661 |
$displays = panels_load_displays(array($did));
|
| 662 |
if (!empty($displays)) {
|
| 663 |
return array_shift($displays);
|
| 664 |
}
|
| 665 |
}
|
| 666 |
|
| 667 |
/**
|
| 668 |
* Save a display object.
|
| 669 |
*
|
| 670 |
* @ingroup mainapi
|
| 671 |
*
|
| 672 |
* Note a new $display only receives a real did once it is run through this function.
|
| 673 |
* Until then, it uses a string placeholder, 'new', in place of a real did. The same
|
| 674 |
* applies to all new panes (whether on a new $display or not); in addition,
|
| 675 |
* panes have sequential numbers appended, of the form 'new-1', 'new-2', etc.
|
| 676 |
*
|
| 677 |
* @param object $display instanceof panels_display \n
|
| 678 |
* The display object to be saved. Passed by reference so the caller need not use
|
| 679 |
* the return value for any reason except convenience.
|
| 680 |
*
|
| 681 |
* @return object $display instanceof panels_display \n
|
| 682 |
*/
|
| 683 |
function panels_save_display(&$display) {
|
| 684 |
// @todo -- update all this to just use drupal_write_record or something like it.
|
| 685 |
if (!empty($display->did) && $display->did != 'new') {
|
| 686 |
db_query("UPDATE {panels_display} SET layout = '%s', layout_settings = '%s', panel_settings = '%s', cache = '%s', title = '%s', hide_title = %d WHERE did = %d", $display->layout, serialize($display->layout_settings), serialize($display->panel_settings), serialize($display->cache), $display->title, $display->hide_title, $display->did);
|
| 687 |
// Get a list of all panes currently in the database for this display so we can know if there
|
| 688 |
// are panes that need to be deleted. (i.e, aren't currently in our list of panes).
|
| 689 |
$result = db_query("SELECT pid FROM {panels_pane} WHERE did = %d", $display->did);
|
| 690 |
while ($pane = db_fetch_object($result)) {
|
| 691 |
$pids[$pane->pid] = $pane->pid;
|
| 692 |
}
|
| 693 |
}
|
| 694 |
else {
|
| 695 |
db_query("INSERT INTO {panels_display} (layout, layout_settings, panel_settings, cache, title, hide_title) VALUES ('%s', '%s', '%s', '%s', '%s', %d)", $display->layout, serialize($display->layout_settings), serialize($display->panel_settings), serialize($display->cache), $display->title, $display->hide_title);
|
| 696 |
$display->did = db_last_insert_id('panels_display', 'did');
|
| 697 |
$pids = array();
|
| 698 |
}
|
| 699 |
|
| 700 |
// update all the panes
|
| 701 |
panels_load_include('plugins');
|
| 702 |
|
| 703 |
foreach ((array) $display->panels as $id => $panes) {
|
| 704 |
$position = 0;
|
| 705 |
$new_panes = array();
|
| 706 |
foreach ((array) $panes as $pid) {
|
| 707 |
$pane = $display->content[$pid];
|
| 708 |
$pane->position = $position++;
|
| 709 |
|
| 710 |
// make variables right.
|
| 711 |
$type = panels_get_content_type($pane->type);
|
| 712 |
$access = isset($pane->access) ? implode(', ', $pane->access) : '';
|
| 713 |
$visibility = !empty($type['visibility serialize']) ? serialize($pane->visibility) : $pane->visibility;
|
| 714 |
$pane->shown = isset($pane->shown) ? $pane->shown : TRUE;
|
| 715 |
|
| 716 |
if (empty($pane->cache)) {
|
| 717 |
$pane->cache = array();
|
| 718 |
}
|
| 719 |
|
| 720 |
$v = array($display->did, $pane->panel, $pane->type, $pane->subtype, serialize($pane->configuration), serialize($pane->cache), $pane->shown, $access, $visibility, $pane->position);
|
| 721 |
|
| 722 |
if (!is_numeric($pid)) {
|
| 723 |
unset($display->content[$pid]);
|
| 724 |
// doin it this way for readability
|
| 725 |
$f = 'did, panel, type, subtype, configuration, cache, shown, access, visibility, position';
|
| 726 |
$q = "%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d";
|
| 727 |
|
| 728 |
db_query("INSERT INTO {panels_pane} ($f) VALUES ($q)", $v);
|
| 729 |
$pane->pid = db_last_insert_id('panels_pane', 'pid');
|
| 730 |
}
|
| 731 |
else {
|
| 732 |
$v[] = $pane->pid;
|
| 733 |
$f = "did = %d, panel = '%s', type = '%s', subtype = '%s', configuration = '%s', cache = '%s', shown = '%s', access = '%s', visibility = '%s', position = '%d'";
|
| 734 |
db_query("UPDATE {panels_pane} SET $f WHERE pid = %d", $v);
|
| 735 |
}
|
| 736 |
// and put it back so our pids and positions can be used
|
| 737 |
$display->content[$pane->pid] = $pane;
|
| 738 |
$new_panes[] = $pane->pid;
|
| 739 |
if (isset($pids[$pane->pid])) {
|
| 740 |
unset($pids[$pane->pid]);
|
| 741 |
}
|
| 742 |
}
|
| 743 |
|
| 744 |
$display->panels[$id] = $new_panes;
|
| 745 |
}
|
| 746 |
if (!empty($pids)) {
|
| 747 |
db_query("DELETE FROM {panels_pane} WHERE pid IN (" . db_placeholders($pids) . ")", $pids);
|
| 748 |
}
|
| 749 |
|
| 750 |
// Clear any cached content for this display.
|
| 751 |
panels_clear_cached_content($display);
|
| 752 |
|
| 753 |
// to be nice, even tho we have a reference.
|
| 754 |
return $display;
|
| 755 |
}
|
| 756 |
|
| 757 |
/**
|
| 758 |
* Delete a display.
|
| 759 |
*/
|
| 760 |
function panels_delete_display($display) {
|
| 761 |
if (is_object($display)) {
|
| 762 |
$did = $display->did;
|
| 763 |
}
|
| 764 |
else {
|
| 765 |
$did = $display;
|
| 766 |
}
|
| 767 |
db_query("DELETE FROM {panels_display} WHERE did = %d", $did);
|
| 768 |
db_query("DELETE FROM {panels_pane} WHERE did = %d", $did);
|
| 769 |
}
|
| 770 |
|
| 771 |
/**
|
| 772 |
* Exports the provided display into portable code.
|
| 773 |
*
|
| 774 |
* This function is primarily intended as a mechanism for cloning displays.
|
| 775 |
* It generates an exact replica (in code) of the provided $display, with
|
| 776 |
* the exception that it replaces all ids (dids and pids) with 'new-*' values.
|
| 777 |
* Only once panels_save_display() is called on the code version of $display will
|
| 778 |
* the exported display written to the database and permanently saved.
|
| 779 |
*
|
| 780 |
* @see panels_page_export() or _panels_page_fetch_display() for sample implementations.
|
| 781 |
*
|
| 782 |
* @ingroup mainapi
|
| 783 |
*
|
| 784 |
* @param object $display instanceof panels_display \n
|
| 785 |
* This export function does no loading of additional data about the provided
|
| 786 |
* display. Consequently, the caller should make sure that all the desired data
|
| 787 |
* has been loaded into the $display before calling this function.
|
| 788 |
* @param string $prefix
|
| 789 |
* A string prefix that is prepended to each line of exported code. This is primarily
|
| 790 |
* used for prepending a double space when exporting so that the code indents and lines up nicely.
|
| 791 |
*
|
| 792 |
* @return string $output
|
| 793 |
* The passed-in $display expressed as code, ready to be imported. Import by running
|
| 794 |
* eval($output) in the caller function; doing so will create a new $display variable
|
| 795 |
* with all the exported values. Note that if you have already defined a $display variable in
|
| 796 |
* the same scope as where you eval(), your existing $display variable WILL be overwritten.
|
| 797 |
*/
|
| 798 |
function panels_export_display($display, $prefix = '') {
|
| 799 |
$output = '';
|
| 800 |
$output .= $prefix . '$display = new panels_display()' . ";\n";
|
| 801 |
$output .= $prefix . '$display->did = \'new\'' . ";\n";
|
| 802 |
// $fields = array('name', 'layout', 'layout_settings', 'panel_settings');
|
| 803 |
// TODO 'name' field was removed several months ago, so temporarily removing
|
| 804 |
// it from here whil investigations into exactly why it was removed continue
|
| 805 |
$fields = array('layout', 'layout_settings', 'panel_settings');
|
| 806 |
foreach ($fields as $field) {
|
| 807 |
$output .= $prefix . '$display->' . $field . ' = ' . panels_var_export($display->$field, $prefix) . ";\n";
|
| 808 |
}
|
| 809 |
|
| 810 |
$output .= $prefix . '$display->content = array()' . ";\n";
|
| 811 |
$output .= $prefix . '$display->panels = array()' . ";\n";
|
| 812 |
$panels = array();
|
| 813 |
|
| 814 |
if (!empty($display->content)) {
|
| 815 |
$pid_counter = 0;
|
| 816 |
$region_counters = array();
|
| 817 |
foreach ($display->content as $pane) {
|
| 818 |
$pane->pid = 'new-' . ++$pid_counter;
|
| 819 |
$output .= panels_export_pane($pane, $prefix . ' ');
|
| 820 |
$output .= "$prefix " . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n";
|
| 821 |
if (!isset($region_counters[$pane->panel])) {
|
| 822 |
$region_counters[$pane->panel] = 0;
|
| 823 |
}
|
| 824 |
$output .= "$prefix " . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ .'] = \'' . $pane->pid . "';\n";
|
| 825 |
}
|
| 826 |
}
|
| 827 |
return $output;
|
| 828 |
}
|
| 829 |
|
| 830 |
function panels_export_pane($pane, $prefix = '') {
|
| 831 |
$output = '';
|
| 832 |
$output = $prefix . '$pane = new stdClass()' . ";\n";
|
| 833 |
$fields = array('pid', 'panel', 'type', 'shown', 'subtype', 'access', 'configuration', 'cache');
|
| 834 |
foreach ($fields as $field) {
|
| 835 |
$output .= "$prefix " . '$pane->' . $field . ' = ' . panels_var_export($pane->$field, "$prefix ") . ";\n";
|
| 836 |
}
|
| 837 |
return $output;
|
| 838 |
}
|
| 839 |
|
| 840 |
function panels_var_export($object, $prefix = '') {
|
| 841 |
if (is_array($object) && empty($object)) {
|
| 842 |
$output = 'array()';
|
| 843 |
}
|
| 844 |
else {
|
| 845 |
// Remove extra space to match Drupal coding standards.
|
| 846 |
$output = str_replace('array (', 'array(', var_export($object, TRUE));
|
| 847 |
}
|
| 848 |
|
| 849 |
if ($prefix) {
|
| 850 |
$output = str_replace("\n", "\n$prefix", $output);
|
| 851 |
}
|
| 852 |
return $output;
|
| 853 |
}
|
| 854 |
|
| 855 |
/**
|
| 856 |
* Render a display by loading the content into an appropriate
|
| 857 |
* array and then passing through to panels_render_layout.
|
| 858 |
*
|
| 859 |
* if $incoming_content is NULL, default content will be applied. Use
|
| 860 |
* an empty string to indicate no content.
|
| 861 |
* @render
|
| 862 |
* @ingroup hook_invocations
|
| 863 |
*/
|
| 864 |
function panels_render_display(&$display) {
|
| 865 |
panels_load_include('display-render');
|
| 866 |
panels_load_include('plugins');
|
| 867 |
return _panels_render_display($display);
|
| 868 |
}
|
| 869 |
|
| 870 |
/**
|
| 871 |
* For external use: Given a layout ID and a $content array, return the
|
| 872 |
* panel display. The content array is filled in based upon the content
|
| 873 |
* available in the layout. If it's a two column with a content
|
| 874 |
* array defined like array('left' => t('Left side'), 'right' =>
|
| 875 |
* t('Right side')), then the $content array should be array('left' =>
|
| 876 |
* $output_left, 'right' => $output_right)
|
| 877 |
* @render
|
| 878 |
*/
|
| 879 |
function panels_print_layout($id, $content) {
|
| 880 |
panels_load_include('plugins');
|
| 881 |
return _panels_print_layout($id, $content);
|
| 882 |
}
|
| 883 |
|
| 884 |
// @layout
|
| 885 |
function panels_print_layout_icon($id, $layout, $title = NULL) {
|
| 886 |
drupal_add_css(panels_get_path('css/panels_admin.css'));
|
| 887 |
$file = $layout['path'] . '/' . $layout['icon'];
|
| 888 |
return theme('panels_layout_icon', $id, theme('image', $file), $title);
|
| 889 |
}
|
| 890 |
|
| 891 |
/**
|
| 892 |
* Theme the layout icon image
|
| 893 |
* @layout
|
| 894 |
* @todo move to theme.inc
|
| 895 |
*/
|
| 896 |
function theme_panels_layout_icon($id, $image, $title = NULL) {
|
| 897 |
$output = '<div class="layout-icon">';
|
| 898 |
$output .= $image;
|
| 899 |
if ($title) {
|
| 900 |
$output .= '<div class="caption">' . $title . '</div>';
|
| 901 |
}
|
| 902 |
$output .= '</div>';
|
| 903 |
return $output;
|
| 904 |
}
|
| 905 |
|
| 906 |
/**
|
| 907 |
* Theme the layout link image
|
| 908 |
* @layout
|
| 909 |
*/
|
| 910 |
function theme_panels_layout_link($title, $id, $image, $link) {
|
| 911 |
$output = '<div class="layout-link">';
|
| 912 |
$output .= $image;
|
| 913 |
$output .= '<div>' . $title . '</div>';
|
| 914 |
$output .= '</div>';
|
| 915 |
return $output;
|
| 916 |
}
|
| 917 |
|
| 918 |
/**
|
| 919 |
* Print the layout link. Sends out to a theme function.
|
| 920 |
* @layout
|
| 921 |
*/
|
| 922 |
function panels_print_layout_link($id, $layout, $link) {
|
| 923 |
drupal_add_css(panels_get_path('css/panels_admin.css'));
|
| 924 |
$file = $layout['path'] . '/' . $layout['icon'];
|
| 925 |
$image = l(theme('image', $file), $link, array('html' => true));
|
| 926 |
$title = l($layout['title'], $link);
|
| 927 |
return theme('panels_layout_link', $title, $id, $image, $link);
|
| 928 |
}
|
| 929 |
|
| 930 |
/**
|
| 931 |
* Implementation of hook_ctools_plugin_directory() to let the system know
|
| 932 |
* we implement task and task_handler plugins.
|
| 933 |
*/
|
| 934 |
function panels_ctools_plugin_directory($plugin) {
|
| 935 |
if ($plugin == 'tasks' || $plugin == 'task_handlers') {
|
| 936 |
return 'plugins/' . $plugin;
|
| 937 |
}
|
| 938 |
}
|