Package base :: Package plugins :: Package node
[hide private]

Source Code for Package base.plugins.node

   1  #!/usr/bin/env python 
   2  # $Id: node.module,v 1.971 2008/08/03 19:02:06 dries Exp $ 
   3   
   4  """ 
   5    The core that allows content to be submitted to the site. Modules and 
   6    scripts may programmatically submit nodes using the usual form API pattern. 
   7   
   8    @package user 
   9    @see <a href='http://drupy.net'>Drupy Homepage</a> 
  10    @see <a href='http://drupal.org'>Drupal Homepage</a> 
  11    @note Drupy is a port of the Drupal project. 
  12    @note This file was ported from Drupal's modules/node/node.module 
  13    @author Brendon Crawford 
  14    @copyright 2008 Brendon Crawford 
  15    @contact message144 at users dot sourceforge dot net 
  16    @created 2008-01-10 
  17    @version 0.1 
  18    @note License: 
  19   
  20      This program is free software; you can redistribute it and/or 
  21      modify it under the terms of the GNU General Public License 
  22      as published by the Free Software Foundation; either version 2 
  23      of the License, or (at your option) any later version. 
  24   
  25      This program is distributed in the hope that it will be useful, 
  26      but WITHOUT ANY WARRANTY; without even the implied warranty of 
  27      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  28      GNU General Public License for more details. 
  29   
  30      You should have received a copy of the GNU General Public License 
  31      along with this program; if not, write to: 
  32       
  33      The Free Software Foundation, Inc., 
  34      51 Franklin Street, Fifth Floor, 
  35      Boston, MA  02110-1301, 
  36      USA 
  37  """ 
  38   
  39  __version__ = "$Revision: 1 $" 
  40   
  41  from lib.drupy import DrupyPHP as php 
  42  from lib.drupy import DrupyImport 
  43  from includes import common as lib_common 
  44  from includes import path as lib_path 
  45  from includes import database as lib_database 
  46  from includes import bootstrap as lib_bootstrap 
  47  from includes import plugin as lib_plugin 
  48   
  49   
  50   
  51  # 
  52  # Nodes changed before this time are always marked as read. 
  53  # 
  54  # Nodes changed after this time may be marked new, updated, or read, depending 
  55  # on their state for the current user. Defaults to 30 days ago. 
  56  # 
  57  NODE_NEW_LIMIT = php.time_() - 30 * 24 * 60 * 60 
  58   
  59  # 
  60  # Node is being built before being viewed normally. 
  61  # 
  62  NODE_BUILD_NORMAL = 0 
  63   
  64  # 
  65  # Node is being built before being previewed. 
  66  # 
  67  NODE_BUILD_PREVIEW = 1 
  68   
  69  # 
  70  # Node is being built before being indexed by search module. 
  71  # 
  72  NODE_BUILD_SEARCH_INDEX = 2 
  73   
  74  # 
  75  # Node is being built before being displayed as a search result. 
  76  # 
  77  NODE_BUILD_SEARCH_RESULT = 3 
  78   
  79  # 
  80  # Node is being built before being displayed as part of an RSS feed. 
  81  # 
  82  NODE_BUILD_RSS = 4 
  83   
  84  # 
  85  # Node is being built before being printed. 
  86  # 
  87  NODE_BUILD_PRINT = 5 
  88   
89 -def hook_help(path, arg):
90 """ 91 Implementation of hook_help(). 92 """ 93 # Remind site administrators about the {node_access} table being flagged 94 # for rebuild. We don't need to issue the message on the confirm form, or 95 # while the rebuild is being processed. 96 if (path != 'admin/content/node-settings/rebuild' and \ 97 path != 'batch' and php.strpos(path, '#') == False 98 and lib_plugin.plugins['user'].access(\ 99 'access administration pages') and access_needs_rebuild()): 100 if (path == 'admin/content/node-settings'): 101 message = lib_common.t(\ 102 'The content access permissions need to be rebuilt.') 103 else: 104 message = lib_common.t('The content access permissions need to ' + \ 105 'be rebuilt. Please visit ' + \ 106 '<a href="@node_access_rebuild">this page</a>.', \ 107 {'@node_access_rebuild' : \ 108 lib_common.url('admin/content/node-settings/rebuild')}) 109 lib_bootstrap.drupal_set_message(message, 'error') 110 if path == 'admin/help#node': 111 output = '<p>' + t('The node module manages content on your ' + \ 112 'site, and stores all posts (regardless of type) as a ' + \ 113 '"node". In addition to basic publishing settings, including ' + \ 114 'whether the post has been published, promoted to the site ' + \ 115 'front page, or should remain present (or sticky) at the top ' + \ 116 'of lists, the node module also records basic information about ' + \ 117 'the author of a post. Optional revision control over edits is ' + \ 118 'available. For additional functionality, the node module is ' + \ 119 'often extended by other modules.') + '</p>' 120 output += '<p>' + t('Though each post on your site is a node, each ' + \ 121 'post is also of a particular ' + \ 122 '<a href="@content-type">content type</a>. ' + \ 123 '<a href="@content-type">Content types</a> are used to define the ' + \ 124 'characteristics of a post, including the title and description ' + \ 125 'of the fields displayed on its add and edit pages. Each ' + \ 126 'content type may have different default settings for ' + \ 127 '<em>Publishing options</em> and other workflow controls. ' + \ 128 'By default, the two content types in a standard Drupal ' + \ 129 'installation are <em>Page</em> and <em>Story</em>. Use the ' + \ 130 '<a href="@content-type">content types page</a> to add new or ' + \ 131 'edit existing content types. Additional content types also ' + \ 132 'become available as you enable additional core, contributed and ' + \ 133 'custom modules.', {'@content-type' : \ 134 lib_common.url('admin/build/types')}) + '</p>' 135 output += '<p>' + lib_common.t('The administrative ' + \ 136 '<a href="@content">content page</a> allows you to review and ' + \ 137 'manage your site content. The ' + \ 138 '<a href="@post-settings">post settings page</a> sets certain ' + \ 139 'options for the display of posts. The node module makes a ' + \ 140 'number of permissions available for each content type, which ' + \ 141 'may be set by role on the ' + \ 142 '<a href="@permissions">permissions page</a>.', \ 143 {'@content' : lib_common.url('admin/content/node'), \ 144 '@post-settings' : lib_common.url('admin/content/node-settings'), \ 145 '@permissions' : lib_common.url('admin/user/permissions')}) + '</p>' 146 output += '<p>' + t('For more information, see the online ' + \ 147 'handbook entry for <a href="@node">Node module</a>.', \ 148 {'@node' : 'http://drupal.org/handbook/modules/node/'}) + '</p>' 149 return output 150 elif path == 'admin/content/node': 151 # Return a non-None value so that the 'more help' link is shown. 152 return ' ' 153 elif path == 'admin/build/types': 154 return '<p>' + t('Below is a list of all the content types on your ' + \ 155 'site. All posts that exist on your site are instances of one ' + \ 156 'of these content types.') + '</p>' 157 elif path == 'admin/build/types/add': 158 return '<p>' + t('To create a new content type, enter the ' + \ 159 'human-readable name, the machine-readable name, and all other ' + \ 160 'relevant fields that are on this page. Once created, users ' + \ 161 'of your site will be able to create posts that are ' + \ 162 'instances of this content type.') + '</p>' 163 elif path == 'node/%/revisions': 164 return '<p>' + lib_common.t('The revisions let you track differences ' + \ 165 'between multiple versions of a post.') + '</p>' 166 elif path == 'node/%/edit': 167 node = load(arg[1]) 168 type_ = get_types('type', node.type_) 169 return (('<p>' + filter_xss_admin(type.help) + '</p>') if \ 170 (not php.empty(type_.help)) else '') 171 if (arg[0] == 'node' and arg[1] == 'add' and arg[2]): 172 type_ = get_types('type', php.str_replace('-', '_', arg[2])) 173 return (('<p>' + filter_xss_admin(type_.help) + '</p>') if \ 174 (not empty(type_.help)) else '')
175 176 177 178
179 -def hook_theme():
180 """ 181 Implementation of hook_theme(). 182 """ 183 return { 184 'node' : { 185 'arguments' : {'node' : None, 'teaser' : False, 'page' : False}, 186 'template' : 'node' 187 }, 188 'node_list' : { 189 'arguments' : {'items' : None, 'title' : None} 190 }, 191 'node_search_admin' : { 192 'arguments' : {'form' : None} 193 }, 194 'node_filter_form' : { 195 'arguments' : {'form' : None}, 196 'file' : 'node.admin.inc' 197 }, 198 'node_filters' : { 199 'arguments' : {'form' : None}, 200 'file' : 'node.admin.inc' 201 }, 202 'node_admin_nodes' : { 203 'arguments' : {'form' : None}, 204 'file' : 'node.admin.inc' 205 }, 206 'node_add_list' : { 207 'arguments' : {'content' : None}, 208 'file' : 'node.pages.inc' 209 }, 210 'node_form' : { 211 'arguments' : {'form' : None}, 212 'file' : 'node.pages.inc' 213 }, 214 'node_preview' : { 215 'arguments' : {'node' : None}, 216 'file' : 'node.pages.inc' 217 }, 218 'node_log_message' : { 219 'arguments' : {'log' : None} 220 }, 221 'node_submitted' : { 222 'arguments' : {'node' : None} 223 }, 224 }
225 226 227 228
229 -def hook_cron():
230 """ 231 Implementation of hook_cron(). 232 """ 233 lib_database.query('DELETE FROM {history} WHERE timestamp < %d', \ 234 NODE_NEW_LIMIT)
235 236
237 -def title_list(result, title = None):
238 """ 239 Gather a listing of links to nodes. 240 241 @param result 242 A DB result object from a query to fetch node objects. If your query 243 joins the <code>node_comment_statistics</code> table so that the 244 <code>comment_count</code> field is available, a title attribute will 245 be added to show the number of comments. 246 @param title 247 A heading for the resulting list. 248 249 @return 250 An HTML list suitable as content for a block, or False if no result can 251 fetch from DB result object. 252 """ 253 items = [] 254 num_rows = False 255 while True: 256 node = db_fetch_object(result) 257 if not node: 258 break 259 items.append(l((({'attributes' : {'title' : format_plural(\ 260 node.comment_count, '1 comment', '@count comments')}}) if \ 261 (node.title, 'node/' + node.nid, \ 262 not php.empty(node.comment_count)) else {}))) 263 num_rows = True 264 return (lib_theme.theme('node_list', items, title) if num_rows else False)
265 266 267
268 -def theme_list(items, title = None):
269 """ 270 Format a listing of links to nodes. 271 272 @ingroup themeable 273 """ 274 return lib_theme.theme('item_list', items, title)
275 276
277 -def tag_new(nid):
278 """ 279 Update the 'last viewed' timestamp of the specified node for current user. 280 """ 281 if (lib_appglobals.user.uid): 282 if (last_viewed(nid)): 283 lib_database.query('UPDATE {history} SET timestamp = %d ' + \ 284 'WHERE uid = %d AND nid = %d', php.time_(), \ 285 lib_appglobals.user.uid, nid) 286 else: 287 lib_database.query('INSERT INTO {history} (uid, nid, timestamp) ' + \ 288 'VALUES (%d, %d, %d)', lib_appglobals.user.uid, nid, php.time_())
289 290 291
292 -def last_viewed(nid):
293 """ 294 Retrieves the timestamp at which the current user last viewed the 295 specified node. 296 """ 297 php.static(last_view, 'history', {}) 298 if (not php.isset(last_view.history, 'nid')): 299 last_view.history[nid] = lib_database.fetch_object(lib_database.query(\ 300 "SELECT timestamp FROM {history} " + \ 301 "WHERE uid = %d AND nid = %d", lib_appglobals.user.uid, nid)) 302 return (last_view.history[nid].timestamp if \ 303 php.isset(last_view.history[nid], 'timestamp') else 0)
304 305 306
307 -def mark(nid, timestamp):
308 """ 309 Decide on the type of marker to be displayed for a given node. 310 311 @param nid 312 Node ID whose history supplies the "last viewed" timestamp. 313 @param timestamp 314 Time which is compared against node's "last viewed" timestamp. 315 @return 316 One of the MARK constants. 317 """ 318 php.static(mark, 'cache', {}) 319 if (not lib_appglobals.user.uid): 320 return MARK_READ 321 if (not php.isset(mark.cache, 'nid')): 322 mark.cache[nid] = last_viewed(nid) 323 if (mark.cache[nid] == 0 and timestamp > NODE_NEW_LIMIT): 324 return MARK_NEW 325 elif (timestamp > mark.cache[nid] and timestamp > NODE_NEW_LIMIT): 326 return MARK_UPDATED 327 return MARK_READ
328 329
330 -def teaser_js(form, form_state):
331 """ 332 See if the user used JS to submit a teaser. 333 """ 334 php.Reference.check(form) 335 php.Reference.check(form_state) 336 if (php.isset(form['#post'], 'teaser_js')): 337 # Glue the teaser to the body. 338 if (php.trim(form_state['values']['teaser_js'])): 339 # Space the teaser from the body 340 body = php.trim(form_state['values']['teaser_js']) + \ 341 "\r\n<not --break-.\r\n" + php.trim(form_state['values']['body']) 342 else: 343 # Empty teaser, no spaces. 344 body = '<not --break-.' + form_state['values']['body'] 345 # Pass updated body value on to preview/submit form processing. 346 form_set_value(form['body'], body, form_state) 347 # Pass updated body value back onto form for those cases 348 # in which the form is redisplayed. 349 form['body']['#value'] = body 350 return form 351 352 353
354 -def teaser_include_verify(form, form_state):
355 """ 356 Ensure value of "teaser_include" checkbox is consistent with other form data. 357 358 This handles two situations in which an unchecked checkbox is rejected: 359 360 1. The user defines a teaser (summary) but it is empty 361 2. The user does not define a teaser (summary) (in this case an 362 unchecked checkbox would cause the body to be empty, or missing 363 the auto-generated teaser). 364 365 If JavaScript is active then it is used to force the checkbox to be 366 checked when hidden, and so the second case will not arise. 367 368 In either case a warning message is output. 369 """ 370 php.Reference.check(form) 371 php.Reference.check(form_state) 372 message = '' 373 # form['#post'] is set only when the form is built for preview/submit. 374 if (php.isset(form['#post']['body']) and \ 375 php.isset(form_state['values'], 'teaser_include') and \ 376 not form_state['values']['teaser_include']): 377 # "teaser_include" checkbox is present and unchecked. 378 if (php.strpos(form_state['values']['body'], '<!--break-.') == 0): 379 # Teaser is empty string. 380 message = lib_common.t(\ 381 'You specified that the summary should not be shown when this ' + \ 382 'post is displayed in full view. This setting is ignored ' + \ 383 'when the summary is empty.') 384 elif (php.strpos(form_state['values']['body'], '<!--break-.') == False): 385 # Teaser delimiter is not present in the body. 386 message = lib_common.t('You specified that the summary should ' + \ 387 'not be shown when this post is displayed in full view. ' + \ 388 'This setting has been ignored since you have not defined a ' + \ 389 'summary for the post. (To define a summary, insert the ' + \ 390 'delimiter "&lt;not --break--&gt;" (without the quotes) in ' + \ 391 'the Body of the post to indicate the end of the summary and ' + \ 392 'the start of the main content.)') 393 if (not php.empty(message)): 394 drupal_set_message(message, 'warning') 395 # Pass new checkbox value on to preview/submit form processing. 396 form_set_value(form['teaser_include'], 1, form_state) 397 # Pass new checkbox value back onto form for those cases 398 # in which form is redisplayed. 399 form['teaser_include']['#value'] = 1 400 return form
401 402 403
404 -def teaser(body, format = None, size = None):
405 """ 406 Generate a teaser for a node body. 407 408 If the end of the teaser is not indicated using the <!--break-. delimiter 409 then we generate the teaser automatically, trying to end it at a sensible 410 place such as the end of a paragraph, a line break, or the end of a 411 sentence (in that order of preference). 412 413 @param body 414 The content for which a teaser will be generated. 415 @param format 416 The format of the content. If the content contains PHP code, we do not 417 split it up to prevent parse errors. If the line break filter is present 418 then we treat newlines embedded in body as line breaks. 419 @param size 420 The desired character length of the teaser. If omitted, the default 421 value will be used. Ignored if the special delimiter is present 422 in body. 423 @return 424 The generated teaser. 425 """ 426 if (size is None): 427 size = lib_bootstrap.variable_get('teaser_length', 600) 428 # Find where the delimiter is in the body 429 delimiter = php.strpos(body, '<!--break-.') 430 # If the size is zero, and there is no delimiter, 431 # the entire body is the teaser. 432 if (size == 0 and delimiter == False): 433 return body 434 # If a valid delimiter has been specified, use it to chop off the teaser. 435 if (delimiter is not False): 436 return php.substr(body, 0, delimiter) 437 # We check for the presence of the PHP evaluator filter in the current 438 # format. If the body contains PHP code, we do not split it up to prevent 439 # parse errors. 440 if (format is not None): 441 filters = filter_list_format(format) 442 if (php.isset(filters['php/0']) and php.strpos(body, '<?') is not False): 443 return body 444 # If we have a short body, the entire body is the teaser. 445 if (drupal_strlen(body) <= size): 446 return body 447 # If the delimiter has not been specified, try to split at paragraph or 448 # sentence boundaries. 449 # The teaser may not be longer than maximum length specified. Initial slice. 450 teaser = truncate_utf8(body, size) 451 # Store the actual length of the UTF8 string -- which might not be the same 452 # as size. 453 max_rpos = php.strlen(teaser) 454 # How much to cut off the end of the teaser so that it doesn't end in the 455 # middle of a paragraph, sentence, or word. 456 # Initialize it to maximum in order to find the minimum. 457 min_rpos = max_rpos 458 # Store the reverse of the teaser. We use strpos on the reversed needle and 459 # haystack for speed and convenience. 460 reversed = php.strrev(teaser) 461 # Build an array of arrays of break points grouped by preference. 462 break_points = array() 463 # A paragraph near the end of sliced teaser is most preferable. 464 break_points.append( {'</p>' : 0} ) 465 # If no complete paragraph then treat line breaks as paragraphs. 466 line_breaks = {'<br />' : 6, '<br>' : 4} 467 # Newline only indicates a line break if line break converter 468 # filter is present. 469 if (php.isset(filters['filter/1'])): 470 line_breaks["\n"] = 1 471 break_points.append( line_breaks ) 472 # If the first paragraph is too long, split at the end of a sentence. 473 # @TODO DRUPY: Fix this somehow 474 # There are additional characters at the end of this line 475 break_points.append( {'. ' : 1, '! ' : 1, '? ' : 1} ) 476 # Iterate over the groups of break points until a break point is found. 477 for points in break_points: 478 # Look for each break point, starting at the end of the teaser. 479 for point,offset in points.items(): 480 # The teaser is already reversed, but the break point isn't. 481 rpos = php.strpos(reversed, php.strrev(point)) 482 if (rpos is not False): 483 min_rpos = php.min(rpos + offset, min_rpos) 484 # If a break point was found in this group, slice and return the teaser. 485 if (min_rpos != max_rpos): 486 # Don't slice with length 0. Length must be <0 to slice from RHS. 487 return (teaser if (min_rpos == 0) else \ 488 php.substr(teaser, 0, 0 - min_rpos)) 489 # If a break point was not found, still return a teaser. 490 return teaser
491 492 493 494
495 -def get_types(op = 'types', node = None, reset = False):
496 """ 497 Builds a list of available node types, and returns all of part of this list 498 in the specified format. 499 500 @param op 501 The format in which to return the list. When this is set to 'type', 502 'module', or 'name', only the specified node type is 503 returned. When set to 504 'types' or 'names', all node types are returned. 505 @param node 506 A node object, array, or string that indicates the node type to return. 507 Leave at default value (None) to return a list of all node types. 508 @param reset 509 Whether or not to reset this function's internal cache (defaults to 510 False). 511 512 @return 513 Either an array of all available node types, or a single node type, in a 514 variable format. Returns False if the node type is not found. 515 """ 516 php.static(get_types, '_node_types', {}) 517 php.static(get_types, '_get_types', {}) 518 if (reset or php.empty(get_types._node_types)): 519 get_types._node_types, get_types._node_names = _types_build() 520 if (node): 521 if (php.is_array(node)): 522 type_ = node['type'] 523 elif (php.is_object(node)): 524 type_ = node.type_ 525 elif (php.is_string(node)): 526 type_ = node 527 if (not php.isset(get_types._node_types, 'type_')): 528 return False 529 if op == 'types': 530 return _node_types 531 elif op =='type': 532 return (_node_types[type] if \ 533 php.isset(get_types._node_types, type_) else False) 534 elif op == 'module': 535 return (get_types._node_types[type_].plugin if \ 536 php.isset(get_types._node_types[type_], 'plugin') else False) 537 elif op == 'names': 538 return get_types._node_names 539 elif op =='name': 540 return (get_types._node_names[type_] if \ 541 php.isset(_node_names, type_) else False)
542 543 544
545 -def types_rebuild():
546 """ 547 Resets the database cache of node types, and saves all new or non-modified 548 module-defined node types to the database. 549 """ 550 _types_build() 551 node_types = get_types('types', None, True) 552 for type_,info in node_types.items(): 553 if (not empty(info.is_new)): 554 type_save(info) 555 if (not empty(info.disabled)): 556 type_delete(info.type_) 557 _types_build()
558 559 560
561 -def type_save(info):
562 """ 563 Saves a node type to the database. 564 565 @param info 566 The node type to save, as an object. 567 568 @return 569 Status flag indicating outcome of the operation. 570 """ 571 is_existing = False 572 existing_type = (info.old_type if \ 573 not php.empty(info.old_type) else info.type) 574 is_existing = lib_database.result(lib_database.query(\ 575 "SELECT COUNT(*) FROM {node_type} WHERE type = '%s'", existing_type)) 576 if (not php.isset(info, 'help')): 577 info.help = '' 578 if (not isset(info, 'min_word_count')): 579 info.min_word_count = 0 580 if (not isset(info, 'body_label')): 581 info.body_label = '' 582 if (is_existing): 583 lib_database.query(\ 584 "UPDATE {node_type} SET type = '%s', name = '%s', " + \ 585 "module = '%s', has_title = %d, title_label = '%s', " + \ 586 "has_body = %d, body_label = '%s', description = '%s', " + \ 587 "help = '%s', min_word_count = %d, custom = %d, modified = %d, " + \ 588 "locked = %d WHERE type = '%s'", \ 589 info.type, info.name, info.module, info.has_title, \ 590 info.title_label, info.has_body, info.body_label, \ 591 info.description, info.help, info.min_word_count, \ 592 info.custom, info.modified, info.locked, existing_type) 593 lib_plugin.invoke_all('node_type', 'update', info) 594 return SAVED_UPDATED 595 else: 596 lib_database.query("INSERT INTO {node_type} (type, name, module, " + \ 597 "has_title, title_label, has_body, body_label, description, " + \ 598 "help, min_word_count, custom, modified, locked, orig_type) " + \ 599 "VALUES ('%s', '%s', '%s', %d, '%s', %d, '%s', '%s', '%s', " + \ 600 "%d, %d, %d, %d, '%s')", + \ 601 info.type, info.name, info.module, info.has_title, \ 602 info.title_label, info.has_body, info.body_label, \ 603 info.description, info.help, info.min_word_count, \ 604 info.custom, info.modified, info.locked, info.orig_type) 605 lib_plugin.invoke_all('node_type', 'insert', info) 606 return SAVED_NEW
607 608
609 -def type_delete(type_):
610 """ 611 Deletes a node type from the database. 612 613 @param type 614 The machine-readable name of the node type to be deleted. 615 """ 616 info = get_types('type', type_) 617 lib_database.query("DELETE FROM {node_type} WHERE type = '%s'", type_) 618 lib_plugin.invoke_all('node_type', 'delete', info)
619 620 621
622 -def type_update_nodes(old_type, type_):
623 """ 624 Updates all nodes of one type to be of another type. 625 626 @param old_type 627 The current node type of the nodes. 628 @param type 629 The new node type of the nodes. 630 631 @return 632 The number of nodes whose node type field was modified. 633 """ 634 lib_database.query("UPDATE {node} SET type = '%s' WHERE type = '%s'", \ 635 type_, old_type) 636 return lib_database.affected_rows()
637 638 639
640 -def _types_build():
641 """ 642 Builds and returns the list of available node types. 643 644 The list of types is built by querying hook_node_info() in all modules, and 645 by comparing this information with the node types in the {node_type} table. 646 """ 647 _node_types = [] 648 _node_names = [] 649 info_array = lib_plugin.invoke_all('node_info') 650 for type_,info in info_array.items(): 651 info['type'] = type_ 652 _node_types[type_] = php.object_(_type_set_defaults(info)) 653 _node_names[type_] = info['name'] 654 type_result = lib_database.query(lib_database.rewrite_sql('\ 655 SELECT nt.type, nt.* FROM {node_type} nt ORDER BY nt.type ASC', \ 656 'nt', 'type')) 657 while True: 658 type_object = lib_database.fetch_object(type_result) 659 if type_object: 660 break 661 # Check for node types from disabled modules and mark 662 # their types for removal. 663 # Types defined by the node module in the database 664 # (rather than by a separate 665 # module using hook_node_info) have a module value of 'node'. 666 if (type_object.plugin != 'node' and \ 667 php.empty(info_array[type_object.type_])): 668 type_object.disabled = True 669 if (not php.isset(_node_types, type_object.type_) or type_object.modified): 670 _node_types[type_object.type_] = type_object 671 _node_names[type_object.type_] = type_object.name 672 if (type_object.type_ != type_object.orig_type): 673 del(_node_types[type_object.orig_type]) 674 del(_node_names[type_object.orig_type]) 675 php.asort(_node_names) 676 return (_node_types, _node_names)
677 678 679
680 -def _type_set_defaults(info):
681 """ 682 Set default values for a node type defined through hook_node_info(). 683 """ 684 if (not php.isset(info['has_title'])): 685 info['has_title'] = True 686 if (info['has_title'] and not php.isset(info, 'title_label')): 687 info['title_label'] = t('Title') 688 if (not isset(info, 'has_body')): 689 info['has_body'] = True 690 if (info['has_body'] and not php.isset(info, 'body_label')): 691 info['body_label'] = lib_common.t('Body') 692 if (not php.isset(info, 'help')): 693 info['help'] = '' 694 if (not php.isset(info, 'min_word_count')): 695 info['min_word_count'] = 0 696 if (not php.isset(info, 'custom')): 697 info['custom'] = False 698 if (not php.isset(info, 'modified')): 699 info['modified'] = False 700 if (not php.isset(info, 'locked')): 701 info['locked'] = True 702 info['orig_type'] = info['type'] 703 info['is_new'] = True 704 return info
705 706 707
708 -def hook(node, hook_):
709 """ 710 Determine whether a node hook exists. 711 712 @param &node 713 Either a node object, node array, or a string containing the node type. 714 @param hook 715 A string containing the name of the hook. 716 @return 717 True iff the hook exists in the node type of node. 718 """ 719 plugin = get_types('module', node) 720 if (module == 'node'): 721 # Avoid function name collisions. 722 lib_plugin = 'node_content' 723 return lib_plugin.hook(plugin, hook_)
724 725 726
727 -def invoke(node, hook_, a2 = None, a3 = None, a4 = None):
728 """ 729 Invoke a node hook. 730 731 @param &node 732 Either a node object, node array, or a string containing the node type. 733 @param hook 734 A string containing the name of the hook. 735 @param a2, a3, a4 736 Arguments to pass on to the hook, after the node argument. 737 @return 738 The returned value of the invoked hook. 739 """ 740 if (hook(node, hook_)): 741 plugin = get_types('plugin', node) 742 if (plugin == 'node'): 743 # Avoid function name collisions. 744 plugin = 'content'; 745 function = plugin + '_' + hook 746 return php.call_user_func(function, node, a2, a3, a4)
747 748 749
750 -def invoke_nodeapi(node, op, a3 = None, a4 = None):
751 """ 752 Invoke a hook_nodeapi() operation in all modules. 753 754 @param &node 755 A node object. 756 @param op 757 A string containing the name of the nodeapi operation. 758 @param a3, a4 759 Arguments to pass on to the hook, after the node and op arguments. 760 @return 761 The returned value of the invoked hooks. 762 """ 763 return_ = [] 764 for name in lib_plugin.implements('nodeapi'): 765 function = DrupyImport.getFunction(lib_plugin.plugins[name], 'nodeapi') 766 result = function(node, op, a3, a4) 767 if (result and php.is_array(result)): 768 return_ = php.array_merge(return_, result) 769 elif (result): 770 return_.append( result ) 771 return return_
772 773 774 """ 775 Load a node object from the database. 776 777 @param param 778 Either the nid of the node or an array of conditions to match against in 779 the database query 780 @param revision 781 Which numbered revision to load. Defaults to the current version. 782 @param reset 783 Whether to reset the internal node_load cache. 784 785 @return 786 A fully-populated node object. 787 """
788 -def load(param = [], revision = None, reset = None):
789 php.static(load, 'nodes', []) 790 if (reset): 791 load.nodes = [] 792 cachable = (revision == None) 793 arguments = [] 794 if (php.is_numeric(param)): 795 if (cachable): 796 # Is the node statically cached? 797 if (php.isset(load.nodes[param])): 798 return (php.clone(nodes[param]) if \ 799 php.is_object(load.nodes[param]) else nodes[param]) 800 cond = 'n.nid = %d' 801 arguments.append( param ) 802 elif (php.is_array(param)): 803 # Turn the conditions into a query. 804 for key,value in param.items(): 805 cond.append( 'n.' + lib_database.escape_table(key) + " = '%s'" ) 806 arguments.append( value ) 807 cond = implode(' AND ', cond) 808 else: 809 return False 810 # Retrieve a field list based on the site's schema. 811 fields = drupal_schema_fields_sql('node', 'n') 812 fields = php.array_merge(fields, \ 813 drupal_schema_fields_sql('node_revisions', 'r')) 814 fields = php.array_merge(fields, ('u.name', 'u.picture', 'u.data')) 815 # Remove fields not needed in the query: n.vid and r.nid are redundant, 816 # n.title is unnecessary because the node title comes from the 817 # node_revisions table. We'll keep r.vid, r.title, and n.nid. 818 fields = php.array_diff(fields, ('n.vid', 'n.title', 'r.nid')) 819 fields = php.implode(', ', fields) 820 # Rename timestamp field for clarity. 821 fields = php.str_replace('r.timestamp', \ 822 'r.timestamp AS revision_timestamp', fields) 823 # Change name of revision uid so it doesn't conflict with n.uid. 824 fields = php.str_replace('r.uid', 'r.uid AS revision_uid', fields) 825 # Retrieve the node. 826 # No db_rewrite_sql is applied so as to get complete indexing for search. 827 if (revision): 828 php.array_unshift(arguments, revision) 829 node = lib_database.fetch_object(lib_database.query(\ 830 'SELECT ' + fields + ' FROM {node} n ' + \ 831 'INNER JOIN {users} u ON u.uid = n.uid ' + \ 832 'INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d ' + \ 833 'WHERE ' + cond, arguments)) 834 else: 835 node = lib_database.fetch_object(lib_database.query(\ 836 'SELECT ' + fields + ' FROM {node} n ' + \ 837 'INNER JOIN {users} u ON u.uid = n.uid ' + \ 838 'INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE ' + \ 839 cond, arguments)) 840 if (node and node.nid): 841 # Call the node specific callback (if any) and piggy-back the 842 # results to the node or overwrite some values. 843 extra = invoke(node, 'load') 844 if extra: 845 for key,value in extra.items(): 846 setattr(node, key, value) 847 extra = invoke_nodeapi(node, 'load') 848 if extra: 849 for key,value in extra.items(): 850 setattr(node, key, value) 851 if (cachable): 852 nodes[node.nid] = (php.clone(node) if php.is_object(node) else node) 853 return node
854 855 856
857 -def validate(node, form = []):
858 """ 859 Perform validation checks on the given node. 860 """ 861 # Convert the node to an object, if necessary. 862 node = php.object_(node) 863 type_ = get_types('type', node) 864 # Make sure the body has the minimum number of words. 865 # TODO : use a better word counting algorithm that will work in other 866 # languages 867 if (not php.empty(type_.min_word_count) and php.isset(node, 'body') and \ 868 php.count(php.explode(' ', node.body)) < type_.min_word_count): 869 lib_form.set_error('body', lib_common.t(\ 870 'The body of your @type is too short. You need at least ' + 871 '%words words.', \ 872 {'%words' : type.min_word_count, '@type' : type.name})) 873 if (php.isset(node, 'nid') and (last_changed(node.nid) > node.changed)): 874 lib_form.set_error('changed', t(\ 875 'This content has been modified by another user, ' + \ 876 'changes cannot be saved.')) 877 if (lib_plugin.plugins['user'].access('administer nodes')): 878 # Validate the "authored by" field. 879 if (not php.empty(node.name)): 880 account = user_load({'name' : node.name}) 881 if not account: 882 # The use of empty() is mandatory in the context of usernames 883 # as the empty string denotes the anonymous user. In case we 884 # are dealing with an anonymous user we set the user ID to 0. 885 lib_form.set_error('name',\ 886 lib_common.t('The username %name does not exist.', \ 887 {'%name' : node.name})) 888 # Validate the "authored on" field. As of PHP 5.1.0, strtotime returns 889 # False instead of -1 upon failure. 890 if (not php.empty(node.date) and php.strtotime(node.date) <= 0): 891 lib_form.set_error('date', lib_common.t(\ 892 'You have to specify a valid date.')) 893 # Do node-type-specific validation checks. 894 invoke(node, 'validate', form) 895 invoke_nodeapi(node, 'validate', form)
896 897 898 899
900 -def submit(node):
901 """ 902 Prepare node for save and allow modules to make changes. 903 """ 904 # Convert the node to an object, if necessary. 905 node = php.object_(node) 906 # Generate the teaser, but only if it hasn't been set (e.g. by a 907 # module-provided 'teaser' form item). 908 if (not php.isset(node.teaser)): 909 if (isset(node.body)): 910 node.teaser = teaser(node.body, (node.format if \ 911 php.isset(node, 'format') else None)) 912 # Chop off the teaser from the body if needed. The teaser_include 913 # property might not be set (eg. in Blog API postings), so only act on 914 # it, if it was set with a given value. 915 if (php.isset(node.teaser_include) and \ 916 not node.teaser_include and \ 917 node.teaser == php.substr(node.body, 0, php.strlen(node.teaser))): 918 node.body = php.substr(node.body, php.strlen(node.teaser)) 919 else: 920 node.teaser = '' 921 if (lib_plugin.plugins['user'].access('administer nodes')): 922 # Populate the "authored by" field. 923 account = lib_plugin.plugins['user'].load({'name' : node.name}) 924 if account: 925 node.uid = account.uid 926 else: 927 node.uid = 0 928 node.created = (php.strtotime(node.date) if \ 929 not empty(node.date) else php.time_()) 930 node.validated = True 931 return node
932 933 934
935 -def save(node):
936 """ 937 Save a node object into the database. 938 """ 939 php.Reference.check(node) 940 # Let modules modify the node before it is saved to the database. 941 invoke_nodeapi(node, 'presave') 942 node.is_new = False 943 # Apply filters to some default node fields: 944 if (php.empty(node.nid)): 945 # Insert a new node. 946 node.is_new = True 947 # When inserting a node, node.log must be set because 948 # {node_revisions}.log does not (and cannot) have a default 949 # value. If the user does not have permission to create 950 # revisions, however, the form will not contain an element for 951 # log so node.log will be unset at this point. 952 if (not php.isset(node, 'log')): 953 node.log = '' 954 # For the same reasons, make sure we have node.teaser and 955 # node.body. We should consider making these fields Noneable 956 # in a future version since node types are not required to use them. 957 if (not php.isset(node, 'teaser')): 958 node.teaser = '' 959 if (not php.isset(node, 'body')): 960 node.body = '' 961 elif (not php.empty(node.revision)): 962 node.old_vid = node.vid 963 else: 964 # When updating a node, avoid clobberring an 965 # existing log entry with an empty one. 966 if (php.empty(node.log)): 967 del(node.log) 968 # Set some required fields: 969 if (php.empty(node.created)): 970 node.created = php.time_() 971 # The changed timestamp is always updated for bookkeeping 972 # purposes (revisions, searching, ...) 973 node.changed = php.time_() 974 node.timestamp = php.time_() 975 node.format = (node.format if php.isset(node.format) else \ 976 FILTER_FORMAT_DEFAULT) 977 update_node = True 978 # Generate the node table query and the node_revisions table query. 979 if (node.is_new): 980 drupal_write_record('node', node) 981 _save_revision(node, lib_appglobals.user.uid) 982 op = 'insert' 983 else: 984 drupal_write_record('node', node, 'nid') 985 if (not empty(node.revision)): 986 _save_revision(node, lib_appglobals.user.uid) 987 else: 988 _save_revision(node, lib_appglobals.user.uid, 'vid') 989 update_node = False 990 op = 'update' 991 if (update_node): 992 lib_database.query(\ 993 'UPDATE {node} SET vid = %d WHERE nid = %d', node.vid, node.nid) 994 # Call the node specific callback (if any). This can be 995 # node_invoke(node, 'insert') or 996 # node_invoke(node, 'update'). 997 invoke(node, op) 998 invoke_nodeapi(node, op) 999 # Update the node access table for this node. 1000 access_acquire_grants(node) 1001 # Clear the page and block caches. 1002 lib_cache.clear_all()
1003 1004 1005
1006 -def _save_revision(node, uid, update = None):
1007 """ 1008 Helper function to save a revision with the uid of the current user. 1009 1010 Node is taken by reference, becuse drupal_write_record() updates the 1011 node with the revision id, and we need to pass that back to the caller. 1012 """ 1013 php.Reference.check(node) 1014 temp_uid = node.uid 1015 node.uid = uid 1016 if (update is not None): 1017 drupal_write_record('node_revisions', node, update) 1018 else: 1019 drupal_write_record('node_revisions', node) 1020 node.uid = temp_uid
1021 1022
1023 -def delete(nid):
1024 """ 1025 Delete a node. 1026 """ 1027 node = load(nid) 1028 if (access('delete', node)): 1029 lib_database.query('DELETE FROM {node} WHERE nid = %d', node.nid) 1030 lib_database.query('DELETE FROM {node_revisions} WHERE nid = %d', node.nid) 1031 # Call the node-specific callback (if any): 1032 invoke(node, 'delete') 1033 invoke_nodeapi(node, 'delete') 1034 # Clear the page and block caches. 1035 lib_cache.clear_all() 1036 # Remove this node from the search index if needed. 1037 # This code is implemented in node module rather than in search module, 1038 # because node module is implementing search module's API, not the other 1039 # way around. 1040 if (lib_plugin.exists('search')): 1041 lib_plugin.plugins['search'].wipe(node.nid, 'node') 1042 watchdog('content', '@type: deleted %title.', {'@type' : node.type, \ 1043 '%title' : node.title}) 1044 drupal_set_message(lib_common.t('@type %title has been deleted.', \ 1045 {'@type' : node_get_types('name', node), '%title' : node.title}))
1046 1047
1048 -def view(node, teaser = False, page = False, links = True):
1049 """ 1050 Generate a display of the given node. 1051 1052 @param node 1053 A node array or node object. 1054 @param teaser 1055 Whether to display the teaser only or the full form. 1056 @param page 1057 Whether the node is being displayed by itself as a page. 1058 @param links 1059 Whether or not to display node links. Links are omitted for node previews. 1060 1061 @return 1062 An HTML representation of the themed node. 1063 """ 1064 node = php.object_(node) 1065 node = build_content(node, teaser, page) 1066 if (links is not None): 1067 node.links = lib_plugin.invoke_all('link', 'node', node, teaser) 1068 drupal_alter('link', node.links, node) 1069 # Set the proper node part, then unset unused node part so that a bad 1070 # theme can not open a security hole. 1071 content = drupal_render(node.content) 1072 if (teaser): 1073 node.teaser = content 1074 del(node.body) 1075 else: 1076 node.body = content 1077 del(node.teaser) 1078 # Allow modules to modify the fully-built node. 1079 invoke_nodeapi(node, 'alter', teaser, page) 1080 return lib_theme.theme('node', node, teaser, page)
1081 1082 1083
1084 -def prepare(node, teaser = False):
1085 """ 1086 Apply filters and build the node's standard elements. 1087 """ 1088 # First we'll overwrite the existing node teaser and body with 1089 # the filtered copiesnot Then, we'll stick those into the content 1090 # array and set the read more flag if appropriate. 1091 node.readmore = (php.strlen(node.teaser) < php.strlen(node.body)) 1092 if (teaser == False): 1093 node.body = check_markup(node.body, node.format, False) 1094 else: 1095 node.teaser = check_markup(node.teaser, node.format, False) 1096 node.content['body'] = { 1097 '#markup' : (node.teaser if teaser else node.body), 1098 '#weight' : 0 1099 } 1100 return node
1101 1102 1103
1104 -def build_content(node, teaser = False, page = False):
1105 """ 1106 Builds a structured array representing the node's content. 1107 1108 @param node 1109 A node object. 1110 @param teaser 1111 Whether to display the teaser only, as on the main page. 1112 @param page 1113 Whether the node is being displayed by itself as a page. 1114 1115 @return 1116 An structured array containing the individual elements 1117 of the node's body. 1118 """ 1119 # The build mode identifies the target for which the node is built. 1120 if (not php.isset(node.build_mode)): 1121 node.build_mode = NODE_BUILD_NORMAL 1122 # Remove the delimiter (if any) that separates the teaser from the body. 1123 node.body = (php.str_replace('<!--break-.', '', node.body) if \ 1124 php.isset(node.body) else '') 1125 # The 'view' hook can be implemented to overwrite the default function 1126 # to display nodes. 1127 if (hook(node, 'view')): 1128 node = invoke(node, 'view', teaser, page) 1129 else: 1130 node = prepare(node, teaser) 1131 # Allow modules to make their own additions to the node. 1132 invoke_nodeapi(node, 'view', teaser, page) 1133 return node
1134 1135 1136
1137 -def show(node, cid, message = False):
1138 """ 1139 Generate a page displaying a single node, along with its comments. 1140 """ 1141 if (message): 1142 drupal_set_title(t(\ 1143 'Revision of %title from %date', \ 1144 {'%title' : node.title, '%date' : \ 1145 format_date(node.revision_timestamp)})) 1146 output = view(node, False, True) 1147 if (php.function_exists('comment_render', lib_comment) and node.comment): 1148 output += lib_comment.render(node, cid) 1149 # Update the history table, stating that this user viewed this node. 1150 tag_new(node.nid) 1151 return output
1152 1153 1154 1155 """ 1156 Theme a log message. 1157 1158 @ingroup themeable 1159 """
1160 -def theme_log_message(log):
1161 return '<div class="log"><div class="title">' + \ 1162 lib_common.t('Log') + ':</div>' + log + '</div>'
1163 1164 1165
1166 -def hook_perm():
1167 """ 1168 Implementation of hook_perm(). 1169 """ 1170 perms = { 1171 'administer content types' : \ 1172 lib_common.t('Manage content types and ' + \ 1173 'content type administration settings.'), 1174 'administer nodes' : \ 1175 lib_common.t('Manage all website content, and ' + \ 1176 'bypass any content-related access control + %warning', \ 1177 {'%warning' : lib_common.t('Warning: Give to trusted ' + \ 1178 'roles only; this permission has security implications.')}), 1179 'access content' : lib_common.t('View published content.'), 1180 'view revisions' : lib_common.t('View content revisions.'), 1181 'revert revisions' : \ 1182 lib_common.t('Replace content with an older revision.'), 1183 'delete revisions' : lib_common.t('Delete content revisions.') 1184 } 1185 for type_ in get_types(): 1186 if (type_.plugin == 'node'): 1187 perms += ist_permissions(type_) 1188 return perms
1189 1190 1191
1192 -def _rankings():
1193 """ 1194 Gather the rankings from the the hook_ranking implementations. 1195 """ 1196 rankings = { 1197 'total' : 0, 'join' : [], 'score' : [], 'args' : [] 1198 } 1199 ranking = lib_plugin.invoke_all('ranking') 1200 if ranking: 1201 for rank,values in ranking.items(): 1202 node_rank = lib_common.variable_get('node_rank_' + rank, 0) 1203 if node_rank: 1204 # If the table defined in the ranking 1205 # isn't already joined, then add it. 1206 if (php.isset(values['join']) and \ 1207 not php.isset(rankings['join'], values['join'])): 1208 rankings['join'][values['join']] = values['join'] 1209 # Add the rankings weighted score multiplier 1210 # value, handling None gracefully. 1211 rankings['score'].append( \ 1212 '%f * COALESCE((' + values['score'] + '), 0)' ) 1213 # Add the the administrator's weighted 1214 # score multiplier value for this ranking. 1215 rankings['total'] += node_rank 1216 rakings['arguments'].append( node_rank ) 1217 # Add any additional arguments used by this ranking. 1218 if (php.isset(values, 'arguments')): 1219 rankings['arguments'] = php.array_merge(rankings['arguments'], \ 1220 values['arguments']) 1221 return rankings
1222 1223 1224
1225 -def hook_search(op = 'search', keys = None):
1226 """ 1227 Implementation of hook_search(). 1228 """ 1229 if op == 'name': 1230 return lib_common.t('Content') 1231 elif op == 'reset': 1232 lib_database.query(\ 1233 "UPDATE {search_dataset} SET reindex = %d WHERE type = 'node'", \ 1234 php.time_()) 1235 return 1236 elif op == 'status': 1237 total = lib_database.result(lib_database.query(\ 1238 'SELECT COUNT(*) FROM {node} WHERE status = 1')) 1239 remaining = lib_database.result(lib_database.query(\ 1240 "SELECT COUNT(*) FROM {node} n " + \ 1241 "LEFT JOIN {search_dataset} d ON d.type = 'node' AND " + \ 1242 "d.sid = n.nid WHERE n.status = 1 AND " + \ 1243 "d.sid IS None OR d.reindex <> 0")) 1244 return {'remaining' : remaining, 'total' : total} 1245 elif op == 'admin': 1246 form = [] 1247 # Output form for defining rank factor weights. 1248 form['content_ranking'] = { 1249 '#type' : 'fieldset', 1250 '#title' : lib_common.t('Content ranking') 1251 } 1252 form['content_ranking']['#theme'] = 'node_search_admin' 1253 form['content_ranking']['info'] = { 1254 '#value' : '<em>' + lib_common.t(\ 1255 'The following numbers control which properties the ' + \ 1256 'content search should favor when ordering the results. ' + \ 1257 'Higher numbers mean more influence, zero means the ' + \ 1258 'property is ignored. Changing these numbers does not ' + \ 1259 'require the search index to be rebuilt. Changes take ' + \ 1260 'effect immediately.') + '</em>' 1261 } 1262 # Note: reversed to reflect that higher number = higher ranking. 1263 options = drupal_map_assoc(range(0, 10)) 1264 for var, values in lib_plugin.invoke_all('ranking').items(): 1265 form['content_ranking']['factors']['node_rank_' + var] = { 1266 '#title' : values['title'], 1267 '#type' : 'select', 1268 '#options' : options, 1269 '#default_value' : variable_get('node_rank_'. var, 0) 1270 } 1271 return form 1272 elif op == 'search': 1273 # Build matching conditions 1274 join1, where1 = lib_database._rewrite_sql() 1275 arguments1 = [] 1276 conditions1 = 'n.status = 1' 1277 type_ = lib_plugin.plugins['search'].query_extract(keys, 'type') 1278 if type_: 1279 types = [] 1280 for t_ in explode(',', type_): 1281 types.append( "n.type = '%s'" ) 1282 arguments1.append( t_ ) 1283 conditions1 += ' AND (' + implode(' OR ', types) + ')' 1284 keys = lib_plugin.plugins['search'].query_insert(keys, 'type') 1285 category = lib_plugin.plugins['search'].query_extract(keys, 'category') 1286 if category: 1287 categories = [] 1288 for c in explode(',', category): 1289 categories.append( "tn.tid = %d" ) 1290 arguments1.append( c ) 1291 conditions1 += ' AND (' + php.implode(' OR ', categories) + ')' 1292 join1 += ' INNER JOIN {term_node} tn ON n.vid = tn.vid' 1293 keys = lib_plugin.plugins['search'].query_insert(keys, 'category') 1294 languages = lib_plugin.plugins['search'].query_extract(keys, 'language') 1295 if languages: 1296 categories = [] 1297 for l in explode(',', languages): 1298 categories.append( "n.language = '%s'" ) 1299 arguments1.append( l ) 1300 conditions1 += ' AND (' + php.implode(' OR ', categories) + ')' 1301 keys = lib_plugin.plugins['search'].query_insert(keys, 'language') 1302 # Get the ranking expressions. 1303 rankings = _rankings() 1304 # When all search factors are disabled (ie they have a weight of zero), 1305 # The default score is based only on keyword relevance. 1306 if (rankings['total'] == 0): 1307 total = 1 1308 arguments2 = [] 1309 join2 = '' 1310 select2 = 'i.relevance AS score' 1311 else: 1312 total = rankings['total'] 1313 arguments2 = rankings['arguments'] 1314 join2 = php.implode(' ', rankings['join']) 1315 select2 = '(' + php.implode(' + ', rankings['score']) + ') AS score' 1316 # Do search. 1317 find = do_search(keys, 'node', \ 1318 'INNER JOIN {node} n ON n.nid = i.sid ' + join1, \ 1319 conditions1 + ('' if empty(where1) else ' AND ' + where1), \ 1320 arguments1, select2, join2, arguments2) 1321 # Load results. 1322 results = [] 1323 for item in find: 1324 # Build the node body. 1325 node = load(item.sid) 1326 node.build_mode = NODE_BUILD_SEARCH_RESULT 1327 node = build_content(node, False, False) 1328 node.body = drupal_render(node.content) 1329 # Fetch comments for snippet. 1330 node.body += lib_plugin.invoke('comment', 'nodeapi', node, \ 1331 'update index') 1332 # Fetch terms for snippet. 1333 node.body += lib_plugin.invoke('taxonomy', \ 1334 'nodeapi', node, 'update index') 1335 extra = invoke_nodeapi(node, 'search result') 1336 results.append({ 1337 'link' : url('node/' + item.sid, {'absolute' : True}), 1338 'type' : check_plain(get_types('name', node)), 1339 'title' : node.title, 1340 'user' : lib_theme.theme('username', node), 1341 'date' : node.changed, 1342 'node' : node, 1343 'extra' : extra, 1344 'score' : ((item.score / total) if total else 0), 1345 'snippet' : lib_plugin.plugins['search'].excerpt(keys, node.body) 1346 }) 1347 return results
1348 1349 1350
1351 -def hook_ranking():
1352 """ 1353 Implementation of hook_ranking(). 1354 """ 1355 # Create the ranking array and add the basic ranking options. 1356 ranking = { 1357 'relevance' : { 1358 'title' : lib_common.t('Keyword relevance'), 1359 # Average relevance values hover around 0.15 1360 'score' : 'i.relevance' 1361 }, 1362 'sticky' : { 1363 'title' : lib_common.t('Content is sticky at top of lists'), 1364 # The sticky flag is either 0 or 1, which is automatically normalized. 1365 'score' : 'n.sticky' 1366 }, 1367 'promote' : { 1368 'title' : lib_common.t('Content is promoted to the front page'), 1369 # The promote flag is either 0 or 1, which is automatically normalized. 1370 'score' : 'n.promote' 1371 } 1372 } 1373 # Add relevance based on creation or changed date. 1374 node_cron_last = lib_bootstrap.variable_get('node_cron_last', 0) 1375 if node_cron_last: 1376 ranking['recent'] = { 1377 'title' : t('Recently posted'), 1378 # Exponential decay with half-life of 6 months, starting at last indexed node 1379 'score' : '(POW(2, GREATEST(n.created, n.changed) - %d) * 6.43e-8)', 1380 'arguments' : [node_cron_last] 1381 } 1382 return ranking
1383 1384
1385 -def hook_user(op, edit, user):
1386 """ 1387 Implementation of hook_user(). 1388 """ 1389 php.Reference.check(edit) 1390 php.Reference.check(user) 1391 if (op == 'delete'): 1392 lib_database.query('UPDATE {node} SET uid = 0 WHERE uid = %d', \ 1393 user.uid) 1394 lib_database.query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', \ 1395 user.uid)
1396 1397 1398
1399 -def theme_search_admin(form):
1400 """ 1401 Theme the content ranking part of the search settings admin page. 1402 1403 @ingroup themeable 1404 """ 1405 output = drupal_render(form['info']) 1406 header = (lib_common.t('Factor'), lib_common.t('Weight')) 1407 for key in element_children(form['factors']): 1408 row = [] 1409 row.append( form['factors'][key]['#title'] ) 1410 del(form['factors'][key]['#title']) 1411 row.append( drupal_render(form['factors'][key]) ) 1412 rows.append( row ) 1413 output += lib_theme.theme('table', header, rows) 1414 output += drupal_render(form) 1415 return output
1416 1417 1418
1419 -def comment_mode(nid):
1420 """ 1421 Retrieve the comment mode for the given node ID (none, read, or read/write). 1422 """ 1423 php.static(comment_mode, 'comment_mode', {}) 1424 if (not php.isset(comment_mode.comment_mode, nid)): 1425 comment_mode.comment_mode[nid] = lib_database.result(lib_database.query(\ 1426 'SELECT comment FROM {node} WHERE nid = %d', nid)) 1427 return comment_mode[nid]
1428 1429 1430 1447 1448 1449
1450 -def _revision_access(node, op = 'view'):
1451 php.static(_revision_access, 'access', []) 1452 if (not php.isset(_revision_access.access, node.vid)): 1453 node_current_revision = load(node.nid) 1454 is_current_revision = (node_current_revision.vid == node.vid) 1455 # There should be at least two revisions. If the vid of the given node 1456 # and the vid of the current revision differs, then we already have two 1457 # different revisions so there is no need for a separate database check. 1458 # Also, if you try to revert to or delete the current revision, that's 1459 # not good. 1460 if (is_current_revision and (lib_database.result(lib_database.query(\ 1461 'SELECT COUNT(vid) FROM {node_revisions} ' + \ 1462 'WHERE nid = %d', \ 1463 node.nid)) == 1 or op == 'update' or op == 'delete')): 1464 _revision_access.access[node.vid] = False 1465 elif (lib_plugin.plugins['user'].access('administer nodes')): 1466 _revision_access.access[node.vid] = True 1467 else: 1468 map_ = {'view' : 'view revisions', 'update' : 'revert revisions',\ 1469 'delete' : 'delete revisions'} 1470 # First check the user permission, second check the access to the 1471 # current revision and finally, if the node passed in is not the current 1472 # revision then access to that, too. 1473 _revision_access.access[node.vid] = (php.isset(map_, op) and \ 1474 lib_plugin.plugins['user'].access(map_[op]) and \ 1475 access(op, node_current_revision) and \ 1476 (is_current_revision or node_access(op, node))) 1477 return _revision_access.access[node.vid]
1478 1479 1480 1481
1482 -def _add_access():
1483 types = get_types() 1484 for type_ in types: 1485 if (hook(type_.type_, 'form') and access('create', type_.type_)): 1486 return True 1487 return False
1488 1489 1490
1491 -def hook_menu():
1492 """ 1493 Implementation of hook_menu(). 1494 """ 1495 items['admin/content/node'] = { 1496 'title' : 'Content', 1497 'description' : "View, edit, and delete your site's content.", 1498 'page callback' : 'drupal_get_form', 1499 'page arguments' : ('node_admin_content',), 1500 'access arguments' : ('administer nodes',) 1501 } 1502 items['admin/content/node/overview'] = { 1503 'title' : 'List', 1504 'type' : MENU_DEFAULT_LOCAL_TASK, 1505 'weight' : -10 1506 } 1507 items['admin/content/node-settings'] = { 1508 'title' : 'Post settings', 1509 'description' : 'Control posting behavior, such as teaser ' + \ 1510 'length, requiring previews before posting, and the number ' + \ 1511 'of posts on the front page.', 1512 'page callback' : 'drupal_get_form', 1513 'page arguments' : ('node_configure',), 1514 'access arguments' : ('administer nodes',) 1515 } 1516 items['admin/content/node-settings/rebuild'] = { 1517 'title' : 'Rebuild permissions', 1518 'page arguments' : ('node_configure_rebuild_confirm',), 1519 # Any user than can potentially trigger a node_acess_needs_rebuild(True) 1520 # has to be allowed access to the 'node access rebuild' confirm form. 1521 'access arguments' : ('access administration pages',), 1522 'type' : MENU_CALLBACK 1523 } 1524 items['admin/build/types'] = { 1525 'title' : 'Content types', 1526 'description' : 'Manage posts by content type, including default ' + \ 1527 'status, front page promotion, etc.', 1528 'page callback' : 'node_overview_types', 1529 'access arguments' : ('administer content types',) 1530 } 1531 items['admin/build/types/list'] = { 1532 'title' : 'List', 1533 'type' : MENU_DEFAULT_LOCAL_TASK, 1534 'weight' : -10 1535 } 1536 items['admin/build/types/add'] = { 1537 'title' : 'Add content type', 1538 'page callback' : 'drupal_get_form', 1539 'page arguments' : ('node_type_form',), 1540 'access arguments' : ('administer content types',), 1541 'type' : MENU_LOCAL_TASK 1542 } 1543 items['node'] = { 1544 'title' : 'Content', 1545 'page callback' : 'node_page_default', 1546 'access arguments' : ('access content',), 1547 'type' : MENU_CALLBACK 1548 } 1549 items['node/add'] = { 1550 'title' : 'Create content', 1551 'page callback' : 'node_add_page', 1552 'access callback' : '_node_add_access', 1553 'weight' : 1 1554 } 1555 items['rss.xml'] = { 1556 'title' : 'RSS feed', 1557 'page callback' : 'node_feed', 1558 'access arguments' : ('access content',), 1559 'type' : MENU_CALLBACK 1560 } 1561 for type_ in get_types('types', None, True): 1562 type_url_str = php.str_replace('_', '-', type_.type_) 1563 items['node/add/' + type_url_str] = { 1564 'title' : drupal_ucfirst(type_.name), 1565 'title callback' : 'check_plain', 1566 'page callback' : 'node_add', 1567 'page arguments' : (2,), 1568 'access callback' : 'node_access', 1569 'access arguments' : ('create', type.type), 1570 'description' : type.description 1571 } 1572 items['admin/build/node-type/' + type_url_str] = { 1573 'title' : type_.name, 1574 'page callback' : 'drupal_get_form', 1575 'page arguments' : ('node_type_form', type), 1576 'access arguments' : ('administer content types',), 1577 'type' : MENU_CALLBACK 1578 } 1579 items['admin/build/node-type/' + type_url_str + '/edit'] = { 1580 'title' : 'Edit', 1581 'type' : MENU_DEFAULT_LOCAL_TASK 1582 } 1583 items['admin/build/node-type/' + type_url_str + '/delete'] = { 1584 'title' : 'Delete', 1585 'page arguments' : ('node_type_delete_confirm', type), 1586 'access arguments' : ('administer content types',), 1587 'type' : MENU_CALLBACK 1588 } 1589 items['node/%node'] = { 1590 'title callback' : 'node_page_title', 1591 'title arguments' : (1,), 1592 'page callback' : 'node_page_view', 1593 'page arguments' : (1,), 1594 'access callback' : 'node_access', 1595 'access arguments' : ('view', 1), 1596 'type' : MENU_CALLBACK 1597 } 1598 items['node/%node/view'] = { 1599 'title' : 'View', 1600 'type' : MENU_DEFAULT_LOCAL_TASK, 1601 'weight' : -10 1602 } 1603 items['node/%node/edit'] = { 1604 'title' : 'Edit', 1605 'page callback' : 'node_page_edit', 1606 'page arguments' : (1,), 1607 'access callback' : 'node_access', 1608 'access arguments' : ('update', 1), 1609 'weight' : 1, 1610 'type' : MENU_LOCAL_TASK 1611 } 1612 items['node/%node/delete'] = { 1613 'title' : 'Delete', 1614 'page callback' : 'drupal_get_form', 1615 'page arguments' : ('node_delete_confirm', 1), 1616 'access callback' : 'node_access', 1617 'access arguments' : ('delete', 1), 1618 'weight' : 1, 1619 'type' : MENU_CALLBACK 1620 } 1621 items['node/%node/revisions'] = { 1622 'title' : 'Revisions', 1623 'page callback' : 'node_revision_overview', 1624 'page arguments' : (1,), 1625 'access callback' : '_node_revision_access', 1626 'access arguments' : (1,), 1627 'weight' : 2, 1628 'type' : MENU_LOCAL_TASK 1629 } 1630 items['node/%node/revisions/%/view'] = { 1631 'title' : 'Revisions', 1632 'load arguments' : (3,), 1633 'page callback' : 'node_show', 1634 'page arguments' : (1, None, True), 1635 'access callback' : '_node_revision_access', 1636 'access arguments' : (1,), 1637 'type' : MENU_CALLBACK 1638 } 1639 items['node/%node/revisions/%/revert'] = { 1640 'title' : 'Revert to earlier revision', 1641 'load arguments' : (3,), 1642 'page callback' : 'drupal_get_form', 1643 'page arguments' : ('node_revision_revert_confirm', 1), 1644 'access callback' : '_node_revision_access', 1645 'access arguments' : (1, 'update'), 1646 'type' : MENU_CALLBACK 1647 } 1648 items['node/%node/revisions/%/delete'] = { 1649 'title' : 'Delete earlier revision', 1650 'load arguments' : (3,), 1651 'page callback' : 'drupal_get_form', 1652 'page arguments' : ('node_revision_delete_confirm', 1), 1653 'access callback' : '_node_revision_access', 1654 'access arguments' : (1, 'delete'), 1655 'type' : MENU_CALLBACK 1656 } 1657 return items
1658 1659 1660
1661 -def page_title(node):
1662 """ 1663 Title callback. 1664 """ 1665 return node.title
1666 1667 1668 1669
1670 -def hook_init():
1671 """ 1672 Implementation of hook_init(). 1673 """ 1674 lib_common.drupal_add_css(\ 1675 lib_common.drupal_get_path('plugin', 'node') + '/node.css')
1676 1677 1678 1679
1680 -def last_changed(nid):
1681 node = lib_database.fetch_object(lib_database.query(\ 1682 'SELECT changed FROM {node} WHERE nid = %d', nid)) 1683 return (node.changed)
1684 1685 1686
1687 -def revision_list(node):
1688 """ 1689 Return a list of all the existing revision numbers. 1690 """ 1691 revisions = [] 1692 result = lib_database.query(\ 1693 'SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, ' + \ 1694 'r.timestamp, u.name FROM {node_revisions} r ' + \ 1695 'LEFT JOIN {node} n ON n.vid = r.vid ' + \ 1696 'INNER JOIN {users} u ON u.uid = r.uid ' + \ 1697 'WHERE r.nid = %d ORDER BY r.timestamp DESC', node.nid) 1698 while True: 1699 revision = lib_database.fetch_object(result) 1700 if not revision: 1701 break 1702 revisions[revision.vid] = revision 1703 return revisions
1704 1705 1706 1707
1708 -def hook_block(op = 'list', delta = ''):
1709 """ 1710 Implementation of hook_block(). 1711 """ 1712 if (op == 'list'): 1713 blocks['syndicate']['info'] = lib_common.t('Syndicate') 1714 # Not worth caching. 1715 blocks['syndicate']['cache'] = BLOCK_NO_CACHE 1716 return blocks 1717 elif (op == 'view'): 1718 block['subject'] = lib_common.t('Syndicate') 1719 block['content'] = lib_theme.theme('feed_icon', \ 1720 url('rss.xml'), lib_common.t('Syndicate')) 1721 return block
1722 1723 1724
1725 -def feed(nids = False, channel = []):
1726 """ 1727 A generic function for generating RSS feeds from a set of nodes. 1728 1729 @param nids 1730 An array of node IDs (nid). Defaults to False so empty feeds can be 1731 generated with passing an empty array, if no items are to be added 1732 to the feed. 1733 @param channel 1734 An associative array containing title, link, description and other keys. 1735 The link should be an absolute URL. 1736 """ 1737 if (nids == False): 1738 nids = [] 1739 result = lib_database.query_range(lib_database.rewrite_sql(\ 1740 'SELECT n.nid, n.created FROM {node} n ' + \ 1741 'WHERE n.promote = 1 AND n.status = 1 ORDER BY n.created DESC'), 0, \ 1742 lib_bootstrap.variable_get('feed_default_items', 10)) 1743 while True: 1744 row = lib_database.fetch_object(result) 1745 nids.append( row.nid ) 1746 item_length = lib_bootstrap.variable_get('feed_item_length', 'teaser') 1747 namespaces = {'xmlns:dc' : 'http://purl.org/dc/elements/1.1/'} 1748 items = '' 1749 for nid in nids: 1750 # Load the specified node: 1751 item = load(nid) 1752 item.build_mode = NODE_BUILD_RSS 1753 item.link = url("node/nid", {'absolute' : True}) 1754 if (item_length != 'title'): 1755 teaser = (True if (item_length == 'teaser') else False) 1756 # Filter and prepare node teaser 1757 if (hook(item, 'view')): 1758 item = invoke(item, 'view', teaser, False) 1759 else: 1760 item = node_prepare(item, teaser) 1761 # Allow modules to change node.teaser before viewing. 1762 invoke_nodeapi(item, 'view', teaser, False) 1763 # Allow modules to add additional item fields and/or modify item 1764 extra = invoke_nodeapi(item, 'rss item') 1765 extra = php.array_merge(extra, \ 1766 ({'key' : 'pubDate', 'value' : php.gmdate('r', item.created)}, \ 1767 {'key' : 'dc:creator', 'value' : item.name}, \ 1768 {'key' : 'guid', 'value' : item.nid + ' at ' + base_url, \ 1769 'attributes' : {'isPermaLink' : 'False'}})) 1770 for element in extra: 1771 if (php.isset(element, 'namespace')): 1772 namespaces = php.array_merge(namespaces, element['namespace']) 1773 # Prepare the item description 1774 if item_length == 'fulltext': 1775 item_text = item.body 1776 elif item_length == 'teaser': 1777 item_text = item.teaser 1778 if (not empty(item.readmore)): 1779 item_text += '<p>' + l(lib_common.t('read more'), \ 1780 'node/' + item.nid, {'absolute' : True, 'attributes' : \ 1781 {'target' : '_blank'}}) + '</p>' 1782 elif item_length == 'title': 1783 item_text = '' 1784 items += format_rss_item(item.title, item.link, item_text, extra) 1785 channel_defaults = { 1786 'version' : '2.0', 1787 'title' : lib_bootstrap.variable_get('site_name', 'Drupal'), 1788 'link' : lib_appglobals.base_url, 1789 'description' : lib_bootstrap.variable_get('site_mission', ''), 1790 'language' : lib_appglobals.language.language 1791 } 1792 channel = php.array_merge(channel_defaults, channel) 1793 output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 1794 output += "<rss version=\"" + channel["version"] + "\" xml:base=\"" + \ 1795 lib_appglobals.base_url + "\" " + drupal_attributes(namespaces) + \ 1796 ">\n" 1797 output += format_rss_channel(channel['title'], channel['link'], \ 1798 channel['description'], items, channel['language']) 1799 output += "</rss>\n" 1800 drupal_set_header('Content-Type: application/rss+xml; charset=utf-8') 1801 print output
1802 1803
1804 -def page_default():
1805 """ 1806 Menu callback; Generate a listing of promoted nodes. 1807 """ 1808 result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10)) 1809 output = '' 1810 num_rows = False 1811 while True: 1812 node = lib_database.fetch_object(result) 1813 if not node: 1814 break 1815 output += view(load(node.nid), 1) 1816 num_rows = True 1817 if (num_rows): 1818 feed_url = url('rss.xml', {'absolute' : True}) 1819 drupal_add_feed(feed_url, lib_bootstrap.variable_get('site_name', \ 1820 'Drupal') + ' ' + t('RSS')) 1821 output += lib_theme.theme('pager', None, \ 1822 lib_bootstrap.variable_get('default_nodes_main', 10)) 1823 else: 1824 default_message = '<h1 class="title">' + lib_common.t(\ 1825 'Welcome to your new Drupal website! ') + '</h1>' 1826 default_message += '<p>' + lib_common.t(\ 1827 'Please follow these steps to set up and start using your website:') + \ 1828 '</p>' 1829 default_message += '<ol>' 1830 default_message += '<li>' + lib_common.t(\ 1831 '<strong>Configure your website</strong> Once logged in, visit ' + \ 1832 'the <a href="@admin">administration section</a>, where you can ' + \ 1833 '<a href="@config">customize and configure</a> all aspects of ' + \ 1834 'your website.', {'@admin' : url('admin'), \ 1835 '@config' : url('admin/settings')}) + '</li>' 1836 default_message += '<li>' + lib_common.t(\ 1837 '<strong>Enable additional functionality</strong> ' + \ 1838 'Next, visit the <a href="@modules">module list</a> and ' + \ 1839 'enable features which suit your specific needs. '+ \ 1840 'You can find additional modules in the <a href="@download_modules">' + \ 1841 'Drupal modules download section</a>.', \ 1842 {'@modules' : url('admin/build/modules'), \ 1843 '@download_modules' : 'http://drupal.org/project/modules'}) + '</li>' 1844 default_message += '<li>' + lib_common.t(\ 1845 '<strong>Customize your website design</strong> To change the ' + \ 1846 '"look and feel" of your website, visit the <a href="@themes">' + \ 1847 'themes section</a> + You may choose from one of the included ' + \ 1848 'themes or download additional themes from the ' + \ 1849 '<a href="@download_themes">Drupal themes download section</a>.', \ 1850 {'@themes' : url('admin/build/themes'), \ 1851 '@download_themes' : 'http://drupal.org/project/themes'}) + '</li>' 1852 default_message += '<li>' + lib_common.t(\ 1853 '<strong>Start posting content</strong> Finally, you can ' + \ 1854 '<a href="@content">create content</a> for your website. ' + \ 1855 'This message will disappear once you have promoted a post ' + \ 1856 'to the front page.', {'@content' : url('node/add')}) + '</li>' 1857 default_message += '</ol>' 1858 default_message += '<p>' + lib_common.t(\ 1859 'For more information, please refer to the <a href="@help">' + \ 1860 'help section</a>, or the <a href="@handbook">online Drupal ' + \ 1861 'handbooks</a> + You may also post at the <a href="@forum">' + \ 1862 'Drupal forum</a>, or view the wide range of ' + \ 1863 '<a href="@support">other support options</a> available.', \ 1864 {'@help' : url('admin/help'), '@handbook' : \ 1865 'http://drupal.org/handbooks', '@forum' : 'http://drupal.org/forum', \ 1866 '@support' : 'http://drupal.org/support'}) + '</p>' 1867 output = '<div id="first-time">' + default_message + '</div>' 1868 drupal_set_title('') 1869 return output
1870 1871 1872
1873 -def page_view(node, cid = None):
1874 """ 1875 Menu callback; view a single node. 1876 """ 1877 drupal_set_title(check_plain(node.title)) 1878 return show(node, cid)
1879 1880 1881
1882 -def hook_update_index():
1883 """ 1884 Implementation of hook_update_index(). 1885 """ 1886 limit = int(lib_bootstrap.variable_get('search_cron_limit', 100)) 1887 # Store the maximum possible comments per thread (used for ranking by 1888 # reply count) 1889 lib_bootstrap.variable_set('node_cron_comments_scale', \ 1890 1.0 / php.max(1, lib_database.result(lib_database.query(\ 1891 'SELECT MAX(comment_count) FROM {node_comment_statistics}')))) 1892 lib_bootstrap.variable_set('node_cron_views_scale', \ 1893 1.0 / php.max(1, lib_database.result(lib_database.query(\ 1894 'SELECT MAX(totalcount) FROM {node_counter}')))) 1895 result = lib_database.query_range(\ 1896 "SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d " + \ 1897 "ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS None OR " + \ 1898 "d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, limit) 1899 while True: 1900 node = db_fetch_object(result) 1901 if not node: 1902 break 1903 _index_node(node)
1904 1905 1906
1907 -def _index_node(node):
1908 """ 1909 Index a single node. 1910 @param node 1911 The node to index. 1912 """ 1913 node = load(node.nid) 1914 # save the changed time of the most recent indexed node, for the 1915 # search results half-life calculation 1916 lib_bootstrap.variable_set('node_cron_last', node.changed) 1917 # Build the node body. 1918 node.build_mode = NODE_BUILD_SEARCH_INDEX 1919 node = build_content(node, False, False) 1920 node.body = drupal_render(node.content) 1921 text = '<h1>' + check_plain(node.title) + '</h1>' + node.body 1922 # Fetch extra data normally not visible 1923 extra = invoke_nodeapi(node, 'update index') 1924 for t in extra: 1925 text += t 1926 # Update index 1927 search_index(node.nid, 'node', text)
1928 1929
1930 -def hook_form_alter(form, form_state, form_id):
1931 """ 1932 Implementation of hook_form_alter(). 1933 """ 1934 php.Reference.check(form) 1935 # Advanced node search form 1936 if (form_id == 'search_form' and \ 1937 form['module']['#value'] == 'node' and \ 1938 lib_plugin.plugins['user'].access('use advanced search')): 1939 # Keyword boxes: 1940 form['advanced'] = { 1941 '#type' : 'fieldset', 1942 '#title' : lib_common.t('Advanced search'), 1943 '#collapsible' : True, 1944 '#collapsed' : True, 1945 '#attributes' : {'class' : 'search-advanced'} 1946 } 1947 form['advanced']['keywords'] = { 1948 '#prefix' : '<div class="criterion">', 1949 '#suffix' : '</div>' 1950 } 1951 form['advanced']['keywords']['or'] = { 1952 '#type' : 'textfield', 1953 '#title' : lib_common.t('Containing any of the words'), 1954 '#size' : 30, 1955 '#maxlength' : 255 1956 } 1957 form['advanced']['keywords']['phrase'] = { 1958 '#type' : 'textfield', 1959 '#title' : lib_common.t('Containing the phrase'), 1960 '#size' : 30, 1961 '#maxlength' : 255 1962 } 1963 form['advanced']['keywords']['negative'] = { 1964 '#type' : 'textfield', 1965 '#title' : lib_common.t('Containing none of the words'), 1966 '#size' : 30, 1967 '#maxlength' : 255 1968 } 1969 # Taxonomy box: 1970 taxonomy = lib_plugin.invoke('taxonomy', 'form_all', 1) 1971 if taxonomy: 1972 form['advanced']['category'] = { 1973 '#type' : 'select', 1974 '#title' : lib_common.t('Only in the category(s)'), 1975 '#prefix' : '<div class="criterion">', 1976 '#size' : 10, 1977 '#suffix' : '</div>', 1978 '#options' : taxonomy, 1979 '#multiple' : True 1980 } 1981 # Node types: 1982 types = php.array_map('check_plain', get_types('names')) 1983 form['advanced']['type'] = { 1984 '#type' : 'checkboxes', 1985 '#title' : lib_common.t('Only of the type(s)'), 1986 '#prefix' : '<div class="criterion">', 1987 '#suffix' : '</div>', 1988 '#options' : types 1989 } 1990 form['advanced']['submit'] = { 1991 '#type' : 'submit', 1992 '#value' : t('Advanced search'), 1993 '#prefix' : '<div class="action">', 1994 '#suffix' : '</div>' 1995 } 1996 # Languages: 1997 language_options = [] 1998 for key,object_ in language_list('language').items(): 1999 language_options[key] = object_.name 2000 if (php.count(language_options) > 1): 2001 form['advanced']['language'] = { 2002 '#type' : 'checkboxes', 2003 '#title' : lib_common.t('Languages'), 2004 '#prefix' : '<div class="criterion">', 2005 '#suffix' : '</div>', 2006 '#options' : language_options 2007 } 2008 form['#validate'].append( 'node_search_validate' )
2009 2010
2011 -def search_validate(form, form_state):
2012 """ 2013 Form API callback for the search form. Registered in node_form_alter(). 2014 """ 2015 php.Reference.check(form_state) 2016 # Initialise using any existing basic search keywords. 2017 keys = form_state['values']['processed_keys'] 2018 # Insert extra restrictions into the search keywords string. 2019 if (php.isset(form_state['values'], 'type') and \ 2020 php.is_array(form_state['values']['type'])): 2021 # Retrieve selected types - Forms API sets the value of unselected 2022 # checkboxes to 0. 2023 form_state['values']['type'] = php.array_filter(\ 2024 form_state['values']['type']) 2025 if (php.count(form_state['values']['type'])): 2026 keys = search_query_insert(keys, 'type', \ 2027 php.implode(',', php.array_keys(form_state['values']['type']))) 2028 if (php.isset(form_state['values'], 'category') and \ 2029 php.is_array(form_state['values']['category'])): 2030 keys = search_query_insert(keys, 'category', \ 2031 php.implode(',', form_state['values']['category'])) 2032 if (php.isset(form_state['values']['language']) and \ 2033 php.is_array(form_state['values']['language'])): 2034 keys = search_query_insert(keys, 'language', php.implode(',', \ 2035 php.array_filter(form_state['values']['language']))) 2036 if (form_state['values']['or'] != ''): 2037 if (php.preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' + \ 2038 form_state['values']['or'], matches)): 2039 keys += ' ' + php.implode(' OR ', matches[1]) 2040 if (form_state['values']['negative'] != ''): 2041 if (php.preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' + \ 2042 form_state['values']['negative'], matches)): 2043 keys += ' -' + php.implode(' -', matches[1]) 2044 if (form_state['values']['phrase'] != ''): 2045 keys += ' "' + php.str_replace('"', ' ', \ 2046 form_state['values']['phrase']) + '"' 2047 if (not php.empty(keys)): 2048 form_set_value(form['basic']['inline']['processed_keys'], trim(keys),\ 2049 form_state)
2050 2051
2052 -def access(op, node, account = None):
2053 """ 2054 @defgroup node_access Node access rights 2055 @{ 2056 The node access system determines who can do what to which nodes. 2057 2058 In determining access rights for a node, node_access() first checks 2059 whether the user has the "administer nodes" permission. Such users have 2060 unrestricted access to all nodes. Then the node module's hook_access() 2061 is called, and a True or False return value will grant or deny access. 2062 This allows, for example, the blog module to always grant access to the 2063 blog author, and for the book module to always deny editing access to 2064 PHP pages. 2065 2066 If node module does not intervene (returns None), then the 2067 node_access table is used to determine access. All node access 2068 modules are queried using hook_node_grants() to assemble a list of 2069 "grant IDs" for the user. This list is compared against the table. 2070 If any row contains the node ID in question (or 0, which stands for "all 2071 nodes"), one of the grant IDs returned, and a value of True for the 2072 operation in question, then access is granted. Note that this table is a 2073 list of grants; any matching row is sufficient to grant access to the 2074 node. 2075 2076 In node listings, the process above is followed except that 2077 hook_access() is not called on each node for performance reasons and for 2078 proper functioning of the pager system. When adding a node listing to your 2079 module, be sure to use db_rewrite_sql() to add 2080 the appropriate clauses to your query for access checks. 2081 2082 To see how to write a node access module of your own, see 2083 node_access_example.module. 2084 2085 Determine whether the current user may perform the given operation on the 2086 specified node. 2087 2088 @param op 2089 The operation to be performed on the node. Possible values are: 2090 - "view" 2091 - "update" 2092 - "delete" 2093 - "create" 2094 @param node 2095 The node object (or node array) on which the operation is to be performed, 2096 or node type (e.g. 'forum') for "create" operation. 2097 @param account 2098 Optional, a user object representing the user for whom the operation is to 2099 be performed. Determines access for a user other than the current user. 2100 @return 2101 True if the operation may be performed. 2102 """ 2103 if (not node): 2104 return False 2105 # Convert the node to an object if necessary: 2106 if (op != 'create'): 2107 node = php.object_(node) 2108 # If no user object is supplied, the access check is for the current user. 2109 if (empty(account)): 2110 account = lib_appglobals.user 2111 # If the node is in a restricted format, disallow editing. 2112 if (op == 'update' and not filter_access(node.format)): 2113 return False 2114 if (lib_plugin.plugins['user'].access('administer nodes', account)): 2115 return True 2116 if (not lib_plugin.plugins['user'].access('access content', account)): 2117 return False 2118 # Can't use node_invoke('access', node), because the access hook takes the 2119 # op parameter before the node parameter. 2120 plugin = get_types('plugin', node) 2121 if (module == 'node'): 2122 plugin = 'node_content'; # Avoid function name collisions. 2123 access = lib_plugin.invoke(plugin, 'access', op, node, account) 2124 if (not is_none(access)): 2125 return access 2126 # If the module did not override the access rights, use those set in the 2127 # node_access table. 2128 if (op != 'create' and node.nid and node.status): 2129 grants = [] 2130 for realm,gids in access_grants(op, account).items(): 2131 for gid in gids: 2132 grants.append( "(gid = gid AND realm = 'realm')" ) 2133 grants_sql = '' 2134 if (php.count(grants)): 2135 grants_sql = 'AND (' + php.implode(' OR ', grants) + ')' 2136 sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) grants_sql AND grant_op >= 1" 2137 result = lib_database.query(sql, node.nid) 2138 return (lib_database.result(result)) 2139 # Let authors view their own nodes. 2140 if (op == 'view' and account.uid == node.uid and account.uid != 0): 2141 return True 2142 return False
2143 2144 2145
2146 -def _access_join_sql(node_alias = 'n', node_access_alias = 'na'):
2147 """ 2148 Generate an SQL join clause for use in fetching a node listing. 2149 2150 @param node_alias 2151 If the node table has been given an SQL alias other than the default 2152 "n", that must be passed here. 2153 @param node_access_alias 2154 If the node_access table has been given an SQL 2155 alias other than the default 2156 "na", that must be passed here. 2157 @return 2158 An SQL join clause. 2159 """ 2160 if (lib_plugin.plugins['user'].access('administer nodes')): 2161 return '' 2162 return 'INNER JOIN {node_access} ' + \ 2163 node_access_alias + ' ON ' + node_access_alias + '.nid = ' + \ 2164 node_alias + '.nid'
2165 2166 2167
2168 -def _access_where_sql(op = 'view', node_access_alias = 'na', account = None):
2169 """ 2170 Generate an SQL where clause for use in fetching a node listing. 2171 2172 @param op 2173 The operation that must be allowed to return a node. 2174 @param node_access_alias 2175 If the node_access table has been given an SQL alias other than the default 2176 "na", that must be passed here. 2177 @param account 2178 The user object for the user performing the operation. If omitted, the 2179 current user is used. 2180 @return 2181 An SQL where clause. 2182 """ 2183 if (lib_plugin.plugins['user'].access('administer nodes')): 2184 return 2185 grants = [] 2186 for realm,gids in node_access_grants(op, account).items(): 2187 for gid in gids: 2188 grants.append( "(node_access_alias.gid = gid AND " + \ 2189 "node_access_alias.realm = 'realm')" ) 2190 grants_sql = '' 2191 if (php.count(grants)): 2192 grants_sql = 'AND (' + php.implode(' OR ', grants) + ')' 2193 sql = "node_access_alias.grant_op >= 1 grants_sql" 2194 return sql
2195 2196 2197
2198 -def access_grants(op, account = None):
2199 """ 2200 Fetch an array of permission IDs granted to the given user ID. 2201 2202 The implementation here provides only the universal "all" grant. A node 2203 access module should implement hook_node_grants() to provide a grant 2204 list for the user. 2205 2206 @param op 2207 The operation that the user is trying to perform. 2208 @param account 2209 The user object for the user performing the operation. If omitted, the 2210 current user is used. 2211 @return 2212 An associative array in which the keys are realms, and the values are 2213 arrays of grants for those realms. 2214 """ 2215 if (account is None): 2216 account = lib_appglobals.user 2217 return php.array_merge({'all' : (0,)},\ 2218 lib_plugin.invoke_all('node_grants', account, op))
2219 2220 2221
2222 -def access_view_all_nodes():
2223 """ 2224 Determine whether the user has a global viewing grant for all nodes. 2225 """ 2226 php.static(access_view_all_nodes, 'access', None) 2227 if (access_view_all_nodes.access is None): 2228 grants = [] 2229 for realm,gids in access_grants('view').items(): 2230 for gid in gids: 2231 grants.append("(gid = gid AND realm = 'realm')" ) 2232 grants_sql = '' 2233 if (php.count(grants)): 2234 grants_sql = 'AND (' + php.implode(' OR ', grants) + ')' 2235 sql = "SELECT COUNT(*) FROM {node_access} " + \ 2236 "WHERE nid = 0 grants_sql AND grant_view >= 1" 2237 result = lib_database.query(sql) 2238 access = lib_database.result(result) 2239 return access
2240 2241 2242
2243 -def hook_db_rewrite_sql(query, primary_table, primary_field):
2244 """ 2245 Implementation of hook_db_rewrite_sql(). 2246 """ 2247 if (primary_field == 'nid' and not access_view_all_nodes()): 2248 return_ = {} 2249 return_['join'] = _access_join_sql(primary_table) 2250 return_['where'] = _access_where_sql() 2251 return_['distinct'] = 1 2252 return return_ 2253 else: 2254 return None
2255 2256
2257 -def access_acquire_grants(node):
2258 """ 2259 This function will call module invoke to get a list of grants and then 2260 write them to the database. It is called at node save, and should be 2261 called by modules whenever something other than a node_save causes 2262 the permissions on a node to change. 2263 2264 This function is the only function that should write to the node_access 2265 table. 2266 2267 @param node 2268 The node to acquire grants for. 2269 """ 2270 grants = lib_plugin.invoke_all('node_access_records', node) 2271 if (empty(grants)): 2272 grants.append( {'realm' : 'all', 'gid' : 0, 'grant_view' : 1, \ 2273 'grant_update' : 0, 'grant_delete' : 0} ) 2274 else: 2275 # retain grants by highest priority 2276 grant_by_priority = [] 2277 for g in grants: 2278 grant_by_priority[php.intval(g['priority'])].append(g) 2279 php.krsort(grant_by_priority) 2280 grants = php.array_shift(grant_by_priority) 2281 access_write_grants(node, grants)
2282 2283 2284
2285 -def access_write_grants(node, grants, realm = None, delete = True):
2286 """ 2287 This function will write a list of grants to the database, deleting 2288 any pre-existing grants. If a realm is provided, it will only 2289 delete grants from that realm, but it will always delete a grant 2290 from the 'all' realm. Modules which utilize node_access can 2291 use this function when doing mass updates due to widespread permission 2292 changes. 2293 2294 @param node 2295 The node being written to. All that is necessary is that it contain 2296 a nid. 2297 @param grants 2298 A list of grants to write. Each grant is an array that must contain the 2299 following keys: realm, gid, grant_view, grant_update, grant_delete. 2300 The realm is specified by a particular module; the gid is as well, and 2301 is a module-defined id to define grant privileges. each grant_* field 2302 is a boolean value. 2303 @param realm 2304 If provided, only read/write grants for that realm. 2305 @param delete 2306 If False, do not delete records. This is only for optimization purposes, 2307 and assumes the caller has already performed a mass delete of some form. 2308 """ 2309 if (delete): 2310 query = 'DELETE FROM {node_access} WHERE nid = %d' 2311 if (realm): 2312 query += " AND realm in ('%s', 'all')" 2313 lib_database.query(query, node.nid, realm) 2314 # Only perform work when node_access modules are active. 2315 if (php.count(lib_plugin.implements('node_grants'))): 2316 for grant in grants: 2317 if (realm and realm != grant['realm']): 2318 continue 2319 # Only write grants; denies are implicit. 2320 if (grant['grant_view'] or grant['grant_update'] or\ 2321 grant['grant_delete']): 2322 lib_database.query("INSERT INTO {node_access} (nid, " + \ 2323 "realm, gid, grant_view, grant_update, grant_delete) " + \ 2324 "VALUES (%d, '%s', %d, %d, %d, %d)", \ 2325 node.nid, grant['realm'], grant['gid'], grant['grant_view'], \ 2326 grant['grant_update'], grant['grant_delete'])
2327 2328 2329
2330 -def access_needs_rebuild(rebuild = None):
2331 """ 2332 Flag / unflag the node access grants for rebuilding, or read the current 2333 value of the flag. 2334 2335 When the flag is set, a message is displayed to users with 'access 2336 administration pages' permission, pointing to the 'rebuild' confirm form. 2337 This can be used as an alternative to direct node_access_rebuild calls, 2338 allowing administrators to decide when they want to perform the actual 2339 (possibly time consuming) rebuild. 2340 When unsure the current user is an adminisrator, node_access_rebuild 2341 should be used instead. 2342 2343 @param rebuild 2344 (Optional) The boolean value to be written. 2345 @return 2346 (If no value was provided for rebuild) The current value of the flag. 2347 """ 2348 if (rebuild is None): 2349 return lib_bootstrap.variable_get('node_access_needs_rebuild', False) 2350 elif (rebuild): 2351 lib_bootstrap.variable_set('node_access_needs_rebuild', True) 2352 else: 2353 lib_bootstrap.variable_del('node_access_needs_rebuild')
2354 2355 2356
2357 -def access_rebuild(batch_mode = False):
2358 """ 2359 Rebuild the node access database. This is occasionally needed by modules 2360 that make system-wide changes to access levels. 2361 2362 When the rebuild is required by an admin-triggered action (e.g module 2363 settings form), calling node_access_needs_rebuild(True) instead of 2364 node_access_rebuild() lets the user perform his changes and actually 2365 rebuild only once he is done. 2366 2367 Note : As of Drupal 6, node access modules are not required to (and actually 2368 should not) call node_access_rebuild() in hook_enable/disable anymore. 2369 2370 @see node_access_needs_rebuild() 2371 2372 @param batch_mode 2373 Set to True to process in 'batch' mode, spawning processing over several 2374 HTTP requests (thus avoiding the risk of PHP timeout if the site has a 2375 large number of nodes). 2376 hook_update_N and any form submit handler are safe contexts to use the 2377 'batch mode'. Less decidable cases (such as calls from hook_user, 2378 hook_taxonomy, hook_node_type...) might consider using the non-batch mode. 2379 """ 2380 lib_database.query("DELETE FROM {node_access}") 2381 # Only recalculate if the site is using a node_access module. 2382 if (php.count(lib_plugin.implements('node_grants'))): 2383 if (batch_mode): 2384 batch = { 2385 'title' : lib_common.t('Rebuilding content access permissions'), 2386 'operations' : ( 2387 ('_access_rebuild_batch_operation', tuple()), 2388 ), 2389 'finished' : '_access_rebuild_batch_finished' 2390 } 2391 batch_set(batch) 2392 else: 2393 # If not in 'safe mode', increase the maximum execution time. 2394 # if (not ini_get('safe_mode')): 2395 # set_time_limit(240) 2396 # } 2397 result = lib_database.query("SELECT nid FROM {node}") 2398 while True: 2399 node = lib_datbase.fetch_object(result) 2400 if not node: 2401 break 2402 loaded_node = load(node.nid, None, True) 2403 # To preserve database integrity, only aquire grants if the node 2404 # loads successfully. 2405 if (not php.empty(loaded_node)): 2406 access_acquire_grants(loaded_node) 2407 else: 2408 # Not using any node_access modules. Add the default grant. 2409 lib_database.query(\ 2410 "INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)") 2411 if (batch is None): 2412 drupal_set_message(lib_common.t('Content permissions have been rebuilt.')) 2413 access_needs_rebuild(False) 2414 cache_clear_all()
2415 2416 2417
2418 -def _access_rebuild_batch_operation(context):
2419 """ 2420 Batch operation for node_access_rebuild_batch. 2421 2422 This is a mutlistep operation : we go through all nodes by packs of 20. 2423 The batch processing engine interrupts processing and sends progress 2424 feedback after 1 second execution time. 2425 """ 2426 php.Reference.check(context) 2427 if (php.empty(context['sandbox'])): 2428 # Initiate multistep processing. 2429 context['sandbox']['progress'] = 0 2430 context['sandbox']['current_node'] = 0 2431 context['sandbox']['max'] = lib_database.result(\ 2432 lib_database.query('SELECT COUNT(DISTINCT nid) FROM {node}')) 2433 # Process the next 20 nodes. 2434 limit = 20 2435 result = lib_database.query_range(\ 2436 "SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", \ 2437 context['sandbox']['current_node'], 0, limit) 2438 while True: 2439 row = lib_database.fetch_array(result) 2440 if not row: 2441 break 2442 loaded_node = load(row['nid'], None, True) 2443 # To preserve database integrity, only aquire grants if the node 2444 # loads successfully. 2445 if (not php.empty(loaded_node)): 2446 access_acquire_grants(loaded_node) 2447 context['sandbox']['progress'] += 1 2448 context['sandbox']['current_node'] = loaded_node.nid 2449 # Multistep processing : report progress. 2450 if (context['sandbox']['progress'] != context['sandbox']['max']): 2451 context['finished'] = \ 2452 context['sandbox']['progress'] / context['sandbox']['max']
2453 2454 2455
2456 -def _access_rebuild_batch_finished(success, results, operations):
2457 """ 2458 Post-processing for node_access_rebuild_batch. 2459 """ 2460 if (success): 2461 drupal_set_message(t('The content access permissions have been rebuilt.')) 2462 access_needs_rebuild(False) 2463 else: 2464 drupal_set_message(t('The content access permissions have not been ' + \ 2465 'properly rebuilt.'), 'error') 2466 cache_clear_all()
2467 2468 2469
2470 -def hook_content_access(op, node, account):
2471 """ 2472 @} End of "defgroup node_access". 2473 2474 2475 @defgroup node_content Hook implementations for user-created content types. 2476 @{ 2477 2478 2479 Implementation of hook_access(). 2480 2481 Named so as not to conflict with node_access() 2482 """ 2483 type_ = (node if php.is_string(node) else (node['type'] if \ 2484 php.is_array(node) else node.type_)) 2485 if (op == 'create'): 2486 return lib_plugin.plugins['user'].access(\ 2487 'create ' + type_ + ' content', account) 2488 if (op == 'update'): 2489 if (lib_plugin.plugins['user'].access(\ 2490 'edit any ' + type_ + ' content', account) or \ 2491 (lib_plugin.plugins['user'].access('edit own ' + \ 2492 type_ + ' content', account) and (account.uid == node.uid))): 2493 return True 2494 if (op == 'delete'): 2495 if (lib_plugin.plugins['user'].access('delete any ' + type_ + \ 2496 ' content', account) or (lib_plugin.plugins['user'].access(\ 2497 'delete own ' + type_ + ' content', account) and \ 2498 (account.uid == node.uid))): 2499 return True
2500 2501
2502 -def hook_content_form(node, form_state):
2503 """ 2504 Implementation of hook_form(). 2505 """ 2506 type_ = get_types('type', node) 2507 form = [] 2508 if (type_.has_title): 2509 form['title'] = { 2510 '#type' : 'textfield', 2511 '#title' : check_plain(type_.title_label), 2512 '#required' : True, 2513 '#default_value' : node.title, 2514 '#maxlength' : 255, 2515 '#weight' : -5 2516 } 2517 if (type_.has_body): 2518 form['body_field'] = body_field(node, type_.body_label, \ 2519 type_.min_word_count) 2520 return form
2521 2522 2523
2524 -def hook_forms():
2525 """ 2526 Implementation of hook_forms(). All node forms share the same form handler 2527 """ 2528 forms = [] 2529 types = get_types() 2530 if types: 2531 for type_ in php.array_keys(types): 2532 forms[type_ + '_node_form']['callback'] = 'node_form' 2533 return forms
2534 2535 2536
2537 -def theme_submitted(node):
2538 """ 2539 Format the "Submitted by username on date/time" for each node 2540 2541 @ingroup themeable 2542 """ 2543 return lib_common.t('Submitted by not username on @datetime', { 2544 'not username' : lib_theme.theme('username', node), 2545 '@datetime' : format_date(node.created), 2546 })
2547 2548 2549
2550 -def hook_hook_info():
2551 """ 2552 Implementation of hook_hook_info(). 2553 """ 2554 return { 2555 'node' : { 2556 'nodeapi' : { 2557 'presave' : { 2558 'runs when' : lib_common.t('When either saving a new post ' + \ 2559 'or updating an existing post') 2560 }, 2561 'insert' : { 2562 'runs when' : lib_common.t('After saving a new post') 2563 }, 2564 'update' : { 2565 'runs when' : lib_common.t('After saving an updated post') 2566 }, 2567 'delete' : { 2568 'runs when' : lib_common.t('After deleting a post') 2569 }, 2570 'view' : { 2571 'runs when' : lib_common.t('When content is viewed by an ' + \ 2572 'authenticated user') 2573 }, 2574 }, 2575 }, 2576 }
2577 2578
2579 -def hook_action_info():
2580 """ 2581 Implementation of hook_action_info(). 2582 """ 2583 return { 2584 'node_publish_action' : { 2585 'type' : 'node', 2586 'description' : lib_common.t('Publish post'), 2587 'configurable' : False, 2588 'behavior' : ('changes_node_property',), 2589 'hooks' : { 2590 'nodeapi' : ('presave',), 2591 'comment' : ('insert', 'update'), 2592 } 2593 }, 2594 'node_unpublish_action' : { 2595 'type' : 'node', 2596 'description' : lib_common.t('Unpublish post'), 2597 'configurable' : False, 2598 'behavior' : ('changes_node_property',), 2599 'hooks' : { 2600 'nodeapi' : ('presave',), 2601 'comment' : ('delete', 'insert', 'update') 2602 } 2603 }, 2604 'node_make_sticky_action' : { 2605 'type' : 'node', 2606 'description' : lib_common.t('Make post sticky'), 2607 'configurable' : False, 2608 'behavior' : ('changes_node_property',), 2609 'hooks' : { 2610 'nodeapi' : ('presave',), 2611 'comment' : ('insert', 'update'), 2612 }, 2613 }, 2614 'node_make_unsticky_action' : { 2615 'type' : 'node', 2616 'description' : lib_common.t('Make post unsticky'), 2617 'configurable' : False, 2618 'behavior' : ('changes_node_property',), 2619 'hooks' : { 2620 'nodeapi' : ('presave',), 2621 'comment' : ('delete', 'insert', 'update'), 2622 }, 2623 }, 2624 'node_promote_action' : { 2625 'type' : 'node', 2626 'description' : lib_common.t('Promote post to front page'), 2627 'configurable' : False, 2628 'behavior' : ('changes_node_property',), 2629 'hooks' : { 2630 'nodeapi' : ('presave',), 2631 'comment' : ('insert', 'update'), 2632 } 2633 }, 2634 'node_unpromote_action' : { 2635 'type' : 'node', 2636 'description' : lib_common.t('Remove post from front page'), 2637 'configurable' : False, 2638 'behavior' : ('changes_node_property',), 2639 'hooks' : { 2640 'nodeapi' : ('presave',), 2641 'comment' : ('delete', 'insert', 'update'), 2642 }, 2643 }, 2644 'node_assign_owner_action' : { 2645 'type' : 'node', 2646 'description' : lib_common.t('Change the author of a post'), 2647 'configurable' : True, 2648 'behavior' : ('changes_node_property',), 2649 'hooks' : { 2650 'any' : True, 2651 'nodeapi' : ('presave',), 2652 'comment' : ('delete', 'insert', 'update'), 2653 }, 2654 }, 2655 'node_save_action' : { 2656 'type' : 'node', 2657 'description' : lib_common.t('Save post'), 2658 'configurable' : False, 2659 'hooks' : { 2660 'comment' : ('delete', 'insert', 'update'), 2661 }, 2662 }, 2663 'node_unpublish_by_keyword_action' : { 2664 'type' : 'node', 2665 'description' : lib_common.t('Unpublish post containing keyword(s)'), 2666 'configurable' : True, 2667 'hooks' : { 2668 'nodeapi' : ('presave', 'insert', 'update'), 2669 } 2670 } 2671 }
2672 2673 2674 2675
2676 -def publish_action(node, context = []):
2677 """ 2678 Implementation of a Drupal action. 2679 Sets the status of a node to 1, meaning published. 2680 """ 2681 php.Reference.check(node) 2682 node.status = 1 2683 watchdog('action', 'Set @type %title to published.', \ 2684 {'@type' : get_types('name', node), '%title' : node.title})
2685 2686 2687
2688 -def node_unpublish_action(node, context = []):
2689 """ 2690 Implementation of a Drupal action. 2691 Sets the status of a node to 0, meaning unpublished. 2692 """ 2693 php.Reference.check(node) 2694 node.status = 0 2695 watchdog('action', 'Set @type %title to unpublished.', \ 2696 {'@type' : get_types('name', node), '%title' : node.title})
2697 2698 2699
2700 -def make_sticky_action(node, context = []):
2701 """ 2702 Implementation of a Drupal action. 2703 Sets the sticky-at-top-of-list property of a node to 1. 2704 """ 2705 php.Reference.check(node) 2706 node.sticky = 1 2707 watchdog('action', 'Set @type %title to sticky.', \ 2708 {'@type' : get_types('name', node), '%title' : node.title})
2709 2710 2711
2712 -def make_unsticky_action(node, context = []):
2713 """ 2714 Implementation of a Drupal action. 2715 Sets the sticky-at-top-of-list property of a node to 0. 2716 """ 2717 php.Reference.check(node) 2718 node.sticky = 0 2719 watchdog('action', 'Set @type %title to unsticky.', \ 2720 {'@type' : get_types('name', node), '%title' : node.title})
2721 2722 2723
2724 -def promote_action(node, context = []):
2725 """ 2726 Implementation of a Drupal action. 2727 Sets the promote property of a node to 1. 2728 """ 2729 php.Reference.check(node) 2730 node.promote = 1 2731 watchdog('action', 'Promoted @type %title to front page.', \ 2732 {'@type' : get_types('name', node), '%title' : node.title})
2733 2734 2735
2736 -def unpromote_action(node, context = []):
2737 """ 2738 Implementation of a Drupal action. 2739 Sets the promote property of a node to 0. 2740 """ 2741 php.Reference.check(node) 2742 node.promote = 0 2743 watchdog('action', 'Removed @type %title from front page.', \ 2744 {'@type' : get_types('name', node), '%title' : node.title})
2745 2746 2747
2748 -def save_action(node):
2749 """ 2750 Implementation of a Drupal action. 2751 Saves a node. 2752 """ 2753 save(node) 2754 watchdog('action', 'Saved @type %title', \ 2755 {'@type' : get_types('name', node), '%title' : node.title})
2756 2757 2758
2759 -def assign_owner_action(node, context):
2760 """ 2761 Implementation of a configurable Drupal action. 2762 Assigns ownership of a node to a user. 2763 """ 2764 php.Reference.check(node) 2765 node.uid = context['owner_uid'] 2766 owner_name = lib_database.result(lib_database.query(\ 2767 "SELECT name FROM {users} WHERE uid = %d", context['owner_uid'])) 2768 watchdog('action', 'Changed owner of @type %title to uid %name.', \ 2769 {'@type' : get_types('type', node), \ 2770 '%title' : node.title, '%name' : owner_name})
2771 2772 2773
2774 -def assign_owner_action_form(context):
2775 description = lib_common.t('The username of the user to which you ' + \ 2776 'would like to assign ownership.') 2777 count = lib_database.result(lib_database.query(\ 2778 "SELECT COUNT(*) FROM {users}")) 2779 owner_name = '' 2780 if (php.isset(context, 'owner_uid')): 2781 owner_name = lib_database.result(lib_datbase.query(\ 2782 "SELECT name FROM {users} WHERE uid = %d", context['owner_uid'])) 2783 # Use dropdown for fewer than 200 users; textbox for more than that. 2784 if (php.intval(count) < 200): 2785 options = [] 2786 result = lib_database.query(\ 2787 "SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name") 2788 while True: 2789 data = lib_database.fetch_object(result) 2790 if not data: 2791 break 2792 options[data.name] = data.name 2793 form['owner_name'] = { 2794 '#type' : 'select', 2795 '#title' : lib_common.t('Username'), 2796 '#default_value' : owner_name, 2797 '#options' : options, 2798 '#description' : description 2799 } 2800 else: 2801 form['owner_name'] = { 2802 '#type' : 'textfield', 2803 '#title' : lib_common.t('Username'), 2804 '#default_value' : owner_name, 2805 '#autocomplete_path' : 'user/autocomplete', 2806 '#size' : '6', 2807 '#maxlength' : '7', 2808 '#description' : description 2809 } 2810 return form
2811 2812 2813
2814 -def assign_owner_action_validate(form, form_state):
2815 count = lib_database.result(lib_database.query(\ 2816 "SELECT COUNT(*) FROM {users} WHERE name = '%s'", \ 2817 form_state['values']['owner_name'])) 2818 if (php.intval(count) != 1): 2819 form_set_error('owner_name', lib_common.t(\ 2820 'Please enter a valid username.'))
2821 2822 2823
2824 -def assign_owner_action_submit(form, form_state):
2825 # Username can change, so we need to store the ID, not the username. 2826 uid = lib_database.result(lib_database.query(\ 2827 "SELECT uid from {users} WHERE name = '%s'", \ 2828 form_state['values']['owner_name'])) 2829 return {'owner_uid' : uid}
2830 2831 2832
2833 -def unpublish_by_keyword_action_form(context):
2834 form['keywords'] = { 2835 '#title' : lib_common.t('Keywords'), 2836 '#type' : 'textarea', 2837 '#description' : lib_common.t('The post will be unpublished if it ' + \ 2838 'contains any of the character sequences above. Use a ' + \ 2839 'comma-separated list of character sequences. Example: funny, ' + \ 2840 'bungee jumping, "Company, Inc." . Character sequences ' + \ 2841 'are case-sensitive.'), 2842 '#default_value' : (drupal_implode_tags(context['keywords']) if \ 2843 php.isset(context, 'keywords') else '') 2844 } 2845 return form
2846 2847 2848 2849
2850 -def unpublish_by_keyword_action_submit(form, form_state):
2851 return {'keywords' : \ 2852 drupal_explode_tags(form_state['values']['keywords'])}
2853 2854 2855
2856 -def unpublish_by_keyword_action(node, context):
2857 """ 2858 Implementation of a configurable Drupal action. 2859 Unpublish a node if it contains a certain string. 2860 2861 @param context 2862 An array providing more information about the context of the call to 2863 this action. 2864 @param comment 2865 A node object. 2866 """ 2867 for keyword in context['keywords']: 2868 if (php.strstr(view(php.clone(node)), keyword) or \ 2869 php.strstr(node.title, keyword)): 2870 node.status = 0 2871 watchdog('action', 'Set @type %title to unpublished.', \ 2872 {'@type' : get_types('name', node), '%title' : node.title}) 2873 break
2874 2875 2876
2877 -def list_permissions(type_):
2878 """ 2879 Helper function to generate standard node permission list for a given type. 2880 2881 @param type 2882 The machine-readable name of the node type. 2883 @return array 2884 An array of permission names and descriptions. 2885 """ 2886 info = get_types('type', type_) 2887 type_ = check_plain(info.type_) 2888 # Build standard list of node permissions for this type. 2889 perms["create type content"] = \ 2890 lib_common.t('Create new %type_name content.', \ 2891 {'%type_name' : info.name}) 2892 perms["delete any type content"] = \ 2893 lib_common.t('Delete any %type_name content, regardless of its author.', \ 2894 {'%type_name' : info.name}) 2895 perms["delete own type content"] = \ 2896 lib_common.t('Delete %type_name content created by the user.', \ 2897 {'%type_name' : info.name}) 2898 perms["edit own type content"] = \ 2899 lib_common.t('Edit %type_name content created by the user.', \ 2900 {'%type_name' : info.name}) 2901 perms["edit any type content"] = \ 2902 lib_common.t('Edit any %type_name content, regardless of its author.', \ 2903 {'%type_name' : info.name}) 2904 return perms
2905