Package base :: Package includes :: Module common
[hide private]

Source Code for Module base.includes.common

   1  #!/usr/bin/env python 
   2  # $Id: common.inc,v 1.783 2008/08/13 07:10:20 dries Exp $ 
   3   
   4  """ 
   5    Common functions that many Drupy plugins will need to reference. 
   6   
   7    @package includes 
   8    @see <a href='http://drupy.net'>Drupy Homepage</a> 
   9    @see <a href='http://drupal.org'>Drupal Homepage</a> 
  10    @note Drupy is a port of the Drupal project. 
  11    @note This file was ported from Drupal's includes/common.inc 
  12    @author Brendon Crawford 
  13    @copyright 2008 Brendon Crawford 
  14    @contact message144 at users dot sourceforge dot net 
  15    @created 2008-01-10 
  16    @version 0.1 
  17    @note License: 
  18   
  19      This program is free software; you can redistribute it and/or 
  20      modify it under the terms of the GNU General Public License 
  21      as published by the Free Software Foundation; either version 2 
  22      of the License, or (at your option) any later version. 
  23   
  24      This program is distributed in the hope that it will be useful, 
  25      but WITHOUT ANY WARRANTY; without even the implied warranty of 
  26      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  27      GNU General Public License for more details. 
  28   
  29      You should have received a copy of the GNU General Public License 
  30      along with this program; if not, write to: 
  31       
  32      The Free Software Foundation, Inc., 
  33      51 Franklin Street, Fifth Floor, 
  34      Boston, MA  02110-1301, 
  35      USA 
  36  """ 
  37   
  38  __version__ = "$Revision: 1 $" 
  39   
  40  # 
  41  # The functions that are critical and need to be available even when serving 
  42  # a cached page are instead located in bootstrap.inc. 
  43  # 
  44   
  45  # 
  46  # Includes 
  47  # 
  48  import urllib2 
  49  from lib.drupy import DrupyPHP as php 
  50  from sites.default import settings 
  51  import bootstrap as lib_bootstrap 
  52  import theme as lib_theme 
  53  #import pager as inc_pager 
  54  import plugin as lib_plugin 
  55  import menu as lib_menu 
  56  #import tablesort as inc_tablesort 
  57  import file as lib_file 
  58  import unicode as lib_unicode 
  59  #import image as inc_image 
  60  #import form as inc_form 
  61  #import mail as inc_mail 
  62  #import actions as inc_actions 
  63   
  64   
  65  # 
  66  # Return status for saving which involved creating a new item. 
  67  # 
  68  SAVED_NEW = 1; 
  69  # 
  70  # Return status for saving which involved an update to an existing item. 
  71  # 
  72  SAVED_UPDATED = 2; 
  73  # 
  74  # Return status for saving which deleted an existing item. 
  75  # 
  76  SAVED_DELETED = 3; 
  77   
  78   
  79   
80 -def drupal_set_content(region = None, data = None):
81 """ 82 Set content for a specified region. 83 84 @param region 85 Page region the content is assigned to. 86 @param data 87 Content to be set. 88 """ 89 php.static(drupal_set_content, 'content', {}) 90 if (not php.is_null(region) and not php.is_null(data)): 91 drupal_set_content.content[region].append( data ); 92 return drupal_set_content.content;
93 94
95 -def drupal_get_content(region = None, delimiter = ' '):
96 """ 97 Get assigned content. 98 99 @param region 100 A specified region to fetch content for. If None, all regions will be 101 returned. 102 @param delimiter 103 Content to be inserted between exploded array elements. 104 """ 105 content = drupal_set_content(); 106 if (region != None): 107 if (php.isset(content, region) and php.is_array(content, region)): 108 return php.implode(delimiter, content[region]); 109 else: 110 for region in php.array_keys(content): 111 if (php.is_array(content[region])): 112 content[region] = php.implode(delimiter, content[region]); 113 return content;
114 115 116
117 -def drupal_set_breadcrumb(breadcrumb = None):
118 """ 119 Set the breadcrumb trail for the current page. 120 121 @param breadcrumb 122 Array of links, starting with "home" and proceeding 123 up to but not including 124 the current page. 125 """ 126 php.static(drupal_set_breadcrumb, 'stored_breadcrumb') 127 if (not php.is_null(breadcrumb)): 128 drupal_set_breadcrumb.stored_breadcrumb = breadcrumb; 129 return drupal_set_breadcrumb.stored_breadcrumb;
130 131
132 -def drupal_get_breadcrumb():
133 """ 134 Get the breadcrumb trail for the current page. 135 """ 136 breadcrumb = drupal_set_breadcrumb(); 137 if (php.is_null(breadcrumb)): 138 breadcrumb = menu_get_active_breadcrumb(); 139 return breadcrumb;
140 141 142
143 -def drupal_set_html_head(data = None):
144 """ 145 Add output to the head tag of the HTML page. 146 147 This function can be called as long the headers aren't sent. 148 """ 149 php.static(drupal_set_html_head, 'stored_head', '') 150 if (not php.is_null(data)): 151 drupal_set_html_head.stored_head += data + "\n"; 152 return drupal_set_html_head.stored_head;
153 154
155 -def drupal_get_html_head():
156 """ 157 Retrieve output to be displayed in the head tag of the HTML page. 158 """ 159 output = '<meta http-equiv="Content-Type" ' + \ 160 'content="text/html; charset=utf-8" />\n'; 161 return output + drupal_set_html_head();
162 163
164 -def drupal_clear_path_cache():
165 """ 166 Reset the static variable which holds the aliases mapped for this request. 167 """ 168 drupal_lookup_path('wipe');
169 170 171
172 -def drupal_set_header(header_ = None):
173 """ 174 Set an HTTP response php.header for the current page. 175 176 Note: When sending a Content-Type php.header, always 177 include a 'charset' type, 178 too. This is necessary to avoid security bugs (e.g. UTF-7 XSS). 179 """ 180 # We use an array to guarantee there are no leading or trailing delimiters. 181 # Otherwise, php.header('') could get called when serving 182 # the page later, which 183 # ends HTTP headers prematurely on some PHP versions. 184 php.static(drupal_set_header, 'stored_headers', []) 185 if (php.strlen(header_) > 0): 186 php.header(header_); 187 drupal_set_header.stored_headers.append(header_); 188 return php.implode("\n", drupal_set_header.stored_headers);
189 190
191 -def drupal_get_headers():
192 """ 193 Get the HTTP response headers for the current page. 194 """ 195 return drupal_set_header();
196 197
198 -def drupal_add_feed(url = None, title = ''):
199 """ 200 Add a feed URL for the current page. 201 202 @param url 203 A url for the feed. 204 @param title 205 The title of the feed. 206 """ 207 php.static(drupal_add_feed, 'stored_feed_links', {}) 208 if (not php.is_null(url) and not \ 209 php.isset(drupal_add_feed.stored_feed_links, url)): 210 drupal_add_feed.stored_feed_links[url] = theme('feed_icon', url, title); 211 drupal_add_link({ 212 'rel' : 'alternate', 213 'type' : 'application/rss+xml', 214 'title' : title, 215 'href' : url 216 }); 217 return drupal_add_feed.stored_feed_links;
218 219
220 -def drupal_get_feeds(delimiter = "\n"):
221 """ 222 Get the feed URLs for the current page. 223 224 @param delimiter 225 A delimiter to split feeds by. 226 """ 227 feeds = drupal_add_feed(); 228 return php.implode(feeds, delimiter);
229 230 231 # 232 # @name HTTP handling 233 # @{ 234
235 -def drupal_query_string_encode(query, exclude = [], parent = ''):
236 """ 237 Functions to properly handle HTTP responses. 238 239 Parse an array into a valid urlencoded query string. 240 241 @param query 242 The array to be processed e.g. php.GET. 243 @param exclude 244 The array filled with keys to be excluded. Use parent[child] to exclude 245 nested items. 246 @param parent 247 Should not be passed, only used in recursive calls. 248 @return 249 An urlencoded string which can be appended to/as the URL query string. 250 """ 251 params = []; 252 for key,value in query.items(): 253 key = drupal_urlencode(key); 254 if (parent): 255 key = parent + '[' + key + ']'; 256 if (php.in_array(key, exclude)): 257 continue; 258 if (php.is_array(value)): 259 params.append( drupal_query_string_encode(value, exclude, key) ); 260 else: 261 params.append( key + '=' + drupal_urlencode(value) ); 262 return php.implode('&', params);
263 264
265 -def drupal_get_destination():
266 """ 267 Prepare a destination query string for use in combination 268 with drupal_goto(). 269 270 Used to direct the user back to the referring page after completing a form. 271 By default the current URL is returned. If a destination exists in the 272 previous request, that destination is returned. As such, a destination can 273 persist across multiple pages. 274 275 @see drupal_goto() 276 """ 277 if (php.isset(php.REQUEST, 'destination')): 278 return 'destination=' + urlencode(php.REQUEST['destination']); 279 else: 280 # Use php.GET here to retrieve the original path in source form. 281 path = (php.GET.__getitem__('q') if php.isset(php.GET, 'q') else ''); 282 query = drupal_query_string_encode(php.GET, ['q']); 283 if (query != ''): 284 path += '?' + query; 285 return 'destination=' + urlencode(path);
286 287 288
289 -def drupal_goto(path = '', query = None, fragment = None, \ 290 http_response_code = 302):
291 """ 292 Send the user to a different Drupal page. 293 294 This issues an on-site HTTP redirect. The function makes sure the redirected 295 URL is formatted correctly. 296 297 Usually the redirected URL is constructed from this function's input 298 parameters. However you may override that behavior by setting a 299 <em>destination</em> in either the php.REQUEST-array (i.e. by using 300 the query string of an URI) or the php.REQUEST['edit']-array (i.e. by 301 using a hidden form field). This is used to direct the user back to 302 the proper page after completing a form. For example, after editing 303 a post on the 'admin/content/node'-page or after having logged on using the 304 'user login'-block in a sidebar. The function drupal_get_destination() 305 can be used to help set the destination URL. 306 307 Drupal will ensure that messages set by drupal_set_message() and other 308 session data are written to the database before the user is redirected. 309 310 This function ends the request; use it rather than a print theme('page') 311 statement in your menu callback. 312 313 @param path 314 A Drupal path or a full URL. 315 @param query 316 A query string component, if any. 317 @param fragment 318 A destination fragment identifier (named anchor). 319 @param http_response_code 320 Valid values for an actual "goto" as per RFC 2616 section 10.3 are: 321 - 301 Moved Permanently (the recommended value for most redirects) 322 - 302 Found (default in Drupal and PHP, sometimes used for spamming search 323 engines) 324 - 303 See Other 325 - 304 Not Modified 326 - 305 Use Proxy 327 - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance") 328 Note: Other values are defined by RFC 2616, but are rarely used and poorly 329 supported. 330 @see drupal_get_destination() 331 """ 332 if (php.isset(php.REQUEST, 'destination')): 333 urlP = php.parse_url(php.urldecode(php.REQUEST['destination'])); 334 elif (php.isset(php.REQUEST['edit'], 'destination')): 335 urlP = php.parse_url(php.urldecode(php.REQUEST['edit']['destination'])); 336 url = url(path, {'query' : urlP['query'], 'fragment' : urlP['fragment'], \ 337 'absolute' : True}); 338 # Remove newlines from the URL to avoid php.header injection attacks. 339 url = php.str_replace(["\n", "\r"], '', url); 340 # Allow plugins to react to the end of the page request before redirecting. 341 # We do not want this while running update.php. 342 if (not php.defined(locals(), 'MAINTENANCE_MODE', True) or \ 343 MAINTENANCE_MODE != 'update'): 344 plugin_invoke_all('exit', url); 345 # Even though session_write_close() is registered as a shutdown function, we 346 # need all session data written to the database before redirecting. 347 session_write_close(); 348 php.header('Location: '. url, True, http_response_code); 349 # The "Location" php.header sends a redirect status 350 # code to the HTTP daemon. In 351 # some cases this can be wrong, so we make sure none of the code below the 352 # drupal_goto() call gets executed upon redirection. 353 exit();
354 355 356
357 -def drupal_site_offline():
358 """ 359 Generates a site off-line message. 360 """ 361 drupal_maintenance_theme(); 362 drupal_set_header('HTTP/1.1 503 Service unavailable'); 363 drupal_set_title(t('Site off-line')); 364 print theme( 365 'maintenance_page', 366 filter_xss_admin( 367 variable_get( 368 'site_offline_message', 369 t( 370 '@site is currently under maintenance. ' + \ 371 'We should be back shortly. Thank you for your patience.', \ 372 {'@site' : variable_get('site_name', 'Drupal')} 373 ) 374 ) 375 ) 376 );
377 378 379
380 -def drupal_not_found():
381 """ 382 Generates a 404 error if the request can not be handled. 383 """ 384 drupal_set_header('HTTP/1.1 404 Not Found'); 385 watchdog('page not found', check_plain(php.GET['q']), None, \ 386 WATCHDOG_WARNING); 387 # Keep old path for reference. 388 if (not php.isset(php.REQUEST, 'destination')): 389 php.REQUEST['destination'] = php.GET['q']; 390 path = drupal_get_normal_path(variable_get('site_404', '')); 391 if (path and path != php.GET['q']): 392 # Set the active item in case there are tabs to display, or other 393 # dependencies on the path. 394 menu_set_active_item(path); 395 return_ = menu_execute_active_handler(path); 396 if (php.empty(return_) or return_ == MENU_NOT_FOUND or \ 397 return_ == MENU_ACCESS_DENIED): 398 drupal_set_title(t('Page not found')); 399 return_ = t('The requested page could not be found.'); 400 # To conserve CPU and bandwidth, omit the blocks. 401 print theme('page', return_, False);
402 403 404
405 -def drupal_access_denied():
406 """ 407 Generates a 403 error if the request is not allowed. 408 """ 409 drupal_set_header('HTTP/1.1 403 Forbidden'); 410 watchdog('access denied', check_plain(php.GET['q']), None, WATCHDOG_WARNING); 411 # Keep old path for reference. 412 if (not php.isset(php.REQUEST, 'destination')): 413 php.REQUEST['destination'] = php.GET['q']; 414 path = drupal_get_normal_path(variable_get('site_403', '')); 415 if (path and path != php.GET['q']): 416 # Set the active item in case there are tabs to display or other 417 # dependencies on the path. 418 menu_set_active_item(path); 419 return_ = menu_execute_active_handler(path); 420 if (php.empty(return_) or return_ == MENU_NOT_FOUND or \ 421 return_ == MENU_ACCESS_DENIED): 422 drupal_set_title(t('Access denied')); 423 return_ = t('You are not authorized to access this page.'); 424 print theme('page', return_);
425 426 427
428 -def drupal_http_request(url, headers = {}, method = 'GET', \ 429 data = None, retry = None):
430 """ 431 Perform an HTTP request. 432 433 This is a flexible and powerful HTTP client implementation. 434 Correctly handles 435 php.GET, php.POST, PUT or any other HTTP requests. Handles redirects. 436 437 @note DRUPY: 438 This function has been modified to use urllib2. 439 Although it does not act exactly as before, it is 440 still good enough to get the job done. 441 Return object should look like this: 442 result: 443 Str error 444 Int code 445 Str request 446 Dict headers 447 Int redirect_code 448 Str redirect_url 449 @param url 450 A string containing a fully qualified URI. 451 @param headers 452 An array containing an HTTP php.header : value pair. 453 @param method 454 A string defining the HTTP request to use. 455 @param data 456 A string containing data to include in the request. 457 @param retry 458 An integer representing how many times to retry the request in case of a 459 redirect. 460 @return 461 An object containing the HTTP request headers, response code, headers, 462 data and redirect status. 463 """ 464 headers['User-Agent'] = 'Drupy (+http://drupy.sourceforge.net/)'; 465 req = urllib2.Request(url, data, headers); 466 res = urllib2.urlopen(req); 467 result = php.object_({ 468 'error' : res.msg, 469 'code' : res.code, 470 'request' : 'NOT-AVAILABLE', 471 'headers' : headers, 472 'data' : res.read() 473 }); 474 return result;
475 476 477 478 479 # 480 # @} End of "HTTP handling". 481 # 482
483 -def drupal_error_handler(errno, message, filename, line, \ 484 context, errType = None):
485 """ 486 Log errors as defined by administrator. 487 488 Error levels: 489 - 0 = Log errors to database. 490 - 1 = Log errors to database and to screen. 491 """ 492 if (errno > 0): 493 # For database errors, we want the line number/file name of the place that 494 # the query was originally called, not _db_query(). 495 err = { 496 'errType' : errType, 497 'message' : message, 498 'filename' : filename, 499 'line' : line 500 }; 501 entry = '%(errType)s : %(message)s in %(filename)s on line %(line)s' % err 502 # Force display of error messages in update.php. 503 if (lib_bootstrap.variable_get('error_level', 1) == 1 or \ 504 strstr(php.SERVER['SCRIPT_NAME'], 'update.py')): 505 lib_bootstrap.drupal_set_message(entry, 'error'); 506 lib_bootstrap.watchdog('php', \ 507 '%(message)s in %(filename)s on line %(line)s.' % err, \ 508 lib_bootstrap.WATCHDOG_ERROR);
509 510 511
512 -def _fix_gpc_magic(item):
513 """ 514 Not needed 515 """ 516 pass;
517 518
519 -def _fix_gpc_magic_files(item, key):
520 """ 521 Helper function to strip slashes from FILES skipping over the tmp_name keys 522 since PHP generates single backslashes for file paths on Windows systems. 523 524 tmp_name does not have backslashes added see 525 http://php.net/manual/en/features.file-upload.php#42280 526 """ 527 pass;
528 529
530 -def fix_gpc_magic():
531 """ 532 Fix double-escaping problems caused by "magic quotes" 533 in some PHP installations. 534 """ 535 pass;
536 537
538 -def t(string, args={}, langcode=None):
539 """ 540 Translate strings to the page language or a given language. 541 542 All human-readable text that will be displayed somewhere 543 within a page should 544 be run through the t() function. 545 546 Examples: 547 @code 548 if (!info or !info['extension']) { 549 form_set_error('picture_upload', \ 550 t('The uploaded file was not an image.')); 551 } 552 553 form['submit'] = array( 554 '#type' : 'submit', 555 '#value' : t('Log in'), 556 ); 557 @endcode 558 559 Any text within t() can be extracted by translators and changed into 560 the equivalent text in their native language. 561 562 Special variables called "placeholders" are used to signal dynamic 563 information in a string which should not be translated. Placeholders 564 can also be used for text that may change from time to time 565 (such as link paths) to be changed without requiring 566 updates to translations. 567 568 For example: 569 @code 570 output = t('There are currently %members and %visitors online.', array( 571 '%members' : format_plural(%(total_users)s, '1 user', '@count users'), 572 '%visitors' : format_plural(%(guests)s.count, \ 573 '1 guest', '@count guests'))); 574 @endcode 575 576 There are three styles of placeholders: 577 - !variable, which indicates that the text should be inserted as-is. This is 578 useful for inserting variables into things like e-mail. 579 @code 580 message[] = t("If you don't want to receive such e-mails, " + \ 581 "you can change your settings at !url.", \ 582 array('!url' : url("user/%(account)s.uid", \ 583 array('absolute' : True)))); 584 @endcode 585 586 - @variable, which indicates that the text should be 587 run through check_plain, 588 to escape HTML characters. Use this for any output that's displayed within 589 a Drupal page. 590 @code 591 drupal_set_title(title = t("@name's blog", \ 592 array('@name' : account.name))); 593 @endcode 594 595 - %variable, which indicates that the string should be HTML escaped and 596 highlighted with theme_placeholder() which shows up by default as 597 <em>emphasized</em>. 598 @code 599 message = t('%name-from sent %name-to an e-mail.', \ 600 array('%name-from' : %(user)s.name, '%name-to' : account.name)); 601 @endcode 602 603 When using t(), try to put entire sentences and strings in one t() call. 604 This makes it easier for translators, as it provides 605 context as to what each 606 word refers to. HTML markup within translation strings i 607 s allowed, but should 608 be avoided if possible. The exception are embedded links; link titles add a 609 context for translators, so should be kept in the main string. 610 611 Here is an example of incorrect usage of t(): 612 @code 613 output += t('<p>Go to the @contact-page.</p>', \ 614 array('@contact-page' : l(t('contact page'), 'contact'))); 615 @endcode 616 617 Here is an example of t() used correctly: 618 @code 619 output += '<p>'. t(\ 620 'Go to the <a href="@contact-page">contact page</a>.', \ 621 array('@contact-page' : url('contact'))) .'</p>'; 622 @endcode 623 624 Also avoid escaping quotation marks wherever possible. 625 626 Incorrect: 627 @code 628 output += t('Don\'t click me.'); 629 @endcode 630 631 Correct: 632 @code 633 output += t("Don't click me."); 634 @endcode 635 636 @param string 637 A string containing the English string to translate. 638 @param args 639 An associative array of replacements to make after translation. Incidences 640 of any key in this array are replaced with the corresponding value. 641 Based on the first character of the key, the value 642 is escaped and/or themed: 643 - !variable: inserted as is 644 - @variable: escape plain text to HTML (check_plain) 645 - %variable: escape text and theme as a placeholder for user-submitted 646 content (check_plain + theme_placeholder) 647 @param langcode 648 Optional language code to translate to a language other than what is used 649 to display the page. 650 @return 651 The translated string. 652 """ 653 php.static(t, 'custom_strings', {}) 654 langcode = (langcode if (langcode != None) else \ 655 lib_appglobals.language.language); 656 # First, check for an array of customized strings. If present, use the array 657 # *instead of* database lookups. This is a high performance way to provide a 658 # handful of string replacements. See settings.php for examples. 659 # Cache the custom_strings variable to improve performance. 660 if (not php.isset(t.custom_strings, langcode)): 661 t.custom_strings[langcode] = \ 662 variable_get('locale_custom_strings_' + langcode, {}); 663 # Custom strings work for English too, even if locale plugin is disabled. 664 if (php.isset(t.custom_strings[langcode], string)): 665 string = t.custom_strings[langcode][string]; 666 # Translate with locale plugin if enabled. 667 elif (php.function_exists('locale') and langcode != 'en'): 668 string = locale(string, langcode); 669 if (php.empty(args)): 670 return string; 671 else: 672 # Transform arguments before inserting them. 673 for key,value in args.items(): 674 if key[0] == '@': 675 # Escaped only. 676 args[key] = check_plain(value); 677 if key[0] == '!': 678 pass; 679 elif key[0] == '%' or True: 680 # Escaped and placeholder. 681 args[key] = lib_theme.theme('placeholder', value); 682 return php.strtr(string, args);
683 684 685 686 687 # 688 # @defgroup validation Input validation 689 # @{ 690 # Functions to validate user input. 691 # 692
693 -def valid_email_address(mail):
694 """ 695 Verify the syntax of the given e-mail address. 696 697 Empty e-mail addresses are allowed. See RFC 2822 for details. 698 699 @param mail 700 A string containing an e-mail address. 701 @return 702 True if the address is in a valid format. 703 """ 704 items = { 705 user : '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\']+', 706 domain : '(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.?)+', 707 ipv4 : '[0-9]{1,3}(\.[0-9]{1,3}){3}', 708 ipv6 : '[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7}' 709 }; 710 mail = php.Reference(); 711 cnt = php.preg_match(\ 712 "/^%(user)s@(%(domain)s|(\[(%(ipv4)s|%(ipv6)s)\]))$/" % items, mail); 713 return (cnt > 0);
714 715 716
717 -def valid_url(url, absolute = False):
718 """ 719 Verify the syntax of the given URL. 720 721 This function should only be used on actual URLs. It should not be used for 722 Drupal menu paths, which can contain arbitrary characters. 723 724 @param url 725 The URL to verify. 726 @param absolute 727 Whether the URL is absolute (beginning with a scheme such as "http:"). 728 @return 729 True if the URL is in a valid format. 730 """ 731 allowed_characters = '[a-z0-9\/:_\-_\.\?\$,;~=#&%\+]'; 732 if (absolute): 733 url = php.Reference(); 734 return (php.preg_match("/^(http|https|ftp):\/\/" + \ 735 allowed_characters + "+$/i", url) > 0); 736 else: 737 url = php.Reference(); 738 return (php.preg_match("/^" + allowed_characters + "+$/i", url) > 0);
739 740 741 # 742 # @} End of "defgroup validation". 743 # 744
745 -def flood_register_event(name):
746 """ 747 Register an event for the current visitor (hostname/IP) to 748 the flood control mechanism. 749 750 @param name 751 The name of an event. 752 """ 753 db_query("INSERT INTO {flood} (event, hostname, timestamp) " + \ 754 "VALUES ('%s', '%s', %d)", name, ip_address(), php.time_());
755 756 757
758 -def flood_is_allowed(name, threshold):
759 """ 760 Check if the current visitor (hostname/IP) is allowed to 761 proceed with the specified event. 762 763 The user is allowed to proceed if he did not trigger the 764 specified event more than threshold times per hour. 765 766 @param name 767 The name of the event. 768 @param number 769 The maximum number of the specified event per hour (per visitor). 770 @return 771 True if the user did not exceed the hourly threshold. False otherwise. 772 """ 773 number = db_result(db_query("SELECT COUNT(*) FROM {flood} " + \ 774 "WHERE event = '%s' AND hostname = '%s' AND timestamp > %d", \ 775 name, ip_address(), drupy_time() - 3600)); 776 return (number < threshold);
777 778 779
780 -def check_file(filename):
781 return php.is_uploaded_file(filename);
782 783 784
785 -def check_url(uri):
786 """ 787 Prepare a URL for use in an HTML attribute. Strips harmful protocols. 788 """ 789 return filter_xss_bad_protocol(uri, False);
790 791 792 # 793 # @defgroup format Formatting 794 # @{ 795 # Functions to format numbers, strings, dates, etc. 796 # 797
798 -def format_rss_channel(title, link, description, items, langcode = None, \ 799 args = {}):
800 """ 801 Formats an RSS channel. 802 803 Arbitrary elements may be added using the args associative array. 804 """ 805 langcode = (langcode if (langcode != None) else \ 806 lib_appglobals.language.language); 807 output = "<channel>\n"; 808 output += ' <title>' + check_plain(title) + "</title>\n"; 809 output += ' <link>' + check_url(link) + "</link>\n"; 810 # The RSS 2.0 "spec" doesn't indicate HTML can be used in the description. 811 # We strip all HTML tags, but need to prevent double encoding from properly 812 # escaped source data (such as &amp becoming &amp;amp;). 813 output += ' <description>' + check_plain(\ 814 decode_entities(strip_tags(description))) + "</description>\n"; 815 output += ' <language>' + check_plain(langcode) + "</language>\n"; 816 output += format_xml_elements(args); 817 output += items; 818 output += "</channel>\n"; 819 return output;
820 821
822 -def format_rss_item(title, link, description, args = {}):
823 """ 824 Format a single RSS item. 825 826 Arbitrary elements may be added using the args associative array. 827 """ 828 output = "<item>\n"; 829 output += ' <title>' + check_plain(title) + "</title>\n"; 830 output += ' <link>' + check_url(link) + "</link>\n"; 831 output += ' <description>' + check_plain(description) + "</description>\n"; 832 output += format_xml_elements(args); 833 output += "</item>\n"; 834 return output;
835 836
837 -def format_xml_elements(array_):
838 """ 839 Format XML elements. 840 841 @param array 842 An array where each item represent an element and is either a: 843 - (key : value) pair (<key>value</key>) 844 - Associative array with fields: 845 - 'key': element name 846 - 'value': element contents 847 - 'attributes': associative array of element attributes 848 849 In both cases, 'value' can be a simple string, or it can be another array 850 with the same format as array itself for nesting. 851 """ 852 output = ''; 853 for key,value in array_.items(): 854 if (php.is_numeric(key)): 855 if (not php.empty(value['key'])): 856 output += ' <' + value['key']; 857 if (php.isset(value, 'attributes') and \ 858 php.is_array(value['attributes'])): 859 output += drupal_attributes(value['attributes']); 860 if (php.isset(value, 'value') and value['value'] != ''): 861 output += '>' + (format_xml_elements(value['value']) if \ 862 php.is_array(value['value']) else \ 863 check_plain(value['value'])) + '</' + value['key'] + ">\n"; 864 else: 865 output += " />\n"; 866 else: 867 output += ' <' + key + '>' + (format_xml_elements(value) if \ 868 php.is_array(value) else check_plain(value)) + "</" + key + ">\n"; 869 return output;
870 871 872
873 -def format_plural(count, singular, plural, args = {}, langcode = None):
874 """ 875 Format a string containing a count of items. 876 877 This function ensures that the string is pluralized correctly. Since t() is 878 called by this function, make sure not to pass already-localized strings to 879 it. 880 881 For example: 882 @code 883 output = format_plural(node.comment_count, '1 comment', \ 884 '@count comments'); 885 @endcode 886 887 Example with additional replacements: 888 @code 889 output = format_plural(update_count, 890 'Changed the content type of 1 post from %old-type to %new-type.', 891 'Changed the content type of @count posts from %old-type to %new-type.', 892 array('%old-type' : %(info)s.old_type, '%new-type' : info.new_type))); 893 @endcode 894 895 @param count 896 The item count to display. 897 @param singular 898 The string for the singular case. Please make sure it is clear this is 899 singular, to ease translation (e.g. use "1 new comment" 900 instead of "1 new"). 901 Do not use @count in the singular string. 902 @param plural 903 The string for the plural case. Please make sure it is clear 904 this is plural, 905 to ease translation. Use @count in place of the item count, as in "@count 906 new comments". 907 @param args 908 An associative array of replacements to make after translation. Incidences 909 of any key in this array are replaced with the corresponding value. 910 Based on the first character of the key, the value is 911 escaped and/or themed: 912 - !variable: inserted as is 913 - @variable: escape plain text to HTML (check_plain) 914 - %variable: escape text and theme as a placeholder for user-submitted 915 content (check_plain + theme_placeholder) 916 Note that you do not need to include @count in this array. 917 This replacement is done automatically for the plural case. 918 @param langcode 919 Optional language code to translate to a language other than 920 what is used to display the page. 921 @return 922 A translated string. 923 """ 924 args['@count'] = count; 925 if (count == 1): 926 return t(singular, args, langcode); 927 # Get the plural index through the gettext formula. 928 index = (locale_get_plural(count, langcode) if \ 929 php.function_exists('locale_get_plural') else -1); 930 # Backwards compatibility. 931 if (index < 0): 932 return t(plural, args, langcode); 933 else: 934 if index == "0": 935 return t(singular, args, langcode); 936 elif index == "1": 937 return t(plural, args, langcode); 938 else: 939 del(args['@count']); 940 args['@count[' + index + ']'] = count; 941 return t(php.strtr(plural, {'@count' : '@count[' + index + ']'}), \ 942 args, langcode);
943 944 945
946 -def parse_size(size):
947 """ 948 Parse a given byte count. 949 950 @param size 951 A size expressed as a number of bytes with optional SI size and unit 952 suffix (e.g. 2, 3K, 5MB, 10G). 953 @return 954 An integer representation of the size. 955 """ 956 suffixes = { 957 '' : 1, 958 'k' : 1024, 959 'm' : 1048576, # 1024 * 1024 960 'g' : 1073741824, # 1024 * 1024 * 1024 961 }; 962 match = [] 963 if (php.preg_match('/([0-9]+)\s*(k|m|g)?(b?(ytes?)?)/i', size, match) > 0): 964 return match[1] * suffixes[drupal_strtolower(match[2])];
965 966 967
968 -def format_size(size, langcode = None):
969 """ 970 Generate a string representation for the given byte count. 971 972 @param size 973 A size in bytes. 974 @param langcode 975 Optional language code to translate to a language other than what is used 976 to display the page. 977 @return 978 A translated string representation of the size. 979 """ 980 if (size < 1000): 981 return format_plural(size, '1 byte', '@count bytes', {}, langcode); 982 else: 983 size = size / 1000 # convert bytes to kilobytes (1000 bytes) 984 units = array('KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); 985 for sufix in units: 986 if (round(size, 2) >= 1000): 987 size = size / 1000; 988 else: 989 break 990 return t('@size @suffix', {'@size' : round(size, 2), '@suffix' : \ 991 suffix}, langcode);
992 993
994 -def format_interval(timestamp, granularity = 2, langcode = None):
995 """ 996 Format a time interval with the requested granularity. 997 998 @param timestamp 999 The length of the interval in seconds. 1000 @param granularity 1001 How many different units to display in the string. 1002 @param langcode 1003 Optional language code to translate to a language other than 1004 what is used to display the page. 1005 @return 1006 A translated string representation of the interval. 1007 """ 1008 units = { 1009 '1 year|@count years' : 31536000, 1010 '1 week|@count weeks' : 604800, 1011 '1 day|@count days' : 86400, 1012 '1 hour|@count hours' : 3600, 1013 '1 min|@count min' : 60, 1014 '1 sec|@count sec' : 1 1015 }; 1016 output = ''; 1017 for key,value in units.items(): 1018 key = php.explode('|', key); 1019 if (timestamp >= value): 1020 output += (' ' if (output != '') else '') + \ 1021 format_plural(floor(timestamp / value), key[0], key[1], {}, langcode); 1022 timestamp %= value; 1023 granularity -= 1; 1024 if (granularity == 0): 1025 break; 1026 return (output if (len(output) > 0) else t('0 sec', {}, langcode));
1027 1028 1029
1030 -def format_date(timestamp, type = 'medium', format = '', \ 1031 timezone = None, langcode = None):
1032 """ 1033 Format a date with the given configured format or a custom format string. 1034 1035 Drupal allows administrators to select formatting strings for 'small', 1036 'medium' and 'large' date formats. This function can handle these formats, 1037 as well as any custom format. 1038 1039 @param timestamp 1040 The exact date to format, as a UNIX timestamp. 1041 @param type 1042 The format to use. Can be "small", "medium" or "large" 1043 for the preconfigured 1044 date formats. If "custom" is specified, then format is required as well. 1045 @param format 1046 A PHP date format string as required by date(). A backslash should be used 1047 before a character to avoid interpreting the character as part of a date 1048 format. 1049 @param timezone 1050 Time zone offset in seconds; if omitted, the user's time zone is used. 1051 @param langcode 1052 Optional language code to translate to a language other than what is used 1053 to display the page. 1054 @return 1055 A translated date string in the requested format. 1056 """ 1057 if (timezone == None): 1058 if (variable_get('configurable_timezones', 1) and \ 1059 lib_appglobals.user.uid and \ 1060 php.strlen(lib_appglobals.user.timezone)): 1061 timezone = lib_appglobals.user.timezone; 1062 else: 1063 timezone = variable_get('date_default_timezone', 0); 1064 timestamp += timezone; 1065 if type == 'small': 1066 format = variable_get('date_format_short', 'm/d/Y - H:i'); 1067 elif type == 'large': 1068 format = variable_get('date_format_long', 'l, F j, Y - H:i'); 1069 elif type == 'custom': 1070 # No change to format. 1071 pass; 1072 elif type == 'medium' or True: 1073 format = variable_get('date_format_medium', 'D, m/d/Y - H:i'); 1074 max = php.strlen(format); 1075 date = ''; 1076 for i in range(max): 1077 c = format[i]; 1078 if (php.strpos('AaDlM', c) != False): 1079 date += t(php.gmdate(c, timestamp), {}, langcode); 1080 elif (c == 'F'): 1081 # Special treatment for long month names: May is both an abbreviation 1082 # and a full month name in English, but other languages have 1083 # different abbreviations. 1084 date += php.trim(t('!long-month-name ' + php.gmdate(c, timestamp), \ 1085 {'!long-month-name' : ''}, langcode)); 1086 elif (php.strpos('BdgGhHiIjLmnsStTUwWYyz', c) != False): 1087 date += php.gmdate(c, timestamp); 1088 elif (c == 'r'): 1089 date += format_date(timestamp - timezone, 'custom', \ 1090 'D, d M Y H:i:s O', timezone, langcode); 1091 elif (c == 'O'): 1092 date += sprintf('%s%02d%02d', ('-' if (timezone < 0) else '+'), \ 1093 abs(timezone / 3600), abs(timezone % 3600) / 60); 1094 elif (c == 'Z'): 1095 date += timezone; 1096 elif (c == '\\'): 1097 date += format[++i]; 1098 else: 1099 date += c; 1100 return date;
1101 1102 1103 1104 # 1105 # @} End of "defgroup format". 1106 # 1107
1108 -def url(path = None, options = {}):
1109 """ 1110 Generate a URL from a Drupal menu path. 1111 Will also pass-through existing URLs. 1112 1113 @param path 1114 The Drupal path being linked to, such as "admin/content/node", or an 1115 existing URL like "http://drupal.org/". The special path 1116 '<front>' may also be given and will generate the site's base URL. 1117 @param options 1118 An associative array of additional options, with the following keys: 1119 'query' 1120 A query string to append to the link, or an array of query key/value 1121 properties. 1122 'fragment' 1123 A fragment identifier (or named anchor) to append to the link. 1124 Do not include the '#' character. 1125 'absolute' (default False) 1126 Whether to force the output to be an absolute link (beginning with 1127 http:). Useful for links that will be displayed outside the site, such 1128 as in an RSS feed. 1129 'alias' (default False) 1130 Whether the given path is an alias already. 1131 'external' 1132 Whether the given path is an external URL. 1133 'language' 1134 An optional language object. Used to build the URL to link to and 1135 look up the proper alias for the link. 1136 'base_url' 1137 Only used internally, to modify the base URL when a language dependent 1138 URL requires so. 1139 'prefix' 1140 Only used internally, to modify the path when a language dependent URL 1141 requires so. 1142 @return 1143 A string containing a URL to the given path. 1144 1145 When creating links in plugins, consider whether l() could be a better 1146 alternative than url(). 1147 """ 1148 php.static(url, 'script') 1149 php.static(url, 'clean_url') 1150 # Merge in defaults. 1151 options = php.array_merge(options, { 1152 'fragment' : '', 1153 'query' : '', 1154 'absolute' : False, 1155 'alias' : False, 1156 'prefix' : '' 1157 }); 1158 if (not php.isset(options, 'external')): 1159 # Return an external link if path contains an allowed absolute URL. 1160 # Only call the slow filter_xss_bad_protocol if path contains a ':' before 1161 # any / ? or #. 1162 colonpos = php.strpos(path, ':'); 1163 options['external'] = (colonpos != False and \ 1164 php.preg_match('![/?#]!', php.substr(path, 0, colonpos) == 0) and \ 1165 filter_xss_bad_protocol(path, False) == check_plain(path)); 1166 # May need language dependent rewriting if language.inc is present. 1167 if (php.function_exists('language_url_rewrite')): 1168 language_url_rewrite(path, options); 1169 if (not php.empty(options['fragment'])): 1170 options['fragment'] = '#' + options['fragment']; 1171 if (php.is_array(options['query'])): 1172 options['query'] = drupal_query_string_encode(options['query']); 1173 if (not php.empty(options['external'])): 1174 # Split off the fragment. 1175 if (php.strpos(path, '#') != False): 1176 p1_ = php.explode('#', path, 2); 1177 (path, old_fragment) 1178 if php.isset(p1_, 0): 1179 path = p1_[0]; 1180 if php.isset(p1_, 1): 1181 old_fragment = p1_[1]; 1182 else: 1183 old_fragment = None; 1184 if (old_fragment != None and php.empty(options['fragment'])): 1185 options['fragment'] = '#' + old_fragment; 1186 # Append the query. 1187 if (not php.empty(options['query'])): 1188 path += ('&' if (php.strpos(path, '?') != False) else '?') + \ 1189 options['query']; 1190 # Reassemble. 1191 return path + options['fragment']; 1192 if (url.script == None): 1193 # On some web servers, such as IIS, we can't omit "index.php". So, we 1194 # generate "index.php?q=foo" instead of "?q=foo" on anything that is not 1195 # Apache. 1196 url.script = ('index.php' if \ 1197 (php.strpos(php.SERVER['php.SERVER_SOFTWARE'], 'Apache') == \ 1198 False) else ''); 1199 # Cache the clean_url variable to improve performance. 1200 if (url.clean_url == None): 1201 url.clean_url = drupy_bool(variable_get('clean_url', '0')); 1202 if (not php.isset(options, 'base_url')): 1203 # The base_url might be rewritten from the language rewrite in domain mode. 1204 options['base_url'] = settings.base_url; 1205 # Preserve the original path before aliasing. 1206 original_path = path; 1207 # The special path '<front>' links to the default front page. 1208 if (path == '<front>'): 1209 path = ''; 1210 elif (not php.empty(path) and not options['alias']): 1211 path = drupal_get_path_alias(path, (options['language'].language if \ 1212 php.isset(options, 'language') else '')); 1213 if (php.function_exists('custom_url_rewrite_outbound')): 1214 # Modules may alter outbound links by reference. 1215 custom_url_rewrite_outbound(path, options, original_path); 1216 base = ((options['base_url'] + '/') if \ 1217 options['absolute'] else base_path()); 1218 prefix = (php.rtrim(options['prefix'], '/') if php.empty(path) else \ 1219 options['prefix']); 1220 path = drupal_urlencode(prefix . path); 1221 if (clean_url): 1222 # With Clean URLs. 1223 if (options['query']): 1224 return base + path + '?' + options['query'] + options['fragment']; 1225 else: 1226 return base + path + options['fragment']; 1227 else: 1228 # Without Clean URLs. 1229 variables = []; 1230 if (not php.empty(path)): 1231 variables.append( 'q=' + path ); 1232 if (not php.empty(options['query'])): 1233 variables.append( options['query'] ); 1234 query = php.implode('&', variables); 1235 if (len(query) > 0): 1236 return base + url.script + '?' + query + options['fragment']; 1237 else: 1238 return base + options['fragment'];
1239 1240 1241
1242 -def drupal_attributes(attributes = {}):
1243 """ 1244 Format an attribute string to insert in a tag. 1245 1246 @param attributes 1247 An associative array of HTML attributes. 1248 @return 1249 An HTML string ready for insertion in a tag. 1250 """ 1251 if (php.is_array(attributes)): 1252 t = ''; 1253 for key,value in attributes.items(): 1254 t += ' %s=%s' % (key, check_plain(value)); 1255 return t;
1256 1257
1258 -def l(text, path, options = {}):
1259 """ 1260 Format an internal Drupal link. 1261 1262 This function correctly handles aliased paths, and allows 1263 themes to highlight 1264 links to the current page correctly, so all internal links output by plugins 1265 should be generated by this function if possible. 1266 1267 @param text 1268 The text to be enclosed with the anchor tag. 1269 @param path 1270 The Drupal path being linked to, such as "admin/content/node". Can be an 1271 external or internal URL. 1272 - If you provide the full URL, it will be considered an external URL. 1273 - If you provide only the path (e.g. "admin/content/node"), it is 1274 considered an internal link. In this case, it must be a system URL 1275 as the url() function will generate the alias. 1276 - If you provide '<front>', it generates a link to the site's 1277 base URL (again via the url() function). 1278 - If you provide a path, and 'alias' is set to True (see below), it is 1279 used as is. 1280 @param options 1281 An associative array of additional options, with the following keys: 1282 'attributes' 1283 An associative array of HTML attributes to apply to the anchor tag. 1284 'query' 1285 A query string to append to the link, or an array of query key/value 1286 properties. 1287 'fragment' 1288 A fragment identifier (named anchor) to append to the link. 1289 Do not include the '#' character. 1290 'absolute' (default False) 1291 Whether to force the output to be an absolute link (beginning with 1292 http:). Useful for links that will be displayed outside the site, such 1293 as in an RSS feed. 1294 'html' (default False) 1295 Whether the title is HTML, or just plain-text. For example for making 1296 an image a link, this must be set to True, or else you will see the 1297 escaped HTML. 1298 'alias' (default False) 1299 Whether the given path is an alias already. 1300 @return 1301 an HTML string containing a link to the given path. 1302 """ 1303 # Merge in defaults. 1304 options = php.array_merge(options, { 1305 'attributes' : {}, 1306 'html' : False, 1307 }); 1308 # Append active class. 1309 if ((path == php.GET['q']) or (path == '<front>' and \ 1310 drupal_is_front_page())): 1311 if (php.isset(options['attributes']['class'])): 1312 options['attributes']['class'] += ' active'; 1313 else: 1314 options['attributes']['class'] = 'active'; 1315 # Remove all HTML and PHP tags from a tooltip. 1316 # For best performance, we act only 1317 # if a quick php.strpos() pre-check gave a suspicion 1318 # (because strip_tags() is expensive). 1319 if (php.isset(options['attributes'], 'title') and \ 1320 php.strpos(options['attributes']['title'], '<') != False): 1321 options['attributes']['title'] = strip_tags(options['attributes']['title']) 1322 return '<a href="' + check_url(url(path, options)) + '"' + \ 1323 drupal_attributes(options['attributes']) + '>' + \ 1324 (text if options['html'] else check_plain(text)) + '</a>';
1325 1326 1327 1340 1341 1342
1343 -def drupal_map_assoc(array_, function = None):
1344 """ 1345 Form an associative array from a linear array. 1346 1347 This function walks through the provided array and constructs an associative 1348 array out of it. The keys of the resulting array will be the values of the 1349 input array. The values will be the same as the keys unless a function is 1350 specified, in which case the output of the function is used for the values 1351 instead. 1352 1353 @param array 1354 A linear array. 1355 @param function 1356 A name of a function to apply to all values before output. 1357 @result 1358 An associative array. 1359 """ 1360 if (function != None): 1361 result = {}; 1362 for key,value in array_.items(): 1363 result[value] = value; 1364 return result; 1365 elif (php.function_exists(function)): 1366 result = {}; 1367 for key,value in array_.items(): 1368 result[value] = function(value); 1369 return result;
1370 1371 1372
1373 -def drupal_eval(code):
1374 """ 1375 Evaluate a string of PHP code. 1376 1377 This is a wrapper around PHP's eval(). It uses output 1378 buffering to capture both 1379 returned and printed text. Unlike eval(), we require code to 1380 be surrounded by 1381 <?php ?> tags; in other words, we evaluate the code as if it 1382 were a stand-alone 1383 PHP file. 1384 1385 Using this wrapper also ensures that the PHP code which is 1386 evaluated can not 1387 overwrite any variables in the calling code, unlike a regular eval() call. 1388 1389 @param code 1390 The code to evaluate. 1391 @return 1392 A string containing the printed output of the code, 1393 followed by the returned 1394 output of the code. 1395 """ 1396 # Store current theme path. 1397 old_theme_path = lib_appglobals.theme_path; 1398 # Restore theme_path to the theme, as long as drupal_eval() executes, 1399 # so code evaluted will not see the caller plugin as the current theme. 1400 # If theme info is not initialized get the path from theme_default. 1401 if (not php.isset(locals(), lib_appglobals.theme_info, True)): 1402 lib_appglobals.theme_path = \ 1403 drupal_get_path('theme', settings.conf['theme_default']); 1404 else: 1405 lib_appglobals.theme_path = php.dirname(lib_appglobals.theme_info.filename); 1406 ob_start(); 1407 exec(code); 1408 output = ob_get_clean(); 1409 # Recover original theme path. 1410 lib_appglobals.theme_path = old_theme_path; 1411 return output;
1412 1413 1414
1415 -def drupal_get_path(type_, name):
1416 """ 1417 Returns the path to a system item (plugin, theme, etc.). 1418 1419 @param type 1420 The type of the item (i.e. theme, theme_engine, plugin). 1421 @param name 1422 The name of the item for which the path is requested. 1423 1424 @return 1425 The path to the requested item. 1426 """ 1427 return lib_bootstrap.drupal_get_filename(type_, name)
1428 1429 1430
1431 -def base_path():
1432 """ 1433 Returns the base URL path of the Drupal installation. 1434 At the very least, this will always default to /. 1435 """ 1436 return lib_appglobals.base_path
1437 1438 1444 1445
1446 -def drupal_add_css(path = None, type = 'plugin', media = 'all', \ 1447 preprocess = True):
1448 """ 1449 Adds a CSS file to the stylesheet queue. 1450 1451 @param path 1452 (optional) The path to the CSS file relative to the base_path(), e.g., 1453 /plugins/devel/devel.css. 1454 1455 Modules should always prefix the names of their CSS files with the plugin 1456 name, for example: system-menus.css rather than simply menus.css. Themes 1457 can override plugin-supplied CSS files based on their filenames, and this 1458 prefixing helps prevent confusing name collisions for theme developers. 1459 See drupal_get_css where the overrides are performed. 1460 1461 If the direction of the current language is right-to-left (Hebrew, 1462 Arabic, etc.), the function will also look for an RTL CSS file and append 1463 it to the list. The name of this file should have an '-rtl.css' suffix. 1464 For example a CSS file called 'name.css' will have a 'name-rtl.css' 1465 file added to the list, if exists in the same directory. This CSS file 1466 should contain overrides for properties which should be reversed or 1467 otherwise different in a right-to-left display. 1468 @param type 1469 (optional) The type of stylesheet that is being added. Types are: plugin 1470 or theme. 1471 @param media 1472 (optional) The media type for the stylesheet, e.g., all, print, screen. 1473 @param preprocess 1474 (optional) Should this CSS file be aggregated and compressed if this 1475 feature has been turned on under the performance section? 1476 1477 What does this actually mean? 1478 CSS preprocessing is the process of aggregating a bunch of separate CSS 1479 files into one file that is then compressed by removing all extraneous 1480 white space. 1481 1482 The reason for merging the CSS files is outlined quite thoroughly here: 1483 http://www.die.net/musings/page_load_time/ 1484 "Load fewer external objects. Due to request overhead, one bigger file 1485 just loads faster than two smaller ones half its size." 1486 1487 However, you should *not* preprocess every file as this can lead to 1488 redundant caches. You should set preprocess = False when: 1489 1490 - Your styles are only used rarely on the site. This could be a special 1491 admin page, the homepage, or a handful of pages that 1492 does not represent 1493 the majority of the pages on your site. 1494 1495 Typical candidates for caching are for example styles for nodes across 1496 the site, or used in the theme. 1497 @return 1498 An array of CSS files. 1499 """ 1500 php.static(drupal_add_css, 'css', {}) 1501 # Create an array of CSS files for each media type first, 1502 # since each type needs to be served 1503 # to the browser differently. 1504 if (path != None): 1505 # This check is necessary to ensure proper cascading of 1506 # styles and is faster than an asort(). 1507 if (not php.isset(drupal_add_css.css, media)): 1508 drupal_add_css.css[media] = {'plugin' : {}, 'theme' : {}}; 1509 drupal_add_css.css[media][type][path] = preprocess; 1510 # If the current language is RTL, add the CSS file with RTL overrides. 1511 if (php.defined('LANGUAGE_RTL') and \ 1512 lib_appglobals.language.direction == LANGUAGE_RTL): 1513 rtl_path = php.str_replace('.css', '-rtl.css', path); 1514 if (php.file_exists(rtl_path)): 1515 drupal_add_css.css[media][type][rtl_path] = preprocess; 1516 return drupal_add_css.css;
1517 1518 1519
1520 -def drupal_get_css(css = None):
1521 """ 1522 Returns a themed representation of all stylesheets that 1523 should be attached to the page. 1524 1525 It loads the CSS in order, with 'plugin' first, then 'theme' afterwards. 1526 This ensures proper cascading of styles so themes can easily override 1527 plugin styles through CSS selectors. 1528 1529 Themes may replace plugin-defined CSS files by adding a stylesheet with the 1530 same filename. For example, themes/garland/system-menus.css would replace 1531 plugins/system/system-menus.css. This allows themes to override complete 1532 CSS files, rather than specific selectors, when necessary. 1533 1534 If the original CSS file is being overridden by a theme, the theme is 1535 responsible for supplying an accompanying RTL CSS file to replace the 1536 plugin's. 1537 1538 @param css 1539 (optional) An array of CSS files. If no array is provided, the default 1540 stylesheets array is used instead. 1541 @return 1542 A string of XHTML CSS tags. 1543 """ 1544 output = ''; 1545 if (css == None): 1546 css = drupal_add_css(); 1547 no_plugin_preprocess = ''; 1548 no_theme_preprocess = ''; 1549 preprocess_css = ((variable_get('preprocess_css', False) and \ 1550 (not php.defined('MAINTENANCE_MODE') or MAINTENANCE_MODE != 'update'))); 1551 directory = file_directory_path(); 1552 is_writable = (php.is_dir(directory) and php.is_writable(directory) and \ 1553 (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == \ 1554 FILE_DOWNLOADS_PUBLIC)); 1555 # A dummy query-string is added to filenames, to gain control over 1556 # browser-caching. The string changes on every update or full cache 1557 # php.flush, forcing browsers to load a new copy of the files, as the 1558 # URL changed. 1559 query_string = '?' + php.substr(\ 1560 variable_get('css_js_query_string', '0'), 0, 1); 1561 for media,types in css.items(): 1562 # If CSS preprocessing is off, we still need to output the styles. 1563 # Additionally, go through any remaining styles if CSS 1564 # preprocessing is on and output the non-cached ones. 1565 for type_,files in types.items(): 1566 if (type_ == 'plugin'): 1567 # Setup theme overrides for plugin styles. 1568 theme_styles = []; 1569 for theme_style in php.array_keys(css[media]['theme']): 1570 theme_styles.append( basename(theme_style) ); 1571 for file_,preprocess in types[type_].items(): 1572 # If the theme supplies its own style using the name 1573 # of the plugin style, skip its inclusion. 1574 # This includes any RTL styles associated with its 1575 # main LTR counterpart. 1576 if (type_ == 'plugin' and php.in_array(\ 1577 php.str_replace('-rtl.css', '.css', \ 1578 basename(file)), theme_styles)): 1579 # Unset the file to prevent its inclusion when 1580 # CSS aggregation is enabled. 1581 del(types[type_][file]); 1582 continue; 1583 # Only include the stylesheet if it exists. 1584 if (lib_file.exists(file)): 1585 if (not preprocess and not (is_writable and preprocess_css)): 1586 # If a CSS file is not to be preprocessed and it's a 1587 # module CSS file, it needs to *always* appear at the *top*, 1588 # regardless of whether preprocessing is on or off. 1589 if (not preprocess and type_ == 'module'): 1590 no_module_preprocess += \ 1591 '<link type="text/css" rel="stylesheet" media="' + media + \ 1592 '" href="' + base_path() + file + query_string + '" />' + "\n" 1593 # If a CSS file is not to be preprocessed and it's a 1594 # theme CSS file, it needs to *always* appear at the *bottom*, 1595 # regardless of whether preprocessing is on or off. 1596 elif (not preprocess and type_ == 'theme'): 1597 no_theme_preprocess += \ 1598 '<link type="text/css" rel="stylesheet" media="' + \ 1599 media + '" href="' + base_path() + file + \ 1600 query_string + '" />' + "\n"; 1601 else: 1602 output += '<link type="text/css" rel="stylesheet" media="' + \ 1603 media + '" href="' + base_path() + file + query_string + \ 1604 '" />' + "\n"; 1605 if (is_writable and preprocess_css): 1606 filename = php.md5(php.serialize(types) + query_string) + '.css'; 1607 preprocess_file = drupal_build_css_cache(types, filename); 1608 output += '<link type="text/css" rel="stylesheet" media="' + media + \ 1609 '" href="' + base_path() + preprocess_file + '" />' + "\n"; 1610 return no_plugin_preprocess . output . no_theme_preprocess;
1611 1612 1613
1614 -def drupal_build_css_cache(types, filename):
1615 """ 1616 Aggregate and optimize CSS files, putting them in the files directory. 1617 1618 @param types 1619 An array of types of CSS files (e.g., screen, print) to aggregate and 1620 compress into one file. 1621 @param filename 1622 The name of the aggregate CSS file. 1623 @return 1624 The name of the CSS file. 1625 """ 1626 data = ''; 1627 # Create the css/ within the files folder. 1628 csspath = file_create_path('css'); 1629 file_check_directory(csspath, FILE_CREATE_DIRECTORY); 1630 if (not php.file_exists(csspath + '/' + filename)): 1631 # Build aggregate CSS file. 1632 for type_ in types: 1633 for file_,cache in type_.items(): 1634 if (not php.empty(cache)): 1635 contents = drupal_load_stylesheet(file_, True); 1636 # Return the path to where this CSS file originated from. 1637 base = base_path() + php.dirname(file_) + '/'; 1638 _drupal_build_css_path(None, base); 1639 # Prefix all paths within this CSS file, ignoring 1640 # external and absolute paths. 1641 data += php.preg_replace_callback(\ 1642 '/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', \ 1643 '_drupal_build_css_path', contents); 1644 # Per the W3C specification at 1645 # http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, 1646 # @import rules must proceed any other style, so we move those to the top. 1647 regexp = '/@import[^;]+;/i'; 1648 matches = [] 1649 php.preg_match_all(regexp, data, matches); 1650 data = php.preg_replace(regexp, '', data); 1651 data = php.implode('', matches[0]) . data; 1652 # Create the CSS file. 1653 file_save_data(data, csspath + '/' + filename, FILE_EXISTS_REPLACE); 1654 return csspath + '/' + filename;
1655 1656 1657
1658 -def _drupal_build_css_path(matches, base = None):
1659 """ 1660 Helper function for drupal_build_css_cache(). 1661 1662 This function will prefix all paths within a CSS file. 1663 """ 1664 php.static(_drupal_build_css_path, 'base', base) 1665 # Store base path for preg_replace_callback. 1666 # Prefix with base and remove '../' segments where possible. 1667 path = _drupal_build_css_path.base + matches[1]; 1668 last = ''; 1669 while (path != last): 1670 last = path; 1671 path = php.preg_replace('`(^|/)(?!../)([^/]+)/../`', '%(1)s', path); 1672 return 'url(' + path + ')';
1673 1674 1675
1676 -def drupal_load_stylesheet(file, optimize = None):
1677 """ 1678 Loads the stylesheet and resolves all @import commands. 1679 1680 Loads a stylesheet and replaces @import commands with the contents of the 1681 imported file. Use this instead of file_get_contents when processing 1682 stylesheets. 1683 1684 The returned contents are compressed removing white space and comments only 1685 when CSS aggregation is enabled. This optimization will not apply for 1686 color.plugin enabled themes with CSS aggregation turned off. 1687 1688 @param file 1689 Name of the stylesheet to be processed. 1690 @param optimize 1691 Defines if CSS contents should be compressed or not. 1692 @return 1693 Contents of the stylesheet including the imported stylesheets. 1694 """ 1695 php.static(drupal_load_stylesheet, 'optimize_', optimize) 1696 # Store optimization parameter for preg_replace_callback with 1697 # nested @import loops. 1698 contents = ''; 1699 if (php.file_exists(file)): 1700 # Load the local CSS stylesheet. 1701 contents = file_get_contents(file); 1702 # Change to the current stylesheet's directory. 1703 cwd = getcwd(); 1704 chdir(php.dirname(file)); 1705 # Replaces @import commands with the actual stylesheet content. 1706 # This happens recursively but omits external files. 1707 contents = php.preg_replace_callback(\ 1708 '/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', \ 1709 '_drupal_load_stylesheet', contents); 1710 # Remove multiple charset declarations for standards compliance 1711 # (and fixing Safari problems). 1712 contents = php.preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', \ 1713 contents); 1714 if (not php.empty(drupal_load_stylesheet.optimize_)): 1715 # Perform some safe CSS optimizations. 1716 contents = php.preg_replace( 1717 '<' + 1718 # Remove whitespace around separators, but keep 1719 # space around parentheses. 1720 "\s*([@{}:;,]|\)\s|\s\()\s* |" + \ 1721 # Remove comments that are not CSS hacks. 1722 '/\*([^*\\\\]|\*(?!/))+\*/ |' + \ 1723 # Remove line breaks. 1724 '[\n\r]' + \ 1725 '>x', \ 1726 '\1', \ 1727 contents \ 1728 ); 1729 # Change back directory. 1730 chdir(cwd); 1731 return contents;
1732 1733 1734
1735 -def _drupal_load_stylesheet(matches):
1736 """ 1737 Loads stylesheets recursively and returns contents with corrected paths. 1738 1739 This function is used for recursive loading of stylesheets and 1740 returns the stylesheet content with all url() paths corrected. 1741 """ 1742 filename = matches[1]; 1743 # Load the imported stylesheet and replace @import commands in there as well. 1744 file = drupal_load_stylesheet(filename); 1745 # Alter all url() paths, but not external. 1746 return php.preg_replace('/url\(([\'"]?)(?![a-z]+:)([^\'")]+)[\'"]?\)?;/i', \ 1747 'url(\1' + php.dirname(filename) + '/', file);
1748 1749
1750 -def drupal_clear_css_cache():
1751 """ 1752 Delete all cached CSS files. 1753 """ 1754 file_scan_directory(file_create_path('css'), '.*', ['.', '..', 'CVS'], \ 1755 'file_delete', True);
1756 1757
1758 -def drupal_add_js(data = None, type_ = 'plugin', scope = 'php.header', \ 1759 defer = False, cache = True, preprocess = True):
1760 """ 1761 Add a JavaScript file, setting or inline code to the page. 1762 1763 The behavior of this function depends on the parameters it is called with. 1764 Generally, it handles the addition of JavaScript to the page, either as 1765 reference to an existing file or as inline code. 1766 The following actions can be 1767 performed using this function: 1768 1769 - Add a file ('core', 'plugin' and 'theme'): 1770 Adds a reference to a JavaScript file to the page. JavaScript files 1771 are placed in a certain order, from 'core' first, to 'plugin' and finally 1772 'theme' so that files, that are added later, can override previously added 1773 files with ease. 1774 1775 - Add inline JavaScript code ('inline'): 1776 Executes a piece of JavaScript code on the current page by 1777 placing the code 1778 directly in the page. This can, for example, be useful 1779 to tell the user that 1780 a new message arrived, by opening a pop up, alert box etc. 1781 1782 - Add settings ('setting'): 1783 Adds a setting to Drupal's global storage of JavaScript settings. Per-page 1784 settings are required by some plugins to function properly. The settings 1785 will be accessible at Drupal.settings. 1786 1787 @param data 1788 (optional) If given, the value depends on the type parameter: 1789 - 'core', 'plugin' or 'theme': Path to the file relative to base_path(). 1790 - 'inline': The JavaScript code that should be placed in the given scope. 1791 - 'setting': An array with configuration options as associative array. The 1792 array is directly placed in Drupal.settings. 1793 You might want to wrap your 1794 actual configuration settings in another variable to 1795 prevent the pollution 1796 of the Drupal.settings namespace. 1797 @param type 1798 (optional) The type of JavaScript that should be added to 1799 the page. Allowed 1800 values are 'core', 'plugin', 'theme', 'inline' and 'setting'. You 1801 can, however, specify any value. It is treated as a reference 1802 to a JavaScript 1803 file. Defaults to 'plugin'. 1804 @param scope 1805 (optional) The location in which you want to place the script. Possible 1806 values are 'php.header' and 'footer' by default. If your theme implements 1807 different locations, however, you can also use these. 1808 @param defer 1809 (optional) If set to True, the defer attribute is set on the <script> tag. 1810 Defaults to False. This parameter is not used with type == 'setting'. 1811 @param cache 1812 (optional) If set to False, the JavaScript file is loaded 1813 anew on every page 1814 call, that means, it is not cached. Defaults to True. Used only when type 1815 references a JavaScript file. 1816 @param preprocess 1817 (optional) Should this JS file be aggregated if this 1818 feature has been turned on under the performance section? 1819 @return 1820 If the first parameter is None, the JavaScript array that 1821 has been built so 1822 far for scope is returned. If the first three parameters are None, 1823 an array with all scopes is returned. 1824 """ 1825 php.static(drupal_add_js, 'javascript', {}) 1826 if (data != None): 1827 # Add jquery.js and drupal.js, as well as the basePath setting, the 1828 # first time a Javascript file is added. 1829 if (php.empty(drupal_add_js.javascript)): 1830 javascript['php.header'] = { 1831 'core' : { 1832 'misc/jquery.js' : {'cache' : True, 'defer' : False, \ 1833 'preprocess' : True}, 1834 'misc/drupal.js' : {'cache' : True, 'defer' : False, \ 1835 'preprocess' : True} 1836 }, 1837 'plugin' : {}, 1838 'theme' : {}, 1839 'setting' : [ 1840 {'basePath' : base_path()} 1841 ], 1842 'inline' : {}, 1843 }; 1844 if (not php.empty(scope) and not \ 1845 php.isset(drupal_add_js.javascript, scope)): 1846 drupal_add_js.javascript[scope] = {'core' : {}, 'plugin' : {}, \ 1847 'theme' : {}, 'setting' : {}, 'inline' : {}}; 1848 if (not php.empty(type) and not php.empty(scope) and not \ 1849 php.isset(drupal_add_js.javascript[scope], type_)): 1850 drupal_add_js.javascript[scope][type_] = []; 1851 if type == 'setting': 1852 drupal_add_js.javascript[scope][type_].append(data); 1853 elif type == 'inline': 1854 drupal_add_js.javascript[scope][type_].append({'code' : data, \ 1855 'defer' : defer}); 1856 else: 1857 # If cache is False, don't preprocess the JS file. 1858 drupal_add_js.javascript[scope][type_][data] = {'cache' : \ 1859 cache, 'defer' : defer, 'preprocess' : (False if not \ 1860 cache else preprocess)}; 1861 if (not php.empty(scope)): 1862 if (php.isset(drupal_add_js.javascript, scope)): 1863 return drupal_add_js.javascript[scope]; 1864 else: 1865 return {}; 1866 else: 1867 return drupal_add_js.javascript;
1868 1869 1870 1871
1872 -def drupal_get_js(scope = 'php.header', javascript = None):
1873 """ 1874 Returns a themed presentation of all JavaScript code for the current page. 1875 1876 References to JavaScript files are placed in a certain order: first, all 1877 'core' files, then all 'plugin' and finally all 'theme' JavaScript files 1878 are added to the page. Then, all settings are output, followed by 'inline' 1879 JavaScript code. If running update.php, all preprocessing is disabled. 1880 1881 @parameter scope 1882 (optional) The scope for which the JavaScript rules should be returned. 1883 Defaults to 'php.header'. 1884 @parameter javascript 1885 (optional) An array with all JavaScript code. Defaults to the default 1886 JavaScript array for the given scope. 1887 @return 1888 All JavaScript code segments and includes for the scope as HTML tags. 1889 """ 1890 if ((not php.defined('MAINTENANCE_MODE') or \ 1891 MAINTENANCE_MODE != 'update') and \ 1892 php.function_exists('locale_update_js_files')): 1893 locale_update_js_files(); 1894 if (javascript == None): 1895 javascript = drupal_add_js(None, None, scope); 1896 if (php.empty(javascript)): 1897 return ''; 1898 output = ''; 1899 preprocessed = ''; 1900 no_preprocess = {'core' : '', 'plugin' : '', 'theme' : ''}; 1901 files = {}; 1902 preprocess_js = (variable_get('preprocess_js', False) and \ 1903 (not php.defined('MAINTENANCE_MODE') or MAINTENANCE_MODE != 'update')); 1904 directory = file_directory_path(); 1905 is_writable_ = php.is_dir(directory) and php.is_writable(directory) and \ 1906 (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == \ 1907 FILE_DOWNLOADS_PUBLIC); 1908 # A dummy query-string is added to filenames, to gain control over 1909 # browser-caching. The string changes on every update or full cache 1910 # php.flush, forcing browsers to load a new copy of the files, as the 1911 # URL changed. Files that should not be cached (see drupal_add_js()) 1912 # get time() as query-string instead, to enforce reload on every 1913 # page request. 1914 query_string = '?' + \ 1915 php.substr(variable_get('css_js_query_string', '0'), 0, 1); 1916 # For inline Javascript to validate as XHTML, all Javascript containing 1917 # XHTML needs to be wrapped in CDATA. To make that backwards compatible 1918 # with HTML 4, we need to comment out the CDATA-tag. 1919 embed_prefix = "\n<!--//--><![CDATA[//><!--\n" 1920 embed_suffix = "\n//--><!]]>\n" 1921 for type_,data in javascript.items(): 1922 if (php.empty(data)): 1923 continue; 1924 if type_ == 'setting': 1925 output += '<script type="text/javascript">' + embed_prefix + \ 1926 'jQuery.extend(Drupal.settings, ' + \ 1927 drupal_to_js(\ 1928 php.call_user_func_array('array_merge_recursive', data)) + \ 1929 ");" + embed_suffix + "</script>\n"; 1930 elif type_ == 'inline': 1931 for infoKey_,info in data.items(): 1932 output += '<script type="text/javascript"' + \ 1933 (' defer="defer"' if info['defer'] else '') + '>' + \ 1934 embed_prefix + info['code'] + embed_suffix + "</script>\n"; 1935 else: 1936 # If JS preprocessing is off, we still need to output the scripts. 1937 # Additionally, go through any remaining scripts if JS 1938 # preprocessing is on and output the non-cached ones. 1939 for path,info in data.items(): 1940 if (not info['preprocess'] or not is_writable_ or not preprocess_js): 1941 no_preprocess[type] += '<script type="text/javascript"' + \ 1942 (' defer="defer"' if info['defer'] else '') + ' src="' + \ 1943 base_path() + path + (query_string if info['cache'] else \ 1944 '?' + drupy_time()) + "\"></script>\n"; 1945 else: 1946 files[path] = info; 1947 # Aggregate any remaining JS files that haven't already been output. 1948 if (is_writable and preprocess_js and php.count(files) > 0): 1949 filename = php.md5(php.serialize(files) + query_string) + '.js' 1950 preprocess_file = drupal_build_js_cache(files, filename) 1951 preprocessed += '<script type="text/javascript" src="' + base_path() + \ 1952 preprocess_file + '"></script>' + "\n" 1953 # Keep the order of JS files consistent as some are 1954 # preprocessed and others are not. 1955 # Make sure any inline or JS setting variables appear last 1956 # after libraries have loaded. 1957 output = preprocessed + php.implode('', no_preprocess) + output; 1958 return output;
1959 1960 1961
1962 -def drupal_add_tabledrag(table_id, action, relationship, group, \ 1963 subgroup = None, source = None, hidden = True, limit = 0):
1964 """ 1965 Assist in adding the tableDrag JavaScript behavior to a themed table. 1966 1967 Draggable tables should be used wherever an outline 1968 or list of sortable items 1969 needs to be arranged by an end-user. Draggable tables are very flexible and 1970 can manipulate the value of form elements placed within individual columns. 1971 1972 To set up a table to use drag and drop in place of weight select-lists or 1973 in place of a form that contains parent relationships, the form must be 1974 themed into a table. The table must have an id attribute set. If using 1975 theme_table(), the id may be set as such: 1976 @code 1977 output = theme('table', %(php.header)s, rows, array('id' : \ 1978 'my-plugin-table')); 1979 return output; 1980 @endcode 1981 1982 In the theme function for the form, a special class must be added to each 1983 form element within the same column, "grouping" them together. 1984 1985 In a situation where a single weight column is 1986 being sorted in the table, the 1987 classes could be added like this (in the theme function): 1988 @code 1989 form['my_elements'][%(delta)s]['weight']['#attributes']['class'] = \ 1990 "my-elements-weight"; 1991 @endcode 1992 1993 Each row of the table must also have a class of "draggable" 1994 in order to enable the 1995 drag handles: 1996 @code 1997 row = array(...); 1998 rows[] = array( 1999 'data' : row, 2000 'class' : 'draggable', 2001 ); 2002 @endcode 2003 2004 When tree relationships are present, the two additional classes 2005 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior: 2006 - Rows with the 'tabledrag-leaf' class cannot have child rows. 2007 - Rows with the 'tabledrag-root' class cannot be nested under a parent row. 2008 2009 Calling drupal_add_tabledrag() would then be written as such: 2010 @code 2011 drupal_add_tabledrag('my-plugin-table', 'order', 'sibling', \ 2012 'my-elements-weight'); 2013 @endcode 2014 2015 In a more complex case where there are several groups in one column (such as 2016 the block regions on the admin/build/block page), a separate subgroup class 2017 must also be added to differentiate the groups. 2018 @code 2019 form['my_elements'][%(region)s][delta]['weight']['#attributes']['class'] = \ 2020 "my-elements-weight my-elements-weight-". region; 2021 @endcode 2022 2023 group is still 'my-element-weight', and the additional subgroup variable 2024 will be passed in as 'my-elements-weight-'. region. This also means that 2025 you'll need to call drupal_add_tabledrag() once for every region added. 2026 2027 @code 2028 foreach (regions as region) { 2029 drupal_add_tabledrag('my-plugin-table', 'order', 'sibling', \ 2030 'my-elements-weight', 'my-elements-weight-'. region); 2031 } 2032 @endcode 2033 2034 In a situation where tree relationships are present, adding multiple 2035 subgroups is not necessary, because the table will contain indentations that 2036 provide enough information about the sibling and parent relationships. 2037 See theme_menu_overview_form() for an example creating a table containing 2038 parent relationships. 2039 2040 Please note that this function should be called from the theme layer, 2041 such as 2042 in a .tpl.php file, theme_ function, or in a template_preprocess function, 2043 not in a form declartion. Though the same JavaScript could be added to the 2044 page using drupal_add_js() directly, this function helps keep template files 2045 clean and readable. It also prevents tabledrag.js from being added twice 2046 accidentally. 2047 2048 @param table_id 2049 String containing the target table's id attribute. If the table does not 2050 have an id, one will need to be set, such as <table id="my-plugin-table">. 2051 @param action 2052 String describing the action to be done on the form item. Either 'match' 2053 'depth', or 'order'. Match is typically used for parent relationships. 2054 Order is typically used to set weights on other form elements with 2055 the same 2056 group. Depth updates the target element with the current indentation. 2057 @param relationship 2058 String describing where the action variable should be performed. Either 2059 'parent', 'sibling', 'group', or 'self'. Parent will only look for fields 2060 up the tree. Sibling will look for fields in the same group in rows above 2061 and below it. Self affects the dragged row itself. Group affects the 2062 dragged row, plus any children below it (the entire dragged group). 2063 @param group 2064 A class name applied on all related form elements for this action. 2065 @param subgroup 2066 (optional) If the group has several subgroups within it, 2067 this string should 2068 contain the class name identifying fields in the same subgroup. 2069 @param source 2070 (optional) If the action is 'match', this string should contain the class 2071 name identifying what field will be used as the source value when matching 2072 the value in subgroup. 2073 @param hidden 2074 (optional) The column containing the field elements may be entirely hidden 2075 from view dynamically when the JavaScript is loaded. Set to False if the 2076 column should not be hidden. 2077 @param limit 2078 (optional) Limit the maximum amount of parenting in this table. 2079 @see block-admin-display-form.tpl.php 2080 @see theme_menu_overview_form() 2081 """ 2082 php.static(drupal_add_tabledrag, 'js_added', False) 2083 if (not drupal_add_tabledrag.js_added): 2084 drupal_add_js('misc/tabledrag.js', 'core'); 2085 drupal_add_tabledrag.js_added = True; 2086 # If a subgroup or source isn't set, assume it is the same as the group. 2087 target = (subgroup if (subgroup != None) else group); 2088 source = (source if source != None else target); 2089 settings['tableDrag'][table_id][group].append({ 2090 'target' : target, 2091 'source' : source, 2092 'relationship' : relationship, 2093 'action' : action, 2094 'hidden' : hidden, 2095 'limit' : limit, 2096 }); 2097 drupal_add_js(settings, 'setting');
2098 2099 2100
2101 -def drupal_build_js_cache(files, filename):
2102 """ 2103 Aggregate JS files, putting them in the files directory. 2104 2105 @param files 2106 An array of JS files to aggregate and compress into one file. 2107 @param filename 2108 The name of the aggregate JS file. 2109 @return 2110 The name of the JS file. 2111 """ 2112 contents = ''; 2113 # Create the js/ within the files folder. 2114 jspath = file_create_path('js'); 2115 file_check_directory(jspath, FILE_CREATE_DIRECTORY); 2116 if (not php.file_exists(jspath + '/' + filename)): 2117 # Build aggregate JS file. 2118 for path,info in files.items(): 2119 if (not php.empty(info['preprocess'])): 2120 # Append a ';' after each JS file to prevent them from 2121 # running together. 2122 contents += file_get_contents(path) + ';'; 2123 # Create the JS file. 2124 file_save_data(contents, jspath + '/' + filename, FILE_EXISTS_REPLACE); 2125 return jspath + '/' + filename;
2126 2127
2128 -def drupal_clear_js_cache():
2129 """ 2130 Delete all cached JS files. 2131 """ 2132 file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), \ 2133 'file_delete', True); 2134 variable_set('javascript_parsed', array());
2135 2136 2137
2138 -def drupal_to_js(var):
2139 """ 2140 Converts a PHP variable into its Javascript equivalent. 2141 2142 We use HTML-safe strings, i.e. with <, > and & escaped. 2143 """ 2144 # json_encode() does not escape <, > and &, so we do 2145 # it with php.str_replace() 2146 return php.str_replace(array("<", ">", "&"), \ 2147 array('\x3c', '\x3e', '\x26'), json_encode(var));
2148 2149 2150
2151 -def drupal_json(var = None):
2152 """ 2153 Return data in JSON format. 2154 2155 This function should be used for JavaScript callback functions returning 2156 data in JSON format. It sets the php.header for JavaScript output. 2157 2158 @param var 2159 (optional) If set, the variable will be converted to JSON and output. 2160 """ 2161 # We are returning JavaScript, so tell the browser. 2162 drupal_set_header('Content-Type: text/javascript; charset=utf-8'); 2163 if (var != None): 2164 print drupal_to_js(var);
2165 2166 2167
2168 -def drupal_urlencode(text):
2169 """ 2170 Wrapper around urlencode() which avoids Apache quirks. 2171 2172 Should be used when placing arbitrary data in an URL. Note that Drupal paths 2173 are urlencoded() when passed through url() and do not require urlencoding() 2174 of individual components. 2175 2176 Notes: 2177 - For esthetic reasons, we do not escape slashes. 2178 This also avoids a 'feature' 2179 in Apache where it 404s on any path containing '%2F'. 2180 - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean 2181 URLs are used, which are interpreted as delimiters by PHP. These 2182 characters are double escaped so PHP will still see the encoded version. 2183 - With clean URLs, Apache changes '//' to '/', so every second slash is 2184 double escaped. 2185 2186 @param text 2187 String to encode 2188 """ 2189 if (variable_get('clean_url', '0')): 2190 return php.str_replace( 2191 ['%2F', '%26', '%23', '//'], 2192 ['/', '%2526', '%2523', '/%252F'], 2193 rawurlencode(text) 2194 ); 2195 else: 2196 return php.str_replace('%2F', '/', rawurlencode(text));
2197 2198 2199
2200 -def drupal_random_bytes(count_):
2201 """ 2202 Returns a string of highly randomized bytes (over the full 8-bit range). 2203 2204 This function is better than simply 2205 calling php.mt_rand() or any other built-in 2206 PHP function because it can return a long string of bytes (compared to < 4 2207 bytes normally from php.mt_rand()) and uses the 2208 best available pseudo-random source. 2209 2210 @param $count 2211 The number of characters (bytes) to return in the string. 2212 """ 2213 # We initialize with the somewhat random PHP process ID on the first call. 2214 php.static(drupal_random_bytes, 'random_state', getmypid()) 2215 output = ''; 2216 # /dev/urandom is available on many *nix systems and is considered the best 2217 # commonly available pseudo-random source. 2218 fh = fopen('/dev/urandom', 'rb') 2219 if (fh != False): 2220 output = fread(fh, count_); 2221 fclose(fh); 2222 # If /dev/urandom is not available or returns no bytes, this loop will 2223 # generate a good set of pseudo-random bytes on any system. 2224 # Note that it may be important that our $random_state is passed 2225 # through php.md5() prior to being rolled into $output, that the two 2226 # php.md5() 2227 # invocations are different, and that the extra input into the first one - 2228 # the php.microtime() - is prepended rather than appended. This is to avoid 2229 # directly leaking $random_state via the $output stream, which could 2230 # allow for trivial prediction of further "random" numbers. 2231 while (php.strlen(output) < count_): 2232 drupal_random_bytes.random_state = php.md5(php.microtime() + \ 2233 php.mt_rand() + drupal_random_bytes.random_state); 2234 output += php.md5(php.mt_rand() + drupal_random_bytes.random_state, True); 2235 return php.substr(output, 0, count);
2236 2237
2238 -def drupal_get_private_key():
2239 """ 2240 Ensure the private key variable used to generate tokens is set. 2241 2242 @return 2243 The private key. 2244 """ 2245 key = variable_get('drupal_private_key', 0); 2246 if (not key): 2247 key = php.md5(drupal_random_bytes(64)); 2248 variable_set('drupal_private_key', key); 2249 return key;
2250 2251 2252
2253 -def drupal_get_token(value = ''):
2254 """ 2255 Generate a token based on value, the current user session and private key. 2256 2257 @param value 2258 An additional value to base the token on. 2259 """ 2260 private_key = drupal_get_private_key(); 2261 return php.md5(session_id() + value + private_key);
2262 2263 2264
2265 -def drupal_valid_token(token, value = '', skip_anonymous = False):
2266 """ 2267 Validate a token based on value, the current user session and private key. 2268 2269 @param token 2270 The token to be validated. 2271 @param value 2272 An additional value to base the token on. 2273 @param skip_anonymous 2274 Set to true to skip token validation for anonymous users. 2275 @return 2276 True for a valid token, false for an invalid token. When skip_anonymous 2277 is true, the return value will always be true for anonymous users. 2278 """ 2279 return ((skip_anonymous and lib_appglobals.user.uid == 0) or \ 2280 (token == php.md5(session_id() + value + \ 2281 variable_get('drupal_private_key', ''))));
2282 2283 2284
2285 -def xmlrpc(url):
2286 """ 2287 Performs one or more XML-RPC request(s). 2288 2289 @param url 2290 An absolute URL of the XML-RPC endpoint. 2291 Example: 2292 http://www.example.com/xmlrpc.php 2293 @param ... 2294 For one request: 2295 The method name followed by a variable number 2296 of arguments to the method. 2297 For multiple requests (system.multicall): 2298 An array of call arrays. Each call array follows the 2299 pattern of the single 2300 request: method name followed by the arguments to the method. 2301 @return 2302 For one request: 2303 Either the return value of the method on success, or False. 2304 If False is returned, see xmlrpc_errno() and xmlrpc_error_msg(). 2305 For multiple requests: 2306 An array of results. Each result will either be the result 2307 returned by the method called, or an xmlrpc_error object if the call 2308 failed. See xmlrpc_error(). 2309 """ 2310 php.require_once('./includes/xmlrpc.inc'); 2311 args = func_get_args(); 2312 return php.call_user_func_array('_xmlrpc', args);
2313 2314 2315 2316
2317 -def _drupal_bootstrap_full():
2318 php.static(_drupal_bootstrap_full, 'called', False) 2319 if (_drupal_bootstrap_full.called): 2320 return; 2321 else: 2322 _drupal_bootstrap_full.called = True; 2323 # Emit the correct charset HTTP php.header. 2324 drupal_set_header('Content-Type: text/html; charset=utf-8'); 2325 # Detect string handling method 2326 lib_unicode.check(); 2327 # Load all enabled plugins 2328 lib_plugin.load_all(); 2329 # Let all plugins take action before menu system handles the request 2330 # We do not want this while running update.php. 2331 if (not php.defined('MAINTENANCE_MODE') or MAINTENANCE_MODE != 'update'): 2332 lib_plugin.invoke_all('init');
2333 2334 2335
2336 -def page_set_cache():
2337 """ 2338 Store the current page in the cache. 2339 2340 We try to store a gzipped version of the cache. This requires the 2341 PHP zlib extension (http://php.net/manual/en/ref.zlib.php). 2342 Presence of the extension is checked by testing for the function 2343 gzencode. There are two compression algorithms: gzip and deflate. 2344 The majority of all modern browsers support gzip or both of them. 2345 We thus only deal with the gzip variant and unzip the cache in case 2346 the browser does not accept gzip encoding. 2347 2348 @see drupal_page_header 2349 """ 2350 if (not lib_appglobals.user.uid and (php.SERVER['REQUEST_METHOD'] == 'GET' or \ 2351 php.SERVER['REQUEST_METHOD'] == 'HEAD') and \ 2352 php.count(drupal_get_messages(None, False)) == 0): 2353 # This will fail in some cases, see page_get_cache() for the explanation. 2354 data = ob_get_contents(); 2355 if (not php.empty(data = ob_get_contents())): 2356 cache = True; 2357 if (variable_get('page_compression', True) and \ 2358 php.function_exists('gzencode')): 2359 # We do not store the data in case the zlib mode is deflate. 2360 # This should be rarely happening. 2361 if (zlib_get_coding_type() == 'deflate'): 2362 cache = False; 2363 elif (zlib_get_coding_type() == False): 2364 data = php.gzencode(data, 9, FORCE_GZIP); 2365 # The remaining case is 'gzip' which means the data is 2366 # already compressed and nothing left to do but to store it. 2367 ob_end_flush(); 2368 if (cache and data): 2369 cache_set(lib_appglobals.base_root + request_uri(), \ 2370 data, 'cache_page', \ 2371 CACHE_TEMPORARY, drupal_get_headers());
2372 2373 2374
2375 -def drupal_cron_run():
2376 """ 2377 Executes a cron run when called 2378 @return 2379 Returns True if ran successfully 2380 """ 2381 # Fetch the cron semaphore 2382 semaphore = variable_get('cron_semaphore', False); 2383 if (semaphore): 2384 if (drupy_time() - semaphore > 3600): 2385 # Either cron has been running for more than an hour or the semaphore 2386 # was not reset due to a database error. 2387 watchdog('cron', \ 2388 'Cron has been running for more than an hour ' + \ 2389 'and is most likely stuck.', {}, WATCHDOG_ERROR); 2390 # Release cron semaphore 2391 variable_del('cron_semaphore'); 2392 else: 2393 # Cron is still running normally. 2394 watchdog('cron', \ 2395 'Attempting to re-run cron while it is already running.', [], \ 2396 WATCHDOG_WARNING); 2397 else: 2398 # Register shutdown callback 2399 register_shutdown_function('drupal_cron_cleanup'); 2400 # Lock cron semaphore 2401 variable_set('cron_semaphore', drupy_time()); 2402 # Iterate through the plugins calling their cron handlers (if any): 2403 plugin_invoke_all('cron'); 2404 # Record cron time 2405 variable_set('cron_last', drupy_time()); 2406 watchdog('cron', 'Cron run completed.', [], WATCHDOG_NOTICE); 2407 # Release cron semaphore 2408 variable_del('cron_semaphore'); 2409 # Return True so other functions can check if it did run successfully 2410 return True;
2411 2412 2413
2414 -def drupal_cron_cleanup():
2415 """ 2416 Shutdown function for cron cleanup. 2417 """ 2418 # See if the semaphore is still locked. 2419 if (variable_get('cron_semaphore', False)): 2420 watchdog('cron', 'Cron run exceeded the time limit and was aborted.', \ 2421 [], WATCHDOG_WARNING); 2422 # Release cron semaphore 2423 variable_del('cron_semaphore');
2424 2425 2426
2427 -def drupal_system_listing(mask, directory, key = 'name', min_depth = 1):
2428 """ 2429 Return an array of system file objects. 2430 2431 Returns an array of file objects of the given type from the site-wide 2432 directory (i.e. plugins/), the all-sites directory (i.e. 2433 sites/all/plugins/), the profiles directory, and site-specific directory 2434 (i.e. sites/somesite/plugins/). The returned array will be keyed using the 2435 key specified (name, basename, filename). Using name or basename will cause 2436 site-specific files to be prioritized over similar files in the default 2437 directories. That is, if a file with the same name appears in both the 2438 site-wide directory and site-specific directory, only the site-specific 2439 version will be included. 2440 2441 @param mask 2442 The regular expression of the files to find. 2443 @param directory 2444 The subdirectory name in which the files are found. For example, 2445 'plugins' will search in both plugins/ and 2446 sites/somesite/plugins/. 2447 @param key 2448 The key to be passed to file_scan_directory(). 2449 @param min_depth 2450 Minimum depth of directories to return files from. 2451 2452 @return 2453 An array of file objects of the specified type. 2454 """ 2455 config = lib_bootstrap.conf_path(); 2456 # When this function is called during Drupal's initial 2457 # installation process, 2458 # the name of the profile that's about to be installed is stored 2459 # in the global 2460 # profile variable. At all other times, the standard Drupal systems variable 2461 # table contains the name of the current profile, and we can call 2462 # variable_get() 2463 # to determine what one is active. 2464 if (lib_appglobals.profile is None): 2465 lib_appglobals.profile = \ 2466 lib_bootstrap.variable_get('install_profile', 'default'); 2467 searchdir = [directory]; 2468 files = {}; 2469 # Always search sites/all/* as well as the global directories 2470 searchdir.append( 'sites/all/%s' % directory ); 2471 # The 'profiles' directory contains pristine collections of plugins and 2472 # themes as organized by a distribution. It is pristine in the same way 2473 # that /plugins is pristine for core; users should avoid changing anything 2474 # there in favor of sites/all or sites/<domain> directories. 2475 if (php.file_exists("profiles/%s/%s" % \ 2476 (lib_appglobals.profile, directory) )): 2477 searchdir.append( "profiles/%s/%s" % (lib_appglobals.profile, directory) ) 2478 if (php.file_exists("%s/%s" % (config, directory))): 2479 searchdir.append( "%s/%s" % (config, directory) ); 2480 # Get current list of items 2481 for dir_ in searchdir: 2482 files = php.array_merge(files, lib_file.scan_directory(dir_, mask, \ 2483 ('.', '..', 'CVS'), 0, True, key, min_depth)); 2484 return files;
2485 2486 2487
2488 -def drupal_alter(type_, data, *additional_args_):
2489 """ 2490 This dispatch function hands off structured Drupal arrays to type-specific 2491 *_alter implementations. It ensures a consistent interface for all altering 2492 operations. 2493 2494 @param type 2495 The data type of the structured array. 'form', 'links', 2496 'node_content', and so on are several examples. 2497 @param data 2498 The structured array to be altered. 2499 @param ... 2500 Any additional params will be passed on to the called 2501 hook_type_alter functions. 2502 """ 2503 php.Reference.check(data); 2504 # PHP's func_get_args() always returns copies of params, 2505 # not references, so 2506 # drupal_alter() can only manipulate data that comes in via the 2507 # required first 2508 # param. For the edge case functions that must pass in an arbitrary number of 2509 # alterable parameters (hook_form_alter() being the best example), 2510 # an array of 2511 # those params can be placed in the __drupal_alter_by_ref key of the data 2512 # array. This is somewhat ugly, but is an unavoidable consequence 2513 # of a flexible 2514 # drupal_alter() function, and the limitations of func_get_args(). 2515 # @todo: Remove this in Drupal 7. 2516 if (php.is_array(data) and php.isset(data['__drupal_alter_by_ref'])): 2517 by_ref_parameters = data['__drupal_alter_by_ref']; 2518 del(data['__drupal_alter_by_ref']); 2519 else: 2520 by_ref_parameters = None; 2521 # Hang onto a reference to the data array so that it isn't blown away later. 2522 # Also, merge in any parameters that need to be passed by reference. 2523 args = [data]; 2524 if (by_ref_parameters != None): 2525 args = php.array_merge(args, by_ref_parameters); 2526 # Now, use func_get_args() to pull in any additional parameters passed into 2527 # the drupal_alter() call. 2528 additional_args = additional_args_; 2529 php.array_shift(additional_args); 2530 php.array_shift(additional_args); 2531 args = tuple(php.array_merge(args, additional_args)); 2532 for plugin in plugin_implements(type_ + '_alter'): 2533 function = plugin + '_' + type_ + '_alter'; 2534 function( *args );
2535 2536 2537
2538 -def drupal_render(elements):
2539 """ 2540 Renders HTML given a structured array tree. 2541 2542 Recursively iterates over each of the array elements, generating HTML code. 2543 This function is usually called from within a another function, like 2544 drupal_get_form() or node_view(). 2545 2546 @param elements 2547 The structured array describing the data to be rendered. 2548 @return 2549 The rendered HTML. 2550 """ 2551 php.Reference.check(elements); 2552 if (php.empty(elements) or (php.isset(elements, '#access') and not \ 2553 elements['#access'])): 2554 return None; 2555 # If the default values for this element haven't been loaded yet, populate 2556 # them. 2557 if (not php.isset(elements, '#defaults_loaded') or not \ 2558 elements['#defaults_loaded']): 2559 info = _element_info(elements['#type']); 2560 if ((not php.empty(elements['#type'])) and info): 2561 for ik,iv in info.items(): 2562 elements[ik] = iv; 2563 # Make any final changes to the element before it is rendered. This means 2564 # that the element or the children can be altered or corrected before the 2565 # element is rendered into the final text. 2566 if (php.isset(elements, '#pre_render')): 2567 for function in elements['#pre_render']: 2568 if (drupal_function_exists(function)): 2569 for ek,ev in function(elements).items(): 2570 elements[ek] = ev 2571 content = ''; 2572 # Either the elements did not go through form_builder or one of the children 2573 # has a #weight. 2574 if (not php.isset(elements, '#sorted')): 2575 php.uasort(elements, element_sort); 2576 elements['#title'] = None 2577 elements['#description'] = None 2578 if (not php.isset(elements['#children'])): 2579 children = element_children(elements); 2580 # Render all the children that use a theme function */ 2581 if (php.isset(elements, '#theme') and \ 2582 php.empty(elements['#theme_used'])): 2583 elements['#theme_used'] = True; 2584 # If we rendered a single element, then we will skip the renderer. 2585 if (php.empty(children)): 2586 elements['#printed'] = True; 2587 else: 2588 elements['#markup'] = ''; 2589 content = theme(elements['#theme'], elements); 2590 # render each of the children using drupal_render and concatenate them */ 2591 if (php.empty(content)): 2592 for key in children: 2593 content += drupal_render(elements[key]); 2594 if (not php.empty(content)): 2595 elements['#children'] = content; 2596 # Until now, we rendered the children, here we render the element itself 2597 if (not php.isset(elements, '#printed')): 2598 content = theme((elements['#type'] if not \ 2599 php.empty(elements['#type']) else 'markup'), elements); 2600 elements['#printed'] = True; 2601 if (not php.empty(content)): 2602 # Filter the outputted content and make any last changes before the 2603 # content is sent to the browser. The changes are made on content 2604 # which allows the output'ed text to be filtered. 2605 if (php.isset(elements, '#post_render')): 2606 for function in elements['#post_render']: 2607 if (drupal_function_exists(function)): 2608 content = function(content, elements); 2609 prefix = (elements['#prefix'] if \ 2610 php.isset(elements, '#prefix') else ''); 2611 suffix = (elements['#suffix'] if \ 2612 php.isset(elements, '#suffix') else ''); 2613 return prefix + content + suffix;
2614 2615 2616
2617 -def element_sort(a, b):
2618 """ 2619 Function used by uasort to sort structured arrays by weight. 2620 """ 2621 a_weight = (a['#weight'] if \ 2622 (php.is_array(a) and php.isset(a['#weight'])) else 0); 2623 b_weight = (b['#weight'] if \ 2624 (php.is_array(b) and php.isset(b['#weight'])) else 0); 2625 if (a_weight == b_weight): 2626 return 0; 2627 return (-1 if (a_weight < b_weight) else 1);
2628 2629 2630
2631 -def element_property(key):
2632 """ 2633 Check if the key is a property. 2634 """ 2635 return (key[0] == '#');
2636 2637 2638
2639 -def element_properties(element):
2640 """ 2641 Get properties of a structured array element. Properties begin with '#'. 2642 """ 2643 return php.array_filter(php.array_keys(element), 'element_property');
2644 2645 2646
2647 -def element_child(key):
2648 """ 2649 Check if the key is a child. 2650 """ 2651 return (not php.isset(key, 0) or key[0] != '#');
2652 2653 2654
2655 -def element_children(element):
2656 """ 2657 Get keys of a structured array tree element 2658 that are not properties (i.e., do not begin with '#'). 2659 """ 2660 return php.array_filter(php.array_keys(element), 'element_child');
2661 2662 2663
2664 -def drupal_common_theme():
2665 """ 2666 Provide theme registration for themes across .inc files. 2667 """ 2668 return { 2669 # theme.inc 2670 'placeholder' : { 2671 'arguments' : {'text' : None} 2672 }, 2673 'page' : { 2674 'arguments' : {'content' : None, 'show_blocks' : True, \ 2675 'show_messages' : True}, 2676 'template' : 'page', 2677 }, 2678 'maintenance_page' : { 2679 'arguments' : {'content' : None, 'show_blocks' : True, \ 2680 'show_messages' : True}, 2681 'template' : 'maintenance-page', 2682 }, 2683 'update_page' : { 2684 'arguments' : {'content' : None, 'show_messages' : True} 2685 }, 2686 'install_page' : { 2687 'arguments' : {'content' : None} 2688 }, 2689 'task_list' : { 2690 'arguments' : {'items' : None, 'active' : None} 2691 }, 2692 'status_messages' : { 2693 'arguments' : {'display' : None} 2694 }, 2695 'links' : { 2696 'arguments' : {'links' : None, 'attributes' : {'class' : 'links'}} 2697 }, 2698 'image' : { 2699 'arguments' : {'path' : None, 'alt' : '', 'title' : '', \ 2700 'attributes' : None, 'getsize' : True} 2701 }, 2702 'breadcrumb' : { 2703 'arguments' : {'breadcrumb' : None} 2704 }, 2705 'help' : { 2706 'arguments' : {} 2707 }, 2708 'submenu' : { 2709 'arguments' : {'links' : None} 2710 }, 2711 'table' : { 2712 'arguments' : {'php.header' : None, 'rows' : None, \ 2713 'attributes' : {}, 'caption' : None} 2714 }, 2715 'table_select_header_cell' : { 2716 'arguments' : {} 2717 }, 2718 'tablesort_indicator' : { 2719 'arguments' : {'style' : None} 2720 }, 2721 'box' : { 2722 'arguments' : {'title' : None, 'content' : None, 'region' : 'main'}, 2723 'template' : 'box', 2724 }, 2725 'block' : { 2726 'arguments' : {'block' : None}, 2727 'template' : 'block', 2728 }, 2729 'mark' : { 2730 'arguments' : {'type' : MARK_NEW} 2731 }, 2732 'item_list' : { 2733 'arguments' : {'items' : {}, 'title' : None, \ 2734 'type' : 'ul', 'attributes' : None} 2735 }, 2736 'more_help_link' : { 2737 'arguments' : {'url' : None} 2738 }, 2739 'xml_icon' : { 2740 'arguments' : {'url' : None} 2741 }, 2742 'feed_icon' : { 2743 'arguments' : {'url' : None, 'title' : None} 2744 }, 2745 'more_link' : { 2746 'arguments' : {'url' : None, 'title' : None} 2747 }, 2748 'closure' : { 2749 'arguments' : {'main' : 0} 2750 }, 2751 'blocks' : { 2752 'arguments' : {'region' : None} 2753 }, 2754 'username' : { 2755 'arguments' : {'object' : None} 2756 }, 2757 'progress_bar' : { 2758 'arguments' : {'percent' : None, 'message' : None} 2759 }, 2760 'indentation' : { 2761 'arguments' : {'size' : 1} 2762 }, 2763 # from pager.inc 2764 'pager' : { 2765 'arguments' : {'tags' : {}, 'limit' : 10, 'element' : 0, \ 2766 'parameters' : {}} 2767 }, 2768 'pager_first' : { 2769 'arguments' : {'text' : None, 'limit' : None, 'element' : 0, \ 2770 'parameters' : {}} 2771 }, 2772 'pager_previous' : { 2773 'arguments' : {'text' : None, 'limit' : None, 'element' : 0, \ 2774 'interval' : 1, 'parameters' : {}} 2775 }, 2776 'pager_next' : { 2777 'arguments' : {'text' : None, 'limit' : None, 'element' : 0, \ 2778 'interval' : 1, 'parameters' : {}} 2779 }, 2780 'pager_last' : { 2781 'arguments' : {'text' : None, 'limit' : None, 'element' : 0, \ 2782 'parameters' : {}} 2783 }, 2784 'pager_link' : { 2785 'arguments' : {'text' : None, 'page_new' : None, 'element' : None, \ 2786 'parameters' : {}, 'attributes' : {}} 2787 }, 2788 # from locale.inc 2789 'locale_admin_manage_screen' : { 2790 'arguments' : {'form' : None} 2791 }, 2792 # from menu.inc 2793 'menu_item_link' : { 2794 'arguments' : {'item' : None} 2795 }, 2796 'menu_tree' : { 2797 'arguments' : {'tree' : None} 2798 }, 2799 'menu_item' : { 2800 'arguments' : {'link' : None, 'has_children' : None, 'menu' : ''} 2801 }, 2802 'menu_local_task' : { 2803 'arguments' : {'link' : None, 'active' : False} 2804 }, 2805 'menu_local_tasks' : { 2806 'arguments' : {} 2807 }, 2808 # from form.inc 2809 'select' : { 2810 'arguments' : {'element' : None} 2811 }, 2812 'fieldset' : { 2813 'arguments' : {'element' : None} 2814 }, 2815 'radio' : { 2816 'arguments' : {'element' : None} 2817 }, 2818 'radios' : { 2819 'arguments' : {'element' : None} 2820 }, 2821 'password_confirm' : { 2822 'arguments' : {'element' : None} 2823 }, 2824 'date' : { 2825 'arguments' : {'element' : None} 2826 }, 2827 'item' : { 2828 'arguments' : {'element' : None} 2829 }, 2830 'checkbox' : { 2831 'arguments' : {'element' : None} 2832 }, 2833 'checkboxes' : { 2834 'arguments' : {'element' : None} 2835 }, 2836 'submit' : { 2837 'arguments' : {'element' : None} 2838 }, 2839 'button' : { 2840 'arguments' : {'element' : None} 2841 }, 2842 'image_button' : { 2843 'arguments' : {'element' : None} 2844 }, 2845 'hidden' : { 2846 'arguments' : {'element' : None} 2847 }, 2848 'token' : { 2849 'arguments' : {'element' : None} 2850 }, 2851 'textfield' : { 2852 'arguments' : {'element' : None} 2853 }, 2854 'form' : { 2855 'arguments' : {'element' : None} 2856 }, 2857 'textarea' : { 2858 'arguments' : {'element' : None} 2859 }, 2860 'markup' : { 2861 'arguments' : {'element' : None} 2862 }, 2863 'password' : { 2864 'arguments' : {'element' : None} 2865 }, 2866 'file' : { 2867 'arguments' : {'element' : None} 2868 }, 2869 'form_element' : { 2870 'arguments' : {'element' : None, 'value' : None} 2871 }, 2872 };
2873 2874 2875 # 2876 # @ingroup schemaapi 2877 # @{ 2878 # 2879
2880 -def drupal_get_schema(table = None, rebuild = False):
2881 """ 2882 Get the schema definition of a table, or the whole database schema. 2883 2884 The returned schema will include any modifications made by any 2885 plugin that implements hook_schema_alter(). 2886 2887 @param table 2888 The name of the table. If not given, the schema of all tables is returned. 2889 @param rebuild 2890 If true, the schema will be rebuilt instead of retrieved from the cache. 2891 """ 2892 php.static(drupal_get_schema, 'schema', []) 2893 if (php.empty(drupal_get_schema.schema) or rebuild): 2894 # Try to load the schema from cache. 2895 cached = cache_get('schema') 2896 if (not rebuild and cached): 2897 drupal_get_schema.schema = cached.data; 2898 # Otherwise, rebuild the schema cache. 2899 else: 2900 drupal_get_schema.schema = []; 2901 # Load the .install files to get hook_schema. 2902 plugin_load_all_includes('install'); 2903 # Invoke hook_schema for all plugins. 2904 for plugin in plugin_implements('schema'): 2905 current = plugin_invoke(plugin, 'schema'); 2906 _drupal_initialize_schema(plugin, current); 2907 drupal_get_schema.schema = \ 2908 php.array_merge(drupal_get_schema.schema, current); 2909 drupal_alter('schema', drupal_get_schema.schema); 2910 cache_set('schema', drupal_get_schema.schema); 2911 if (table != None): 2912 return drupal_get_schema.schema; 2913 elif (php.isset(drupal_get_schema.schema, table)): 2914 return drupal_get_schema.schema[table]; 2915 else: 2916 return False;
2917 2918 2919
2920 -def drupal_install_schema(plugin):
2921 """ 2922 Create all tables that a plugin defines in its hook_schema(). 2923 2924 Note: This function does not pass the plugin's schema through 2925 hook_schema_alter(). The plugin's tables will be created exactly as the 2926 plugin defines them. 2927 2928 @param plugin 2929 The plugin for which the tables will be created. 2930 @return 2931 An array of arrays with the following key/value pairs: 2932 success: a boolean indicating whether the query succeeded 2933 query: the SQL query(s) executed, passed through check_plain() 2934 """ 2935 schema = drupal_get_schema_unprocessed(plugin); 2936 _drupal_initialize_schema(plugin, schema); 2937 ret = array(); 2938 for name,table in schema.items(): 2939 db_create_table(ret, name, table); 2940 return ret;
2941 2942 2943
2944 -def drupal_uninstall_schema(plugin):
2945 """ 2946 Remove all tables that a plugin defines in its hook_schema(). 2947 2948 Note: This function does not pass the plugin's schema through 2949 hook_schema_alter(). The plugin's tables will be created exactly as the 2950 plugin defines them. 2951 2952 @param plugin 2953 The plugin for which the tables will be removed. 2954 @return 2955 An array of arrays with the following key/value pairs: 2956 success: a boolean indicating whether the query succeeded 2957 query: the SQL query(s) executed, passed through check_plain() 2958 """ 2959 schema = drupal_get_schema_unprocessed(plugin); 2960 _drupal_initialize_schema(plugin, schema); 2961 ret = []; 2962 for table in schema: 2963 db_drop_table(ret, table['name']); 2964 return ret;
2965 2966 2967
2968 -def drupal_get_schema_unprocessed(plugin_, table = None):
2969 """ 2970 Returns the unprocessed and unaltered version of a plugin's schema. 2971 2972 Use this function only if you explicitly need the original 2973 specification of a schema, as it was defined in a plugin's 2974 hook_schema(). No additional default values will be set, 2975 hook_schema_alter() is not invoked and these unprocessed 2976 definitions won't be cached. 2977 2978 This function can be used to retrieve a schema specification in 2979 hook_schema(), so it allows you to derive your tables from existing 2980 specifications. 2981 2982 It is also used by drupal_install_schema() and 2983 drupal_uninstall_schema() to ensure that a plugin's tables are 2984 created exactly as specified without any changes introduced by a 2985 plugin that implements hook_schema_alter(). 2986 2987 @param plugin 2988 The plugin to which the table belongs. 2989 @param table 2990 The name of the table. If not given, the plugin's complete schema 2991 is returned. 2992 """ 2993 # Load the .install file to get hook_schema. 2994 plugin_load_install(plugin_); 2995 schema = plugin_invoke(plugin_, 'schema'); 2996 if (not php.is_null(table) and php.isset(schema, table)): 2997 return schema[table]; 2998 else: 2999 return schema;
3000 3001
3002 -def _drupal_initialize_schema(plugin, schema):
3003 """ 3004 Fill in required default values for 3005 table definitions returned by hook_schema(). 3006 3007 @param plugin 3008 The plugin for which hook_schema() was invoked. 3009 @param &schema 3010 The schema definition array as it was returned by the plugin's 3011 hook_schema(). 3012 3013 Drupy(BC): schema is a reference 3014 """ 3015 php.Reference.check(schema); 3016 # Set the name and plugin key for all tables. 3017 for name,table in schema.items(): 3018 if (php.empty(table['plugin'])): 3019 schema[name]['plugin'] = plugin; 3020 if (not php.isset(table, 'name')): 3021 schema[name]['name'] = name;
3022 3023 3024
3025 -def drupal_schema_fields_sql(table, prefix = None):
3026 """ 3027 Retrieve a list of fields from a table schema. 3028 The list is suitable for use in a SQL query. 3029 3030 @param table 3031 The name of the table from which to retrieve fields. 3032 @param 3033 An optional prefix to to all fields. 3034 3035 @return An array of fields. 3036 """ 3037 schema = drupal_get_schema(table); 3038 fields = php.array_keys(schema['fields']); 3039 if (prefix != None): 3040 columns = []; 3041 for field in fields: 3042 columns.append( "%(prefix)s.field" % {'prefix':prefix} ); 3043 return columns; 3044 else: 3045 return fields;
3046 3047 3048
3049 -def drupal_write_record(table, object_, update = []):
3050 """ 3051 Save a record to the database based upon the schema. 3052 3053 Default values are filled in for missing items, and 'serial' 3054 (auto increment) types are filled in with IDs. 3055 3056 @param table 3057 The name of the table; this must exist in schema API. 3058 @param &object 3059 The object to write. This is a reference, as defaults according to 3060 the schema may be filled in on the object, as well as ID on the serial 3061 type(s). Both array an object types may be passed. 3062 @param update 3063 If this is an update, specify the primary keys' field names. It is the 3064 caller's responsibility to know if a record for this object already 3065 exists in the database. If there is only 1 key, 3066 you may pass a simple string. 3067 @return 3068 Failure to write a record will return False. Otherwise SAVED_NEW or 3069 SAVED_UPDATED is returned depending on the operation performed. The 3070 object parameter contains values for any serial fields defined by 3071 the table. For example, object.nid will be populated after inserting 3072 a new node. 3073 """ 3074 php.Reference.check(object_); 3075 # Standardize update to an array. 3076 if (p.is_string(update)): 3077 update = [update]; 3078 schema = drupal_get_schema(table); 3079 if php.empty(schema): 3080 return False 3081 # Convert to an object if needed. 3082 if (php.is_array(object_)): 3083 tmp = php.object_(object_); 3084 array_ = True; 3085 else: 3086 tmp = php.clone(object_) 3087 array_ = False; 3088 fields = defs = values = serials = placeholders = []; 3089 # Go through our schema, build SQL, and when inserting, fill in defaults for 3090 # fields that are not set. 3091 for field,info in schema['fields'].items(): 3092 # Special case -- skip serial types if we are updating. 3093 if (info['type'] == 'serial' and php.count(update)): 3094 continue; 3095 # For inserts, populate defaults from Schema if not already provided 3096 if (not php.isset(tmp, field) and not \ 3097 php.count(update) and php.isset(info, 'default')): 3098 setattr(tmp, field, info['default']); 3099 # Track serial fields so we can helpfully populate them after the query. 3100 if (info['type'] == 'serial'): 3101 serials.append( field ); 3102 # Ignore values for serials when inserting data. Unsupported. 3103 delattr(tmp, field); 3104 # Build arrays for the fields, placeholders, and values in our query. 3105 if (php.isset(tmp, field)): 3106 fields.append( field ); 3107 placeholders.append( db_type_placeholder(info['type']) ); 3108 if (php.empty(info['serialize'])): 3109 values.append( getattr( tmp, field ) ); 3110 elif (not php.empty(getattr( tmp, field ))): 3111 values.append( php.serialize( getattr(tmp, field) ) ); 3112 else: 3113 values.append( '' ); 3114 if (php.empty(fields)): 3115 # No changes requested. 3116 # If we began with an array, convert back so we don't surprise the caller. 3117 if (array_): 3118 php.array_merge(object_, php.array_(tmp), True) 3119 else: 3120 php.object_merge(object_, tmp, True) 3121 return; 3122 # Build the SQL. 3123 query = ''; 3124 if (php.count(update) == 0): 3125 query = "INSERT INTO {" + table + "} (" + \ 3126 php.implode(', ', fields) + ') VALUES (' + \ 3127 php.implode(', ', placeholders) + ')'; 3128 return_ = SAVED_NEW; 3129 else: 3130 query = ''; 3131 for id,field in fields.items(): 3132 if (not php.empty(query)): 3133 query += ', '; 3134 query += field + ' = ' + placeholders[id]; 3135 for key in update: 3136 conditions.append( key + " = " + \ 3137 db_type_placeholder(schema['fields'][key]['type']) ); 3138 values.append( tmp.key ); 3139 query = "UPDATE {" + table + "} SET query WHERE " + \ 3140 php.implode(' AND ', conditions); 3141 return_ = SAVED_UPDATED; 3142 # Execute the SQL. 3143 if (db_query(query, values)): 3144 if (not php.empty(serials)): 3145 # Get last insert ids and fill them in. 3146 for field in serials: 3147 setattr( tmp, field, db_last_insert_id(table, field) ); 3148 else: 3149 return_ = False 3150 # If we began with an array, convert back so we don't surprise the caller. 3151 if (array_): 3152 php.array_merge(object_, php.array_(tmp), True) 3153 else: 3154 php.object_merge(object_, tmp, True) 3155 return return_;
3156 3157 3158
3159 -def drupal_parse_info_file(filename):
3160 """ 3161 Parse Drupal info file format. 3162 3163 Files should use an ini-like format to specify values. 3164 White-space generally doesn't matter, except inside values. 3165 e.g. 3166 3167 @verbatim 3168 key = value 3169 key = "value" 3170 key = 'value' 3171 key = "multi-line 3172 3173 value" 3174 key = 'multi-line 3175 3176 value' 3177 key 3178 = 3179 'value' 3180 @endverbatim 3181 3182 Arrays are created using a php.GET-like syntax: 3183 3184 @verbatim 3185 key[] = "numeric array" 3186 key[index] = "associative array" 3187 key[index][] = "nested numeric array" 3188 key[index][index] = "nested associative array" 3189 @endverbatim 3190 3191 PHP constants are substituted in, but only when used as the entire value: 3192 3193 Comments should start with a semi-colon at the beginning of a line. 3194 3195 This function is NOT for placing arbitrary plugin-specific settings. Use 3196 variable_get() and variable_set() for that. 3197 3198 Information stored in the plugin.info file: 3199 - name: The real name of the plugin for display purposes. 3200 - description: A brief description of the plugin. 3201 - dependencies: An array of shortnames of other 3202 plugins this plugin depends on. 3203 - package: The name of the package of plugins this plugin belongs to. 3204 3205 Example of .info file: 3206 @verbatim 3207 name = Forum 3208 description = Enables threaded discussions about general topics. 3209 dependencies[] = taxonomy 3210 dependencies[] = comment 3211 package = Core - optional 3212 version = VERSION 3213 @endverbatim 3214 3215 @param filename 3216 The file we are parsing. Accepts file with relative or absolute path. 3217 @return 3218 The info array. 3219 """ 3220 return DrupyHelper.get_import(filename).__all__; 3221 3222 3223 """ 3224 Severity levels, as defined in RFC 3164: 3225 http://www.ietf.org/rfc/rfc3164.txt. 3226 3227 @return 3228 Array of the possible severity levels for log messages. 3229 3230 @see watchdog() 3231 """
3232 -def watchdog_severity_levels():
3233 """ 3234 Parse Drupal info file format. 3235 3236 Files should use an ini-like format to specify values. 3237 White-space generally doesn't matter, except inside values. 3238 e.g. 3239 3240 @verbatim 3241 key = value 3242 key = "value" 3243 key = 'value' 3244 key = "multi-line 3245 3246 value" 3247 key = 'multi-line 3248 3249 value' 3250 key 3251 = 3252 'value' 3253 @endverbatim 3254 3255 Arrays are created using a php.GET-like syntax: 3256 3257 @verbatim 3258 key[] = "numeric array" 3259 key[index] = "associative array" 3260 key[index][] = "nested numeric array" 3261 key[index][index] = "nested associative array" 3262 @endverbatim 3263 3264 PHP constants are substituted in, but only when used as the entire value: 3265 3266 Comments should start with a semi-colon at the beginning of a line. 3267 3268 This function is NOT for placing arbitrary plugin-specific settings. Use 3269 variable_get() and variable_set() for that. 3270 3271 Information stored in the plugin.info file: 3272 - name: The real name of the plugin for display purposes. 3273 - description: A brief description of the plugin. 3274 - dependencies: An array of shortnames of 3275 other plugins this plugin depends on. 3276 - package: The name of the package of plugins this plugin belongs to. 3277 3278 Example of .info file: 3279 @verbatim 3280 name = Forum 3281 description = Enables threaded discussions about general topics. 3282 dependencies[] = taxonomy 3283 dependencies[] = comment 3284 package = Core - optional 3285 version = VERSION 3286 @endverbatim 3287 3288 @param filename 3289 The file we are parsing. Accepts file with relative or absolute path. 3290 @return 3291 The info array. 3292 """ 3293 return { 3294 WATCHDOG_EMERG : t('emergency'), 3295 WATCHDOG_ALERT : t('alert'), 3296 WATCHDOG_CRITICAL : t('critical'), 3297 WATCHDOG_ERROR : t('error'), 3298 WATCHDOG_WARNING : t('warning'), 3299 WATCHDOG_NOTICE : t('notice'), 3300 WATCHDOG_INFO : t('info'), 3301 WATCHDOG_DEBUG : t('debug') 3302 };
3303 3304 3305
3306 -def drupal_explode_tags(tags):
3307 """ 3308 Explode a string of given tags into an array. 3309 """ 3310 # This regexp allows the following types of user input: 3311 # this, "somecompany, llc", "and ""this"" w,o.rks", foo bar 3312 regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; 3313 matches = [] 3314 php.preg_match_all(regexp, tags, matches); 3315 typed_tags = array_unique(matches[1]); 3316 tags = []; 3317 for tag in typed_tags: 3318 # If a user has escaped a term (to demonstrate that it is a group, 3319 # or includes a comma or quote character), we remove the escape 3320 # formatting so to save the term into the database as the user intends. 3321 tag = php.trim(php.str_replace('""', '"', \ 3322 php.preg_replace('/^"(.*)"$/', '\1', tag))); 3323 if (tag != ""): 3324 tags.append( tag ); 3325 return tags;
3326 3327 3328
3329 -def drupal_implode_tags(tags):
3330 """ 3331 Implode an array of tags into a string. 3332 """ 3333 encoded_tags = []; 3334 for tag in tags: 3335 # Commas and quotes in tag names are special cases, so encode them. 3336 if (php.strpos(tag, ',') != False or php.strpos(tag, '"') != False): 3337 tag = '"' + php.str_replace('"', '""', tag) + '"'; 3338 encoded_tags.append( tag ); 3339 return php.implode(', ', encoded_tags);
3340 3341 3342
3343 -def drupal_flush_all_caches():
3344 """ 3345 Flush all cached data on the site. 3346 3347 Empties cache tables, rebuilds the menu cache and theme registries, and 3348 exposes a hook for other plugins to clear their own cache data as well. 3349 """ 3350 # Change query-strings on css/js files to enforce reload for all users. 3351 _drupal_flush_css_js(); 3352 registry_rebuild(); 3353 drupal_clear_css_cache(); 3354 drupal_clear_js_cache(); 3355 system_theme_data(); 3356 drupal_theme_rebuild(); 3357 menu_rebuild(); 3358 node_types_rebuild(); 3359 # Don't clear cache_form - in-progress form submissions may break. 3360 # Ordered so clearing the page cache will always be the last action. 3361 core = ['cache', 'cache_block', 'cache_filter', \ 3362 'cache_registry', 'cache_page']; 3363 cache_tables = php.array_merge(plugin_invoke_all('php.flush_caches'), core); 3364 for table in cache_tables: 3365 cache_clear_all('*', table, True);
3366 3367 3368
3369 -def _drupal_flush_css_js():
3370 """ 3371 Helper function to change query-strings on css/js files. 3372 3373 Changes the character added to all css/js files as dummy query-string, 3374 so that all browsers are forced to reload fresh files. We keep 3375 20 characters history (FIFO) to avoid repeats, but only the first 3376 (newest) character is actually used on urls, to keep them short. 3377 This is also called from update.php. 3378 """ 3379 string_history = variable_get('css_js_query_string', '00000000000000000000'); 3380 new_character = string_history[0]; 3381 characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 3382 while (php.strpos(string_history, new_character) != False): 3383 new_character = characters[php.mt_rand(0, php.strlen(characters) - 1)]; 3384 variable_set('css_js_query_string', new_character + \ 3385 php.substr(string_history, 0, 19));
3386