[ Index ] |
|
Code source de b2evolution 2.1.0-beta |
1 <?php 2 /** 3 * This file implements the Item class. 4 * 5 * This file is part of the evoCore framework - {@link http://evocore.net/} 6 * See also {@link http://sourceforge.net/projects/evocms/}. 7 * 8 * @copyright (c)2003-2007 by Francois PLANQUE - {@link http://fplanque.net/} 9 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}. 10 * 11 * {@internal License choice 12 * - If you have received this file as part of a package, please find the license.txt file in 13 * the same folder or the closest folder above for complete license terms. 14 * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/) 15 * then you must choose one of the following licenses before using the file: 16 * - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php 17 * - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php 18 * }} 19 * 20 * {@internal Open Source relicensing agreement: 21 * Daniel HAHLER grants Francois PLANQUE the right to license 22 * Daniel HAHLER's contributions to this file and the b2evolution project 23 * under any OSI approved OSS license (http://www.opensource.org/licenses/). 24 * }} 25 * 26 * @package evocore 27 * 28 * {@internal Below is a list of authors who have contributed to design/coding of this file: }} 29 * @author blueyed: Daniel HAHLER. 30 * @author fplanque: Francois PLANQUE. 31 * @author gorgeb: Bertrand GORGE / EPISTEMA 32 * @author mbruneau: Marc BRUNEAU / PROGIDISTRI 33 * 34 * @version $Id: _item.class.php,v 1.15 2007/11/04 01:10:57 fplanque Exp $ 35 */ 36 if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); 37 38 /** 39 * Includes: 40 */ 41 load_class('items/model/_itemlight.class.php'); 42 load_funcs('items/model/_item.funcs.php'); 43 44 45 /** 46 * Item Class 47 * 48 * @package evocore 49 */ 50 class Item extends ItemLight 51 { 52 /** 53 * The User who has created the Item (lazy-filled). 54 * @see Item::get_creator_User() 55 * @see Item::set_creator_User() 56 * @var User 57 * @access protected 58 */ 59 var $creator_User; 60 61 62 /** 63 * @deprecated by {@link $creator_User} 64 * @var User 65 */ 66 var $Author; 67 68 69 /** 70 * ID of the user that created the item 71 * @var integer 72 */ 73 var $creator_user_ID; 74 75 76 /** 77 * The assigned User to the item. 78 * Can be NULL 79 * @see Item::get_assigned_User() 80 * @see Item::assign_to() 81 * 82 * @var User 83 * @access protected 84 */ 85 var $assigned_User; 86 87 /** 88 * ID of the user that created the item 89 * Can be NULL 90 * 91 * @var integer 92 */ 93 var $assigned_user_ID; 94 95 /** 96 * The visibility status of the item. 97 * 98 * 'published', 'deprecated', 'protected', 'private' or 'draft' 99 * 100 * @var string 101 */ 102 var $status; 103 /** 104 * Locale code for the Item content. 105 * 106 * Examples: en-US, zh-CN-utf-8 107 * 108 * @var string 109 */ 110 var $locale; 111 112 var $content; 113 114 /** 115 * Lazy filled, use split_page() 116 */ 117 var $content_pages = NULL; 118 119 120 var $wordcount; 121 /** 122 * The list of renderers, imploded by '.'. 123 * @var string 124 * @access protected 125 */ 126 var $renderers; 127 /** 128 * Comments status 129 * 130 * "open", "disabled" or "closed 131 * 132 * @var string 133 */ 134 var $comment_status; 135 136 var $pst_ID; 137 var $datedeadline = ''; 138 var $priority; 139 140 /** 141 * Have post processing notifications been handled? 142 * @var string 143 */ 144 var $notifications_status; 145 /** 146 * Which cron task is responsible for handling notifications? 147 * @var integer 148 */ 149 var $notifications_ctsk_ID; 150 151 /** 152 * array of IDs or NULL if we don't know... 153 * 154 * @var array 155 */ 156 var $extra_cat_IDs = NULL; 157 158 /** 159 * Array of tags (strings) 160 * 161 * Lazy loaded. 162 * 163 * @var array 164 */ 165 var $tags = NULL; 166 167 /** 168 * Array of Links attached to this item. 169 * 170 * NULL when not initialized. 171 * 172 * @var array 173 * @access public 174 */ 175 var $Links = NULL; 176 177 178 var $priorities; 179 180 /** 181 * Pre-rendered content, cached by format/validated renderers. 182 * 183 * Can be NULL 184 * 185 * @see Item::get_prerendered_content() 186 * @access protected 187 * @var array 188 */ 189 var $content_prerendered; 190 191 192 /** 193 * Constructor 194 * 195 * @param object table Database row 196 * @param string 197 * @param string 198 * @param string 199 * @param string for derived classes 200 * @param string datetime field name 201 * @param string datetime field name 202 * @param string User ID field name 203 * @param string User ID field name 204 */ 205 function Item( $db_row = NULL, $dbtable = 'T_items__item', $dbprefix = 'post_', $dbIDname = 'post_ID', $objtype = 'Item', 206 $datecreated_field = 'datecreated', $datemodified_field = 'datemodified', 207 $creator_field = 'creator_user_ID', $lasteditor_field = 'lastedit_user_ID' ) 208 { 209 global $localtimenow, $default_locale, $current_User; 210 211 $this->priorities = array( 212 1 => /* TRANS: Priority name */ T_('1 - Highest'), 213 2 => /* TRANS: Priority name */ T_('2 - High'), 214 3 => /* TRANS: Priority name */ T_('3 - Medium'), 215 4 => /* TRANS: Priority name */ T_('4 - Low'), 216 5 => /* TRANS: Priority name */ T_('5 - Lowest'), 217 ); 218 219 // Call parent constructor: 220 parent::ItemLight( $db_row, $dbtable, $dbprefix, $dbIDname, $objtype, 221 $datecreated_field, $datemodified_field, 222 $creator_field, $lasteditor_field ); 223 224 if( is_null($db_row) ) 225 { // New item: 226 if( isset($current_User) ) 227 { // use current user as default, if available (which won't be the case during install) 228 $this->set_creator_User( $current_User ); 229 } 230 $this->set( 'notifications_status', 'noreq' ); 231 // Set the renderer list to 'default' will trigger all 'opt-out' renderers: 232 $this->set( 'renderers', array('default') ); 233 $this->set( 'status', 'published' ); 234 $this->set( 'locale', $default_locale ); 235 $this->set( 'priority', 3 ); 236 } 237 else 238 { 239 $this->datecreated = $db_row->post_datecreated; // Needed for history display 240 $this->creator_user_ID = $db_row->post_creator_user_ID; // Needed for history display 241 $this->lastedit_user_ID = $db_row->post_lastedit_user_ID; // Needed for history display 242 $this->assigned_user_ID = $db_row->post_assigned_user_ID; 243 $this->status = $db_row->post_status; 244 $this->content = $db_row->post_content; 245 $this->pst_ID = $db_row->post_pst_ID; 246 $this->datedeadline = $db_row->post_datedeadline; 247 $this->priority = $db_row->post_priority; 248 $this->locale = $db_row->post_locale; 249 $this->wordcount = $db_row->post_wordcount; 250 $this->notifications_status = $db_row->post_notifications_status; 251 $this->notifications_ctsk_ID = $db_row->post_notifications_ctsk_ID; 252 $this->comment_status = $db_row->post_comment_status; // Comments status 253 254 // echo 'renderers=', $db_row->post_renderers; 255 $this->renderers = $db_row->post_renderers; 256 257 $this->views = $db_row->post_views; 258 } 259 } 260 261 262 /** 263 * @todo use extended dbchange instead of set_param... 264 * @todo Normalize to set_assigned_User!? 265 */ 266 function assign_to( $user_ID, $dbupdate = true /* BLOAT!? */ ) 267 { 268 // echo 'assigning user #'.$user_ID; 269 if( ! empty($user_ID) ) 270 { 271 if( $dbupdate ) 272 { // Record ID for DB: 273 $this->set_param( 'assigned_user_ID', 'number', $user_ID, true ); 274 } 275 else 276 { 277 $this->assigned_user_ID = $user_ID; 278 } 279 $UserCache = & get_Cache( 'UserCache' ); 280 $this->assigned_User = & $UserCache->get_by_ID( $user_ID ); 281 } 282 else 283 { 284 // fp>> DO NOT set (to null) immediately OR it may KILL the current User object (big problem if it's the Current User) 285 unset( $this->assigned_User ); 286 if( $dbupdate ) 287 { // Record ID for DB: 288 $this->set_param( 'assigned_user_ID', 'number', NULL, true ); 289 } 290 else 291 { 292 $this->assigned_User = NULL; 293 } 294 $this->assigned_user_ID = NULL; 295 } 296 297 } 298 299 300 /** 301 * Template function: display author/creator of item 302 * 303 * @param string String to display before author name 304 * @param string String to display after author name 305 * @param string Output format, see {@link format_to_output()} 306 */ 307 function author( $params = array() ) 308 { 309 // Make sure we are not missing any param: 310 $params = array_merge( array( 311 'before' => ' ', 312 'after' => ' ', 313 'format' => 'htmlbody', 314 ), $params ); 315 316 // Load User 317 $this->get_creator_User(); 318 319 echo $params['before']; 320 echo $this->creator_User->preferred_name( $params['format'], false ); 321 echo $params['after']; 322 } 323 324 325 /** 326 * Load data from Request form fields. 327 * 328 * This requires the blog (e.g. {@link $blog_ID} or {@link $main_cat_ID} to be set). 329 * 330 * @param boolean true to force edit date (as long as perms permit) 331 * @return boolean true if loaded data seems valid. 332 */ 333 function load_from_Request( $force_edit_date = false ) 334 { 335 global $default_locale, $allowed_uri_scheme, $current_User; 336 337 if( param( 'post_locale', 'string', NULL ) !== NULL ) { 338 $this->set_from_Request( 'locale' ); 339 } 340 341 if( param( 'item_typ_ID', 'integer', NULL ) !== NULL ) { 342 $this->set_from_Request( 'ptyp_ID', 'item_typ_ID' ); 343 } 344 345 if( param( 'post_url', 'string', NULL ) !== NULL ) { 346 param_check_url( 'post_url', $allowed_uri_scheme ); 347 $this->set_from_Request( 'url' ); 348 } 349 // Note: post_url is not part of the simple form, so this message can be a little bit akward there 350 if( $this->status == 'redirected' && empty($this->url) ) 351 { 352 param_error( 'post_url', T_('If you want to redirect this post, you must specify an URL! (Expert mode)') ); 353 } 354 355 if( ( $force_edit_date || param( 'edit_date', 'integer', 0 ) ) 356 && $current_User->check_perm( 'edit_timestamp' ) ) 357 { // We can use user date: 358 param_date( 'item_issue_date', T_('Please enter a valid issue date.'), $force_edit_date /* required */ ); 359 if( strlen(get_param('item_issue_date')) ) 360 { // only set it, if a date was given: 361 param_time( 'item_issue_time' ); 362 $this->set( 'issue_date', form_date( get_param( 'item_issue_date' ), get_param( 'item_issue_time' ) ) ); // TODO: cleanup... 363 } 364 } 365 366 if( param( 'post_excerpt', 'string', NULL ) !== NULL ) { 367 $this->set_from_Request( 'excerpt' ); 368 } 369 370 if( param( 'post_urltitle', 'string', NULL ) !== NULL ) { 371 $this->set_from_Request( 'urltitle' ); 372 } 373 374 if( param( 'item_tags', 'string', NULL ) !== NULL ) { 375 $this->set_tags_from_string( get_param('item_tags') ); 376 // pre_dump( $this->tags ); 377 } 378 379 // Workflow stuff: 380 if( param( 'item_st_ID', 'integer', NULL ) !== NULL ) { 381 $this->set_from_Request( 'pst_ID', 'item_st_ID' ); 382 } 383 384 if( param( 'item_assigned_user_ID', 'integer', NULL ) !== NULL ) { 385 $this->assign_to( get_param('item_assigned_user_ID') ); 386 } 387 388 if( param( 'item_priority', 'integer', NULL ) !== NULL ) { 389 $this->set_from_Request( 'priority', 'item_priority', true ); 390 } 391 392 if( param_date( 'item_deadline', T_('Please enter a valid deadline.'), false, NULL ) !== NULL ) { 393 $this->set_from_Request( 'datedeadline', 'item_deadline', true ); 394 } 395 396 // Allow comments for this item (only if set to "post_by_post" for the Blog): 397 $this->load_Blog(); 398 if( $this->Blog->allowcomments == 'post_by_post' ) 399 { 400 if( param( 'post_comment_status', 'string', 'open' ) !== NULL ) 401 { // 'open' or 'closed' or ... 402 $this->set_from_Request( 'comment_status' ); 403 } 404 } 405 406 if( param( 'renderers_displayed', 'integer', 0 ) ) 407 { // use "renderers" value only if it has been displayed (may be empty) 408 $Plugins_admin = & get_Cache('Plugins_admin'); 409 $renderers = $Plugins_admin->validate_renderer_list( param( 'renderers', 'array', array() ) ); 410 $this->set( 'renderers', $renderers ); 411 } 412 else 413 { 414 $renderers = $this->get_renderers_validated(); 415 } 416 417 if( ($content = param( 'content', 'html', NULL )) !== NULL ) 418 { 419 $post_title = param( 'post_title', 'html', NULL ); 420 421 // Do some optional filtering on the content 422 // Typically stuff that will help the content to validate 423 // Useful for code display. 424 // Will probably be used for validation also. 425 $Plugins_admin = & get_Cache('Plugins_admin'); 426 $Plugins_admin->filter_contents( $post_title /* by ref */, $content /* by ref */, $renderers ); 427 428 $this->set( 'content', format_to_post( $content ) ); 429 $this->set( 'title', format_to_post( $post_title ) ); 430 } 431 432 return ! param_errors_detected(); 433 } 434 435 436 /** 437 * Template function: display anchor for permalinks to refer to. 438 */ 439 function anchor() 440 { 441 global $Settings; 442 443 // In case you have old cafelog permalinks, uncomment the following lines: 444 // $title = preg_replace( '/[^a-zA-Z0-9_\.-]/', '_', $this->title ); 445 // echo '<a id="'.$title.'"></a>'; 446 447 echo '<a id="item_'.$this->ID.'"></a>'; 448 } 449 450 451 /** 452 * Template function: display assignee of item 453 * 454 * @param string 455 * @param string 456 * @param string Output format, see {@link format_to_output()} 457 */ 458 function assigned_to( $before = '', $after = '', $format = 'htmlbody' ) 459 { 460 if( $this->get_assigned_User() ) 461 { 462 echo $before; 463 $this->assigned_User->preferred_name( $format ); 464 echo $after; 465 } 466 } 467 468 469 /** 470 * Get list of assigned user options 471 * 472 * @uses UserCache::get_blog_member_option_list() 473 * @return string HTML select options list 474 */ 475 function get_assigned_user_options() 476 { 477 $UserCache = & get_Cache( 'UserCache' ); 478 return $UserCache->get_blog_member_option_list( $this->blog_ID, $this->assigned_user_ID, 479 true, ($this->ID != 0) /* if this Item is already serialized we'll load the default anyway */ ); 480 } 481 482 483 /** 484 * Check if user can see comments on this post, which he cannot if they 485 * are disabled for the Item or never allowed for the blog. 486 * 487 * @return boolean 488 */ 489 function can_see_comments() 490 { 491 if( $this->comment_status == 'disabled' 492 || ( $this->get_Blog() && $this->Blog->allowcomments == 'never' ) ) 493 { // Comments are disabled on this post 494 return false; 495 } 496 497 return true; // OK, user can see comments 498 } 499 500 501 /** 502 * Template function: Check if user can leave comment on this post or display error 503 * 504 * @param string|NULL string to display before any error message; NULL to not display anything, but just return boolean 505 * @param string string to display after any error message 506 * @param string error message for non published posts, '#' for default 507 * @param string error message for closed comments posts, '#' for default 508 * @return boolean true if user can post, false if s/he cannot 509 */ 510 function can_comment( $before_error = '<p><em>', $after_error = '</em></p>', $non_published_msg = '#', $closed_msg = '#' ) 511 { 512 global $Plugins; 513 514 $display = ( ! is_null($before_error) ); 515 516 // Ask Plugins (it can say NULL and would get skipped in Plugin::trigger_event_first_return()): 517 // Examples: 518 // - A plugin might want to restrict comments on posts older than 20 days. 519 // - A plugin might want to allow comments always for certain users (admin). 520 if( $event_return = $Plugins->trigger_event_first_return( 'ItemCanComment', array( 'Item' => $this ) ) ) 521 { 522 $plugin_return_value = $event_return['plugin_return']; 523 if( $plugin_return_value === true ) 524 { 525 return true; // OK, user can comment! 526 } 527 528 if( $display && is_string($plugin_return_value) ) 529 { 530 echo $before_error; 531 echo $plugin_return_value; 532 echo $after_error; 533 } 534 535 return false; 536 } 537 538 if( $this->comment_status == 'disabled' ) 539 { // Comments are disabled on this post 540 return false; 541 } 542 543 if( $this->comment_status == 'closed' ) 544 { // Comments are closed on this post 545 546 if( $display) 547 { 548 if( $closed_msg == '#' ) 549 $closed_msg = T_( 'Comments are closed for this post.' ); 550 551 echo $before_error; 552 echo $closed_msg; 553 echo $after_error; 554 } 555 556 return false; 557 } 558 559 if( ($this->status == 'draft') || ($this->status == 'deprecated' ) || ($this->status == 'redirected' ) ) 560 { // Post is not published 561 562 if( $display ) 563 { 564 if( $non_published_msg == '#' ) 565 $non_published_msg = T_( 'This post is not published. You cannot leave comments.' ); 566 567 echo $before_error; 568 echo $non_published_msg; 569 echo $after_error; 570 } 571 572 return false; 573 } 574 575 $this->get_Blog(); 576 if( $this->Blog->allowcomments == 'never') 577 { 578 return false; 579 } 580 581 return true; // OK, user can comment! 582 } 583 584 585 /** 586 * Template function: Check if user can can rate this post 587 * 588 * @return boolean true if user can post, false if s/he cannot 589 */ 590 function can_rate() 591 { 592 $this->get_Blog(); 593 594 if( $this->Blog->get_setting('allow_rating') == 'never' ) 595 { 596 return false; 597 } 598 599 return true; // OK, user can rate! 600 } 601 602 603 /** 604 * Get the prerendered content. If it has not been generated yet, it will. 605 * 606 * @todo dh> Currently this makes up one query per displayed item. Probably the cache should get pre-fetched by ItemList2? 607 * fp> DEFINITELY!!! Preloading all pre-rendered contents for the current Itemlistpage is paramount! 608 * 609 * @todo dh> In general, $content_prerendered gets only queried once per item, so it seems like a memory waste to cache the query result..! 610 * fp> I don't know if this is supposed to be related but that doesn't change anything to the previous todo. 611 * 612 * NOTE: This calls {@link Item::dbupdate()}, if renderers get changed (from Plugin hook). 613 * 614 * @param string Format, see {@link format_to_output()}. 615 * Only "htmlbody", "entityencoded", "xml" and "text" get cached. 616 * @return string 617 */ 618 function get_prerendered_content( $format ) 619 { 620 global $Plugins; 621 622 $post_renderers = $this->get_renderers_validated(); 623 $cache_key = $format.'/'.implode('.', $post_renderers); 624 625 if( ! isset( $this->content_prerendered[$cache_key] ) ) 626 { 627 $use_cache = $this->ID && in_array( $format, array( 'htmlbody', 'entityencoded', 'xml', 'text' ) ); 628 629 // $use_cache = false; 630 631 if( $use_cache ) 632 { // the format/item can be cached: 633 global $DB; 634 635 $cache = $DB->get_var( " 636 SELECT itpr_content_prerendered 637 FROM T_items__prerendering 638 WHERE itpr_itm_ID = ".$this->ID." 639 AND itpr_format = '".$format."' 640 AND itpr_renderers = '".implode('.', $post_renderers)."'", 0, 0, 'Check prerendered item content' ); 641 642 if( $cache !== NULL ) // may be empty string 643 { // Retrieved from cache: 644 // echo ' retrieved from prerendered cache'; 645 $this->content_prerendered[$cache_key] = $cache; 646 } 647 } 648 649 if( ! isset( $this->content_prerendered[$cache_key] ) ) 650 { // Not cached yet: 651 global $Debuglog; 652 653 if( $this->update_renderers_from_Plugins() ) 654 { 655 $post_renderers = $this->get_renderers_validated(); // might have changed from call above 656 $cache_key = $format.'/'.implode('.', $post_renderers); 657 658 // Save new renderers with item: 659 $this->dbupdate(); 660 } 661 662 // Call RENDERER plugins: 663 // pre_dump( $this->content ); 664 $this->content_prerendered[$cache_key] = $this->content; 665 $Plugins->render( $this->content_prerendered[$cache_key] /* by ref */, $post_renderers, $format, array( 'Item' => $this ), 'Render' ); 666 // pre_dump( $this->content_prerendered[$cache_key] ); 667 668 $Debuglog->add( 'Generated pre-rendered content ['.$cache_key.'] for item #'.$this->ID, 'items' ); 669 670 if( $use_cache ) 671 { // save into DB (using REPLACE INTO because it may have been pre-rendered by another thread since the SELECT above) 672 $DB->query( " 673 REPLACE INTO T_items__prerendering (itpr_itm_ID, itpr_format, itpr_renderers, itpr_content_prerendered) 674 VALUES ( ".$this->ID.", '".$format."', ".$DB->quote(implode('.', $post_renderers)).', '.$DB->quote($this->content_prerendered[$cache_key]).' )', 'Cache prerendered item content' ); 675 } 676 } 677 } 678 else 679 { 680 $Debuglog->add( 'Fetched pre-rendered content ['.$cache_key.'] for item #'.$this->ID, 'items' ); 681 } 682 683 return $this->content_prerendered[$cache_key]; 684 } 685 686 687 /** 688 * Set the pre-rendered content. 689 * 690 * This is meant to get called by ItemList2, which would do a single query for all 691 * items. 692 * 693 * @param string Pre-rendered content 694 * @param string Cache-Key ($format.'/'.$renderers). See {@link Item::get_prerendered_content()} for the appropriate query skeleton. 695 */ 696 function set_prerendered_content( $content, $cache_key ) 697 { 698 $this->content_prerendered[$cache_key] = $content; 699 } 700 701 702 /** 703 * Trigger {@link Plugin::ItemApplyAsRenderer()} event and adjust renderers according 704 * to return value. 705 * @return boolean True if renderers got changed. 706 */ 707 function update_renderers_from_Plugins() 708 { 709 global $Plugins; 710 711 $r = false; 712 713 foreach( $Plugins->get_list_by_event('ItemApplyAsRenderer') as $Plugin ) 714 { 715 if( empty($Plugin->code) ) 716 continue; 717 718 $plugin_r = $Plugin->ItemApplyAsRenderer( $tmp_params = array('Item' => & $this) ); 719 720 if( is_bool($plugin_r) ) 721 { 722 if( $plugin_r ) 723 { 724 $r = $this->add_renderer( $Plugin->code ) || $r; 725 } 726 else 727 { 728 $r = $this->remove_renderer( $Plugin->code ) || $r; 729 } 730 } 731 } 732 733 return $r; 734 } 735 736 737 /** 738 * Make sure, the pages have been obtained (and split up_ from prerendered cache. 739 * 740 * @param string Format, used to retrieve the matching cache; see {@link format_to_output()} 741 */ 742 function split_pages( $format = 'htmlbody' ) 743 { 744 if( ! isset( $this->content_pages[$format] ) ) 745 { 746 // SPLIT PAGES: 747 $this->content_pages[$format] = explode( '<!--nextpage-->', $this->get_prerendered_content($format) ); 748 749 $this->pages = count( $this->content_pages[$format] ); 750 // echo ' Pages:'.$this->pages; 751 } 752 } 753 754 755 /** 756 * Get a specific page to display (from the prerendered cache) 757 * 758 * @param integer Page number 759 * @param string Format, used to retrieve the matching cache; see {@link format_to_output()} 760 */ 761 function get_content_page( $page, $format = 'htmlbody' ) 762 { 763 // Make sure, the pages are split up: 764 $this->split_pages( $format ); 765 766 if( $page < 1 ) 767 { 768 $page = 1; 769 } 770 771 if( $page > $this->pages ) 772 { 773 $page = $this->pages; 774 } 775 776 return $this->content_pages[$format][$page-1]; 777 } 778 779 780 /** 781 * This is like a teaser with no HTML and a cropping. 782 * 783 * @todo fp> allow use to submit his own excerpt in expert editing mode 784 */ 785 function get_content_excerpt( $crop_at = 200 ) 786 { 787 // Get teaser for page 1: 788 // fp> Note: I'm not sure about using 'text' here, but there should definitely be no rendering here. 789 $output = $this->get_content_teaser( 1, false, 'text' ); 790 791 // Get rid of all HTML: 792 $output = strip_tags( $output ); 793 794 // Ger rid of all new lines: 795 $output = trim( str_replace( array( "\r", "\n", "\t" ), array( ' ', ' ', ' ' ), $output ) ); 796 797 if( strlen( $output ) > $crop_at ) 798 { 799 $output = substr( $output, 0, $crop_at ).'...'; 800 } 801 802 return $output; 803 } 804 805 806 /** 807 * Display content teaser of item (will stop at "<!-- more -->" 808 */ 809 function content_teaser( $params ) 810 { 811 // Make sure we are not missing any param: 812 $params = array_merge( array( 813 'before' => '', 814 'after' => '', 815 'disppage' => '#', 816 'stripteaser' => '#', 817 'format' => 'htmlbody', 818 ), $params ); 819 820 $r = $this->get_content_teaser( $params['disppage'], $params['stripteaser'], $params['format'] ); 821 822 if( !empty($r) ) 823 { 824 echo $params['before']; 825 echo $r; 826 echo $params['after']; 827 } 828 } 829 830 /** 831 * Template function: get content teaser of item (will stop at "<!-- more -->" 832 * 833 * @param mixed page number to display specific page, # for url parameter 834 * @param boolean # if you don't want to repeat teaser after more link was pressed and <-- noteaser --> has been found 835 * @param string filename to use to display more 836 * @return string 837 */ 838 function get_content_teaser( $disppage = '#', $stripteaser = '#', $format = 'htmlbody' ) 839 { 840 global $Plugins, $preview, $Debuglog; 841 global $more; 842 843 // Get requested content page: 844 if( $disppage === '#' ) 845 { // We want to display the page requested by the user: 846 global $page; 847 $disppage = $page; 848 } 849 850 $content_page = $this->get_content_page( $disppage, $format ); // cannot include format_to_output() because of the magic below.. eg '<!--more-->' will get stripped in "xml" 851 // pre_dump($content_page); 852 853 $content_parts = explode( '<!--more-->', $content_page ); 854 // echo ' Parts:'.count($content_parts); 855 856 if( count($content_parts) > 1 ) 857 { // This is an extended post (has a more section): 858 if( $stripteaser === '#' ) 859 { 860 // If we're in "more" mode and we want to strip the teaser, we'll strip: 861 $stripteaser = ( $more && preg_match('/<!--noteaser-->/', $content_page ) ); 862 } 863 864 if( $stripteaser ) 865 { 866 return NULL; 867 } 868 } 869 870 $output = $content_parts[0]; 871 872 // Trigger Display plugins FOR THE STUFF THAT WOULD NOT BE PRERENDERED: 873 $output = $Plugins->render( $output, $this->get_renderers_validated(), $format, array( 874 'Item' => $this, 875 'preview' => $preview, 876 'dispmore' => ($more != 0), 877 ), 'Display' ); 878 879 // Character conversions 880 $output = format_to_output( $output, $format ); 881 882 return $output; 883 } 884 885 886 /** 887 * Display content teaser of item (will stop at "<!-- more -->" 888 */ 889 function content_extension( $params ) 890 { 891 // Make sure we are not missing any param: 892 $params = array_merge( array( 893 'before' => '', 894 'after' => '', 895 'disppage' => '#', 896 'format' => 'htmlbody', 897 'force_more' => false, 898 ), $params ); 899 900 $r = $this->get_content_extension( $params['disppage'], $params['force_more'], $params['format'] ); 901 902 if( !empty($r) ) 903 { 904 echo $params['before']; 905 echo $r; 906 echo $params['after']; 907 } 908 } 909 910 911 /** 912 * Template function: get content extension of item (part after "<!-- more -->") 913 * 914 * @param mixed page number to display specific page, # for url parameter 915 * @param boolean 916 * @param string filename to use to display more 917 * @return string 918 */ 919 function get_content_extension( $disppage = '#', $force_more = false, $format = 'htmlbody' ) 920 { 921 global $Plugins, $more, $preview; 922 923 if( ! $more && ! $force_more ) 924 { // NOT in more mode: 925 return NULL; 926 } 927 928 // Get requested content page: 929 if( $disppage === '#' ) 930 { // We want to display the page requested by the user: 931 global $page; 932 $disppage = $page; 933 } 934 935 $content_page = $this->get_content_page( $disppage, $format ); // cannot include format_to_output() because of the magic below.. eg '<!--more-->' will get stripped in "xml" 936 // pre_dump($content_page); 937 938 $content_parts = explode( '<!--more-->', $content_page ); 939 // echo ' Parts:'.count($content_parts); 940 941 if( count($content_parts) < 2 ) 942 { // This is NOT an extended post 943 return NULL; 944 } 945 946 // Output everything after <!-- more --> 947 array_shift($content_parts); 948 $output = implode('', $content_parts); 949 950 // Trigger Display plugins FOR THE STUFF THAT WOULD NOT BE PRERENDERED: 951 $output = $Plugins->render( $output, $this->get_renderers_validated(), $format, array( 952 'Item' => $this, 953 'preview' => $preview, 954 'dispmore' => true, 955 ), 'Display' ); 956 957 // Character conversions 958 $output = format_to_output( $output, $format ); 959 960 return $output; 961 } 962 963 964 /** 965 * Increase view counter 966 * 967 * @todo merge with inc_viewcount 968 */ 969 function count_view( $params = array() ) 970 { 971 // Make sure we are not missing any param: 972 $params = array_merge( array( 973 'allow_multiple_counts_per_page' => false, 974 ), $params ); 975 976 977 global $Hit, $preview, $Debuglog; 978 979 if( $preview ) 980 { 981 // echo 'PREVIEW'; 982 return false; 983 } 984 985 /* 986 * Check if we want to increment view count, see {@link Hit::is_new_view()} 987 */ 988 if( ! $Hit->is_new_view() ) 989 { // This is a reload 990 // echo 'RELOAD'; 991 return false; 992 } 993 994 if( ! $params['allow_multiple_counts_per_page'] ) 995 { // Check that we don't increase multiple viewcounts on the same page 996 // This make the assumption that the first post in a list is "viewed" and the other are not (necesarily) 997 global $view_counts_on_this_page; 998 if( $view_counts_on_this_page >= 1 ) 999 { // we already had a count on this page 1000 // echo 'ALREADY HAD A COUNT'; 1001 return false; 1002 } 1003 $view_counts_on_this_page++; 1004 } 1005 1006 //echo 'COUNTING VIEW'; 1007 1008 // Increment view counter (only if current User is not the item's author) 1009 return $this->inc_viewcount(); // won't increment if current_User == Author 1010 1011 } 1012 1013 1014 /** 1015 * Template tag 1016 */ 1017 function more_link( $params = array() ) 1018 { 1019 echo $this->get_more_link( $params ); 1020 } 1021 1022 1023 /** 1024 * Display more link 1025 * 1026 * @param string string to display before more link 1027 * @param string string to display after more link 1028 * @param string 1029 * @param string 1030 * @param mixed 1031 * @param string Output format, see {@link format_to_output()} 1032 */ 1033 function get_more_link( $params = array() ) 1034 { 1035 // Make sure we are not missing any param: 1036 $params = array_merge( array( 1037 'before' => '<p class="bMore">', 1038 'after' => '</p>', 1039 'link_text' => '#', // text to display as the more link 1040 'anchor_text' => '#', // text to display as the more anchor (once the more link has been clicked) 1041 'disppage' => '#', // page number to display specific page, # for url parameter 1042 'format' => 'htmlbody', 1043 ), $params ); 1044 1045 global $more; 1046 1047 // Get requested content page: 1048 if( $params['disppage'] === '#' ) 1049 { // We want to display the page requested by the user: 1050 global $page; 1051 $params['disppage'] = $page; 1052 } 1053 1054 $content_page = $this->get_content_page( $params['disppage'], $params['format'] ); // cannot include format_to_output() because of the magic below.. eg '<!--more-->' will get stripped in "xml" 1055 // pre_dump($content_page); 1056 1057 $content_parts = explode( '<!--more-->', $content_page ); 1058 // echo ' Parts:'.count($content_parts); 1059 1060 if( count($content_parts) < 2 ) 1061 { // This is NOT an extended post: 1062 return ''; 1063 } 1064 1065 if( ! $more ) 1066 { // We're NOT in "more" mode: 1067 if( $params['link_text'] == '#' ) 1068 { // TRANS: this is the default text for the extended post "more" link 1069 $params['link_text'] = T_('Read more').' »'; 1070 } 1071 1072 return format_to_output( $params['before'] 1073 .'<a href="'.$this->get_permanent_url().'#more'.$this->ID.'">' 1074 .$params['link_text'].'</a>' 1075 .$params['after'], $params['format'] ); 1076 } 1077 elseif( ! preg_match('/<!--noteaser-->/', $content_page ) ) 1078 { // We are in mode mode and we're not hiding the teaser: 1079 // (if we're higin the teaser we display this as a normal page ie: no anchor) 1080 if( $params['anchor_text'] == '#' ) 1081 { // TRANS: this is the default text displayed once the more link has been activated 1082 $params['anchor_text'] = '<p class="bMore">'.T_('Follow up:').'</p>'; 1083 } 1084 1085 return format_to_output( '<a id="more'.$this->ID.'" name="more'.$this->ID.'"></a>' 1086 .$params['anchor_text'], $params['format'] ); 1087 } 1088 } 1089 1090 1091 /** 1092 * Template function: display deadline date (datetime) of Item 1093 * 1094 * @param string date/time format: leave empty to use locale default date format 1095 * @param boolean true if you want GMT 1096 */ 1097 function deadline_date( $format = '', $useGM = false ) 1098 { 1099 if( empty($format) ) 1100 echo mysql2date( locale_datefmt(), $this->datedeadline, $useGM); 1101 else 1102 echo mysql2date( $format, $this->datedeadline, $useGM); 1103 } 1104 1105 1106 /** 1107 * Template function: display deadline time (datetime) of Item 1108 * 1109 * @param string date/time format: leave empty to use locale default time format 1110 * @param boolean true if you want GMT 1111 */ 1112 function deadline_time( $format = '', $useGM = false ) 1113 { 1114 if( empty($format) ) 1115 echo mysql2date( locale_timefmt(), $this->datedeadline, $useGM ); 1116 else 1117 echo mysql2date( $format, $this->datedeadline, $useGM ); 1118 } 1119 1120 1121 /** 1122 * Get reference to array of Links 1123 */ 1124 function & get_Links() 1125 { 1126 // Make sure links are loaded: 1127 $this->load_links(); 1128 1129 return $this->Links; 1130 } 1131 1132 1133 /** 1134 * Template function: display number of links attached to this Item 1135 */ 1136 function linkcount() 1137 { 1138 // Make sure links are loaded: 1139 $this->load_links(); 1140 1141 echo count($this->Links); 1142 } 1143 1144 1145 /** 1146 * Load links if they were not loaded yet. 1147 */ 1148 function load_links() 1149 { 1150 if( is_null( $this->Links ) ) 1151 { // Links have not been loaded yet: 1152 $LinkCache = & get_Cache( 'LinkCache' ); 1153 $this->Links = & $LinkCache->get_by_item_ID( $this->ID ); 1154 } 1155 } 1156 1157 1158 /** 1159 * Get array of tags 1160 * 1161 * Load from DB if necessary 1162 * 1163 * @return array 1164 */ 1165 function & get_tags() 1166 { 1167 global $DB; 1168 1169 if( !isset( $this->tags ) ) 1170 { 1171 $this->tags = $DB->get_col( 1172 'SELECT tag_name 1173 FROM T_items__itemtag INNER JOIN T_items__tag ON itag_tag_ID = tag_ID 1174 WHERE itag_itm_ID = '.$this->ID.' 1175 ORDER BY tag_name', 0, 'Get tags for Item' ); 1176 } 1177 1178 // pre_dump( $this->tags ); 1179 return $this->tags; 1180 } 1181 1182 1183 /** 1184 * Split tags by space or comma 1185 * 1186 * @todo fp> allow tags with spaces when quoted like "long tag". Nota comma should never be allowed in a tag. 1187 * 1188 * @param string 1189 */ 1190 function set_tags_from_string( $tags ) 1191 { 1192 if( empty($tags) ) 1193 { 1194 $this->tags = array(); 1195 } 1196 else 1197 { 1198 $this->tags = preg_split( '/[\s,]+/', $tags ); 1199 } 1200 // pre_dump( $this->tags ); 1201 } 1202 1203 1204 /** 1205 * Template function: Provide link to message form for this Item's author. 1206 * 1207 * @param string url of the message form 1208 * @param string to display before link 1209 * @param string to display after link 1210 * @param string link text 1211 * @param string link title 1212 * @param string class name 1213 * @return boolean true, if a link was displayed; false if there's no email address for the Item's author. 1214 */ 1215 function msgform_link( $params = array() ) 1216 { 1217 // Make sure we are not missing any param: 1218 $params = array_merge( array( 1219 'before' => ' ', 1220 'after' => ' ', 1221 'text' => '#', 1222 'title' => '#', 1223 'class' => '', 1224 'format' => 'htmlbody', 1225 'form_url' => '#current_blog#', 1226 ), $params ); 1227 1228 $this->get_creator_User(); 1229 1230 if( empty($this->creator_User->email) ) 1231 { // We have no email for this Author :( 1232 return false; 1233 } 1234 if( empty($this->creator_User->allow_msgform) ) 1235 { 1236 return false; 1237 } 1238 1239 if( $params['form_url'] == '#current_blog#' ) 1240 { // Get 1241 global $Blog; 1242 $params['form_url'] = $Blog->get('msgformurl'); 1243 } 1244 1245 $params['form_url'] = url_add_param( $params['form_url'], 'recipient_id='.$this->creator_User->ID.'&post_id='.$this->ID 1246 .'&redirect_to='.rawurlencode(url_rel_to_same_host(regenerate_url('','','','&'), $params['form_url'])) ); 1247 1248 if( $params['title'] == '#' ) $params['title'] = T_('Send email to post author'); 1249 if( $params['text'] == '#' ) $params['text'] = get_icon( 'email', 'imgtag', array( 'class' => 'middle', 'title' => $params['title'] ) ); 1250 1251 echo $params['before']; 1252 echo '<a href="'.$params['form_url'].'" title="'.$params['title'].'"'; 1253 if( !empty( $params['class'] ) ) echo ' class="'.$params['class'].'"'; 1254 echo '>'.$params['text'].'</a>'; 1255 echo $params['after']; 1256 1257 return true; 1258 } 1259 1260 1261 /** 1262 * Template function: Provide link to message form for this Item's assigned User. 1263 * 1264 * @param string url of the message form 1265 * @param string to display before link 1266 * @param string to display after link 1267 * @param string link text 1268 * @param string link title 1269 * @param string class name 1270 * @return boolean true, if a link was displayed; false if there's no email address for the assigned User. 1271 */ 1272 function msgform_link_assigned( $form_url, $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '' ) 1273 { 1274 if( ! $this->get_assigned_User() || empty($this->assigned_User->email) ) 1275 { // We have no email for this Author :( 1276 return false; 1277 } 1278 1279 $form_url = url_add_param( $form_url, 'recipient_id='.$this->assigned_User->ID ); 1280 $form_url = url_add_param( $form_url, 'post_id='.$this->ID ); 1281 1282 if( $title == '#' ) $title = T_('Send email to assigned user'); 1283 if( $text == '#' ) $text = get_icon( 'email', 'imgtag', array( 'class' => 'middle', 'title' => $title ) ); 1284 1285 echo $before; 1286 echo '<a href="'.$form_url.'" title="'.$title.'"'; 1287 if( !empty( $class ) ) echo ' class="'.$class.'"'; 1288 echo '>'.$text.'</a>'; 1289 echo $after; 1290 1291 return true; 1292 } 1293 1294 1295 /** 1296 * 1297 */ 1298 function page_links( $before = '#', $after = '#', $separator = ' ', $single = '', $current_page = '#', $pagelink = '%d', $url = '' ) 1299 { 1300 1301 // Make sure, the pages are split up: 1302 $this->split_pages(); 1303 1304 if( $this->pages <= 1 ) 1305 { // Single page: 1306 echo $single; 1307 return; 1308 } 1309 1310 if( $before == '#' ) $before = '<p>'.T_('Pages:').' '; 1311 if( $after == '#' ) $after = '</p>'; 1312 1313 if( $current_page == '#' ) 1314 { 1315 global $page; 1316 $current_page = $page; 1317 } 1318 1319 if( empty($url) ) 1320 { 1321 $url = $this->get_permanent_url( '', '', '&' ); 1322 } 1323 1324 $page_links = array(); 1325 1326 for( $i = 1; $i <= $this->pages; $i++ ) 1327 { 1328 $text = str_replace('%d', $i, $pagelink); 1329 1330 if( $i != $current_page ) 1331 { 1332 if( $i == 1 ) 1333 { // First page special: 1334 $page_links[] = '<a href="'.$url.'">'.$text.'</a>'; 1335 } 1336 else 1337 { 1338 $page_links[] = '<a href="'.url_add_param( $url, 'page='.$i ).'">'.$text.'</a>'; 1339 } 1340 } 1341 else 1342 { 1343 $page_links[] = $text; 1344 } 1345 } 1346 1347 echo $before; 1348 echo implode( $separator, $page_links ); 1349 echo $after; 1350 } 1351 1352 1353 /** 1354 * Display the images linked to the current Item 1355 * 1356 * @param array of params 1357 * @param string Output format, see {@link format_to_output()} 1358 */ 1359 function images( $params = array(), $format = 'htmlbody' ) 1360 { 1361 echo $this->get_images( $params, $format ); 1362 } 1363 1364 1365 /** 1366 * Get block of images linked to the current Item 1367 * 1368 * @param array of params 1369 * @param string Output format, see {@link format_to_output()} 1370 */ 1371 function get_images( $params = array(), $format = 'htmlbody' ) 1372 { 1373 $params = array_merge( array( 1374 'before' => '<div>', 1375 'before_image' => '<div class="image_block">', 1376 'before_image_legend' => '<div class="image_legend">', 1377 'after_image_legend' => '</div>', 1378 'after_image' => '</div>', 1379 'after' => '</div>', 1380 'image_size' => 'fit-720x500' 1381 ), $params ); 1382 1383 $FileCache = & get_Cache( 'FileCache' ); 1384 1385 $FileList = & new DataObjectList2( $FileCache ); 1386 1387 1388 $SQL = & new SQL(); 1389 $SQL->SELECT( 'file_ID, file_title, file_root_type, file_root_ID, file_path, file_alt, file_desc' ); 1390 $SQL->FROM( 'T_links INNER JOIN T_files ON link_file_ID = file_ID' ); 1391 $SQL->WHERE( 'link_itm_ID = '.$this->ID ); 1392 $SQL->ORDER_BY( 'link_ID' ); 1393 1394 $FileList->sql = $SQL->get(); 1395 1396 $FileList->query( false, false, false ); 1397 1398 $r = ''; 1399 /** 1400 * @var File 1401 */ 1402 $File = NULL; 1403 while( $File = & $FileList->get_next() ) 1404 { 1405 if( ! $File->is_image() ) 1406 { // Skip anything that is not an image 1407 // fp> TODO: maybe this property should be stored in link_ltype_ID 1408 continue; 1409 } 1410 // Generate the IMG tag with all the alt, title and desc if available 1411 $r .= $File->get_tag( $params['before_image'], $params['before_image_legend'], $params['after_image_legend'], $params['after_image'], $params['image_size'] ); 1412 } 1413 1414 if( !empty($r) ) 1415 { 1416 $r = $params['before'].$r.$params['after']; 1417 1418 // Character conversions 1419 $r = format_to_output( $r, $format ); 1420 } 1421 1422 return $r; 1423 } 1424 1425 1426 /** 1427 * Template function: Displays link to the feed for comments on this item 1428 * 1429 * @param string Type of feedback to link to (rss2/atom) 1430 * @param string String to display before the link (if comments are to be displayed) 1431 * @param string String to display after the link (if comments are to be displayed) 1432 * @param string Link title 1433 */ 1434 function feedback_feed_link( $skin = '_rss2', $before = '', $after = '', $title='#' ) 1435 { 1436 if( ! $this->can_see_comments() ) 1437 { // Comments disabled 1438 return; 1439 } 1440 1441 if( $title == '#' ) 1442 { 1443 $title = get_icon( 'feed' ).' '.T_('Comment feed for this post'); 1444 } 1445 1446 $url = $this->get_feedback_feed_url($skin); 1447 1448 echo $before; 1449 echo '<a href="'.$url.'">'.format_to_output($title).'</a>'; 1450 echo $after; 1451 } 1452 1453 1454 /** 1455 * Get URL to display the post comments in an XML feed. 1456 * 1457 * @param string 1458 */ 1459 function get_feedback_feed_url( $skin_folder_name ) 1460 { 1461 $this->load_Blog(); 1462 1463 return url_add_param( $this->Blog->get_tempskin_url( $skin_folder_name ), 'disp=comments&p='.$this->ID); 1464 } 1465 1466 1467 /** 1468 * Template function: Displays link to feedback page (under some conditions) 1469 * 1470 * @param array 1471 */ 1472 function feedback_link( $params ) 1473 { 1474 if( ! $this->can_see_comments() ) 1475 { // Comments disabled 1476 return; 1477 } 1478 1479 $params = array_merge( array( 1480 'type' => 'feedbacks', 1481 'status' => 'published', 1482 'link_before' => '', 1483 'link_after' => '', 1484 'link_text_zero' => '#', 1485 'link_text_one' => '#', 1486 'link_text_more' => '#', 1487 'link_title' => '#', 1488 'use_popup' => false, 1489 'url' => '#', 1490 ), $params ); 1491 1492 1493 // dh> TODO: Add plugin hook, where a Pingback plugin could hook and provide "pingbacks" 1494 switch( $params['type'] ) 1495 { 1496 case 'feedbacks': 1497 if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display feedback / Leave a comment'); 1498 if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('Send feedback').' »'; 1499 if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 feedback').' »'; 1500 if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d feedbacks').' »'; 1501 break; 1502 1503 case 'comments': 1504 if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display comments / Leave a comment'); 1505 if( $params['link_text_zero'] == '#' ) 1506 { 1507 if( $this->can_comment( NULL ) ) // NULL, because we do not want to display errors here! 1508 { 1509 $params['link_text_zero'] = T_('Leave a comment').' »'; 1510 } 1511 else 1512 { 1513 $params['link_text_zero'] = ''; 1514 } 1515 } 1516 if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 comment').' »'; 1517 if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d comments').' »'; 1518 break; 1519 1520 case 'trackbacks': 1521 $this->get_Blog(); 1522 if( ! $this->Blog->get( 'allowtrackbacks' ) ) 1523 { // Trackbacks not allowed on this blog: 1524 return; 1525 } 1526 if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display trackbacks / Get trackback address for this post'); 1527 if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('Send a trackback').' »'; 1528 if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 trackback').' »'; 1529 if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d trackbacks').' »'; 1530 break; 1531 1532 case 'pingbacks': 1533 // Obsolete, but left for skin compatibility 1534 $this->get_Blog(); 1535 if( ! $this->Blog->get( 'allowtrackbacks' ) ) 1536 { // Trackbacks not allowed on this blog: 1537 // We'll consider pingbacks to follow the same restriction 1538 return; 1539 } 1540 if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display pingbacks'); 1541 if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('No pingback yet').' »'; 1542 if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 pingback').' »'; 1543 if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d pingbacks').' »'; 1544 break; 1545 1546 default: 1547 debug_die( "Unknown feedback type [{$params['type']}]" ); 1548 } 1549 1550 $link_text = $this->get_feedback_title( $params['type'], $params['link_text_zero'], $params['link_text_one'], $params['link_text_more'], $params['status'] ); 1551 1552 if( empty($link_text) ) 1553 { // No link, no display... 1554 return false; 1555 } 1556 1557 if( $params['url'] == '#' ) 1558 { // We want a link to single post: 1559 $params['url'] = $this->get_single_url( 'auto' ); 1560 } 1561 1562 1563 echo $params['link_before']; 1564 1565 if( !empty( $params['url'] ) ) 1566 { 1567 echo '<a href="'.$params['url']; 1568 echo '#'.$params['type'].'" '; // Position on feedback 1569 echo 'title="'.$params['link_title'].'"'; 1570 if( $params['use_popup'] ) 1571 { // Special URL if we can open a popup (i-e if JS is enabled): 1572 $popup_url = url_add_param( $params['url'], 'disp=feedback-popup' ); 1573 echo ' onclick="return pop_up_window( \''.$popup_url.'\', \'evo_comments\' )"'; 1574 } 1575 echo '>'; 1576 echo $link_text; 1577 echo '</a>'; 1578 } 1579 else 1580 { 1581 echo $link_text; 1582 } 1583 1584 echo $params['link_after']; 1585 } 1586 1587 1588 /** 1589 * Get text depending on number of comments 1590 * 1591 * @param string Type of feedback to link to (feedbacks (all)/comments/trackbacks/pingbacks) 1592 * @param string Link text to display when there are 0 comments 1593 * @param string Link text to display when there is 1 comment 1594 * @param string Link text to display when there are >1 comments (include %d for # of comments) 1595 * @param string Status of feedbacks to count 1596 */ 1597 function get_feedback_title( $type = 'feedbacks', $zero = '#', $one = '#', $more = '#', $status = 'published' ) 1598 { 1599 if( ! $this->can_see_comments() ) 1600 { // Comments disabled 1601 return NULL; 1602 } 1603 1604 // dh> TODO: Add plugin hook, where a Pingback plugin could hook and provide "pingbacks" 1605 switch( $type ) 1606 { 1607 case 'feedbacks': 1608 if( $zero == '#' ) $zero = ''; 1609 if( $one == '#' ) $one = T_('1 feedback'); 1610 if( $more == '#' ) $more = T_('%d feedbacks'); 1611 break; 1612 1613 case 'comments': 1614 if( $zero == '#' ) $zero = ''; 1615 if( $one == '#' ) $one = T_('1 comment'); 1616 if( $more == '#' ) $more = T_('%d comments'); 1617 break; 1618 1619 case 'trackbacks': 1620 if( $zero == '#' ) $zero = ''; 1621 if( $one == '#' ) $one = T_('1 trackback'); 1622 if( $more == '#' ) $more = T_('%d trackbacks'); 1623 break; 1624 1625 case 'pingbacks': 1626 // Obsolete, but left for skin compatibility 1627 if( $zero == '#' ) $zero = ''; 1628 if( $one == '#' ) $one = T_('1 pingback'); 1629 if( $more == '#' ) $more = T_('%d pingbacks'); 1630 break; 1631 1632 default: 1633 debug_die( "Unknown feedback type [$type]" ); 1634 } 1635 1636 $number = generic_ctp_number( $this->ID, $type, $status ); 1637 1638 if( $number == 0 ) 1639 return $zero; 1640 elseif( $number == 1 ) 1641 return $one; 1642 elseif( $number > 1 ) 1643 return str_replace( '%d', $number, $more ); 1644 } 1645 1646 1647 /** 1648 * Template function: Displays feeback moderation info 1649 * 1650 * @param string Type of feedback to link to (feedbacks (all)/comments/trackbacks/pingbacks) 1651 * @param string String to display before the link (if comments are to be displayed) 1652 * @param string String to display after the link (if comments are to be displayed) 1653 * @param string Link text to display when there are 0 comments 1654 * @param string Link text to display when there is 1 comment 1655 * @param string Link text to display when there are >1 comments (include %d for # of comments) 1656 * @param string Link 1657 * @param boolean true to hide if no feedback 1658 */ 1659 function feedback_moderation( $type = 'feedbacks', $before = '', $after = '', 1660 $zero = '', $one = '#', $more = '#', $edit_comments_link = '#', $params = array() ) 1661 { 1662 /** 1663 * @var User 1664 */ 1665 global $current_User; 1666 1667 $params = array_merge( array( 1668 'type' => 'feedbacks', 1669 'block_before' => '', 1670 'blo_after' => '', 1671 'link_text_zero' => '#', 1672 'link_text_one' => '#', 1673 'link_text_more' => '#', 1674 'link_title' => '#', 1675 'use_popup' => false, 1676 'url' => '#', 1677 'type' => 'feedbacks', 1678 '' => '', 1679 ), $params ); 1680 1681 if( isset($current_User) && $current_User->check_perm( 'blog_comments', 'any', false, $this->blog_ID ) ) 1682 { // We jave permission to edit comments: 1683 if( $edit_comments_link == '#' ) 1684 { // Use default link: 1685 global $admin_url; 1686 $edit_comments_link = '<a href="'.$admin_url.'?ctrl=items&blog='.$this->blog_ID.'&p='.$this->ID.'#comments" title="'.T_('Moderate these feedbacks').'">'.get_icon( 'edit' ).' '.T_('Moderate...').'</a>'; 1687 } 1688 } 1689 else 1690 { // User has no right to edit comments: 1691 $edit_comments_link = ''; 1692 } 1693 1694 // Inject Edit/moderate link as relevant: 1695 $zero = str_replace( '%s', $edit_comments_link, $zero ); 1696 $one = str_replace( '%s', $edit_comments_link, $one ); 1697 $more = str_replace( '%s', $edit_comments_link, $more ); 1698 1699 $r = $this->get_feedback_title( $type, $zero, $one, $more, 'draft' ); 1700 1701 if( !empty( $r ) ) 1702 { 1703 echo $before.$r.$after; 1704 } 1705 1706 } 1707 1708 1709 1710 /** 1711 * Gets button for deleting the Item if user has proper rights 1712 * 1713 * @param string to display before link 1714 * @param string to display after link 1715 * @param string link text 1716 * @param string link title 1717 * @param string class name 1718 * @param boolean true to make this a button instead of a link 1719 * @param string page url for the delete action 1720 */ 1721 function get_delete_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $button = false, $actionurl = '#' ) 1722 { 1723 global $current_User, $admin_url; 1724 1725 if( ! is_logged_in() ) return false; 1726 1727 if( ! $current_User->check_perm( 'blog_del_post', 'any', false, $this->blog_ID ) ) 1728 { // User has right to delete this post 1729 return false; 1730 } 1731 1732 if( $text == '#' ) 1733 { 1734 if( ! $button ) 1735 { 1736 $text = get_icon( 'delete', 'imgtag' ).' '.T_('Delete!'); 1737 } 1738 else 1739 { 1740 $text = T_('Delete!'); 1741 } 1742 } 1743 1744 if( $title == '#' ) $title = T_('Delete this post'); 1745 1746 if( $actionurl == '#' ) 1747 { 1748 $actionurl = $admin_url.'?ctrl=items&action=delete&post_ID='; 1749 } 1750 $url = $actionurl.$this->ID; 1751 1752 $r = $before; 1753 if( $button ) 1754 { // Display as button 1755 $r .= '<input type="button"'; 1756 $r .= ' value="'.$text.'" title="'.$title.'" onclick="if ( confirm(\''; 1757 $r .= TS_('You are about to delete this post!\\nThis cannot be undone!'); 1758 $r .= '\') ) { document.location.href=\''.$url.'\' }"'; 1759 if( !empty( $class ) ) $r .= ' class="'.$class.'"'; 1760 $r .= '/>'; 1761 } 1762 else 1763 { // Display as link 1764 $r .= '<a href="'.$url.'" title="'.$title.'" onclick="return confirm(\''; 1765 $r .= TS_('You are about to delete this post!\\nThis cannot be undone!'); 1766 $r .= '\')"'; 1767 if( !empty( $class ) ) $r .= ' class="'.$class.'"'; 1768 $r .= '>'.$text.'</a>'; 1769 } 1770 $r .= $after; 1771 1772 return $r; 1773 } 1774 1775 1776 /** 1777 * Displays button for deleting the Item if user has proper rights 1778 * 1779 * @param string to display before link 1780 * @param string to display after link 1781 * @param string link text 1782 * @param string link title 1783 * @param string class name 1784 * @param boolean true to make this a button instead of a link 1785 * @param string page url for the delete action 1786 */ 1787 function delete_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $button = false, $actionurl = '#' ) 1788 { 1789 echo $this->get_delete_link( $before, $after, $text, $title, $class, $button, $actionurl ); 1790 } 1791 1792 1793 /** 1794 * Provide link to edit a post if user has edit rights 1795 */ 1796 function get_edit_link( $params = array() ) 1797 { 1798 global $current_User, $admin_url; 1799 1800 if( ! is_logged_in() ) return false; 1801 1802 if( ! $this->ID ) 1803 { // preview.. 1804 return false; 1805 } 1806 1807 if( ! $current_User->check_perm( 'item_post!'.$this->status, 'edit', false, $this ) ) 1808 { // User has no right to edit this post 1809 return false; 1810 } 1811 1812 // Make sure we are not missing any param: 1813 $params = array_merge( array( 1814 'before' => ' ', 1815 'after' => ' ', 1816 'text' => '#', 1817 'title' => '#', 1818 'class' => '', 1819 'save_context' => true, 1820 ), $params ); 1821 1822 1823 if( $params['text'] == '#' ) $params['text'] = get_icon( 'edit' ).' '.T_('Edit...'); 1824 1825 if( $params['title'] == '#' ) $params['title'] = T_('Edit this post...'); 1826 1827 $actionurl = $admin_url.'?ctrl=items&action=edit&p='.$this->ID; 1828 if( $params['save_context'] ) 1829 { 1830 $actionurl .= '&redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ) ); 1831 } 1832 1833 1834 $r = $params['before']; 1835 $r .= '<a href="'.$actionurl; 1836 $r .= '" title="'.$params['title'].'"'; 1837 if( !empty( $params['class'] ) ) $r .= ' class="'.$params['class'].'"'; 1838 $r .= '>'.$params['text'].'</a>'; 1839 $r .= $params['after']; 1840 1841 return $r; 1842 } 1843 1844 1845 /** 1846 * Template tag 1847 * @see Item::get_edit_link() 1848 */ 1849 function edit_link( $params = array() ) 1850 { 1851 echo $this->get_edit_link( $params ); 1852 } 1853 1854 1855 /** 1856 * Provide link to publish a post if user has edit rights 1857 * 1858 * Note: publishing date will be updated 1859 * 1860 * @param string to display before link 1861 * @param string to display after link 1862 * @param string link text 1863 * @param string link title 1864 * @param string class name 1865 * @param string glue between url params 1866 */ 1867 function get_publish_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&', $save_context = true ) 1868 { 1869 global $current_User, $admin_url; 1870 1871 if( ! is_logged_in() ) return false; 1872 1873 if( ($this->status == 'published') // Already published! 1874 || ! ($current_User->check_perm( 'item_post!published', 'edit', false, $this )) 1875 || ! ($current_User->check_perm( 'edit_timestamp' ) ) ) 1876 { // User has no right to publish this post now: 1877 return false; 1878 } 1879 1880 if( $text == '#' ) $text = get_icon( 'publish', 'imgtag' ).' '.T_('Publish NOW!'); 1881 if( $title == '#' ) $title = T_('Publish now using current date and time.'); 1882 1883 $r = $before; 1884 $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action=publish'.$glue.'post_ID='.$this->ID; 1885 if( $save_context ) 1886 { 1887 $r .= $glue.'redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ) ); 1888 } 1889 $r .= '" title="'.$title.'"'; 1890 if( !empty( $class ) ) $r .= ' class="'.$class.'"'; 1891 $r .= '>'.$text.'</a>'; 1892 $r .= $after; 1893 1894 return $r; 1895 } 1896 1897 1898 function publish_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&', $save_context = true ) 1899 { 1900 echo $this->get_publish_link( $before, $after, $text, $title, $class, $glue, $save_context ); 1901 } 1902 1903 1904 /** 1905 * Provide link to deprecate a post if user has edit rights 1906 * 1907 * @param string to display before link 1908 * @param string to display after link 1909 * @param string link text 1910 * @param string link title 1911 * @param string class name 1912 * @param string glue between url params 1913 */ 1914 function get_deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&' ) 1915 { 1916 global $current_User, $admin_url; 1917 1918 if( ! is_logged_in() ) return false; 1919 1920 if( ($this->status == 'deprecated') // Already deprecateded! 1921 || ! ($current_User->check_perm( 'item_post!deprecated', 'edit', false, $this )) ) 1922 { // User has no right to deprecated this post: 1923 return false; 1924 } 1925 1926 if( $text == '#' ) $text = get_icon( 'deprecate', 'imgtag' ).' '.T_('Deprecate!'); 1927 if( $title == '#' ) $title = T_('Deprecate this post!'); 1928 1929 $r = $before; 1930 $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action=deprecate'.$glue.'post_ID='.$this->ID; 1931 $r .= '" title="'.$title.'"'; 1932 if( !empty( $class ) ) $r .= ' class="'.$class.'"'; 1933 $r .= '>'.$text.'</a>'; 1934 $r .= $after; 1935 1936 return $r; 1937 } 1938 1939 1940 /** 1941 * Display link to deprecate a post if user has edit rights 1942 * 1943 * @param string to display before link 1944 * @param string to display after link 1945 * @param string link text 1946 * @param string link title 1947 * @param string class name 1948 * @param string glue between url params 1949 */ 1950 function deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&' ) 1951 { 1952 echo $this->get_deprecate_link( $before, $after, $text, $title, $class, $glue ); 1953 } 1954 1955 1956 /** 1957 * Template function: display priority of item 1958 * 1959 * @param string 1960 * @param string 1961 */ 1962 function priority( $before = '', $after = '' ) 1963 { 1964 if( isset($this->priority) ) 1965 { 1966 echo $before; 1967 echo $this->priority; 1968 echo $after; 1969 } 1970 } 1971 1972 1973 /** 1974 * Template function: display list of priority options 1975 */ 1976 function priority_options( $field_value, $allow_none ) 1977 { 1978 $priority = isset($field_value) ? $field_value : $this->priority; 1979 1980 $r = ''; 1981 if( $allow_none ) 1982 { 1983 $r = '<option value="">'./* TRANS: "None" select option */T_('No priority').'</option>'; 1984 } 1985 1986 foreach( $this->priorities as $i => $name ) 1987 { 1988 $r .= '<option value="'.$i.'"'; 1989 if( $priority == $i ) 1990 { 1991 $r .= ' selected="selected"'; 1992 } 1993 $r .= '>'.$name.'</option>'; 1994 } 1995 1996 return $r; 1997 } 1998 1999 2000 /** 2001 * Template function: display checkable list of renderers 2002 * 2003 * @param array|NULL If given, assume these renderers to be checked. 2004 */ 2005 function renderer_checkboxes( $item_renderers = NULL ) 2006 { 2007 global $Plugins, $inc_path, $admin_url; 2008 2009 load_funcs('plugins/_plugin.funcs.php'); 2010 2011 $Plugins->restart(); // make sure iterator is at start position 2012 2013 $atLeastOneRenderer = false; 2014 2015 if( is_null($item_renderers) ) 2016 { 2017 $item_renderers = $this->get_renderers(); 2018 } 2019 // pre_dump( $item_renderers ); 2020 2021 echo '<input type="hidden" name="renderers_displayed" value="1" />'; 2022 2023 foreach( $Plugins->get_list_by_events( array('RenderItemAsHtml', 'RenderItemAsXml', 'RenderItemAsText') ) as $loop_RendererPlugin ) 2024 { // Go through whole list of renders 2025 // echo ' ',$loop_RendererPlugin->code; 2026 if( empty($loop_RendererPlugin->code) ) 2027 { // No unique code! 2028 continue; 2029 } 2030 if( $loop_RendererPlugin->apply_rendering == 'stealth' 2031 || $loop_RendererPlugin->apply_rendering == 'never' ) 2032 { // This is not an option. 2033 continue; 2034 } 2035 $atLeastOneRenderer = true; 2036 2037 echo '<div>'; 2038 2039 // echo $loop_RendererPlugin->apply_rendering; 2040 2041 echo '<input type="checkbox" class="checkbox" name="renderers[]" value="'; 2042 echo $loop_RendererPlugin->code; 2043 echo '" id="renderer_'; 2044 echo $loop_RendererPlugin->code; 2045 echo '"'; 2046 2047 switch( $loop_RendererPlugin->apply_rendering ) 2048 { 2049 case 'always': 2050 echo ' checked="checked"'; 2051 echo ' disabled="disabled"'; 2052 break; 2053 2054 case 'opt-out': 2055 if( in_array( $loop_RendererPlugin->code, $item_renderers ) // Option is activated 2056 || in_array( 'default', $item_renderers ) ) // OR we're asking for default renderer set 2057 { 2058 echo ' checked="checked"'; 2059 } 2060 break; 2061 2062 case 'opt-in': 2063 if( in_array( $loop_RendererPlugin->code, $item_renderers ) ) // Option is activated 2064 { 2065 echo ' checked="checked"'; 2066 } 2067 break; 2068 2069 case 'lazy': 2070 if( in_array( $loop_RendererPlugin->code, $item_renderers ) ) // Option is activated 2071 { 2072 echo ' checked="checked"'; 2073 } 2074 echo ' disabled="disabled"'; 2075 break; 2076 } 2077 2078 echo ' title="'; 2079 echo format_to_output($loop_RendererPlugin->short_desc, 'formvalue'); 2080 echo '" />' 2081 .' <label for="renderer_'; 2082 echo $loop_RendererPlugin->code; 2083 echo '" title="'; 2084 echo format_to_output($loop_RendererPlugin->short_desc, 'formvalue'); 2085 echo '">'; 2086 echo format_to_output($loop_RendererPlugin->name); 2087 echo '</label>'; 2088 2089 // fp> TODO: the first thing we want here is a TINY javascript popup with the LONG desc. The links to readme and external help should be inside of the tiny popup. 2090 // fp> a javascript DHTML onhover help would be evenb better than the JS popup 2091 2092 // internal README.html link: 2093 echo ' '.$loop_RendererPlugin->get_help_link('$readme'); 2094 // external help link: 2095 echo ' '.$loop_RendererPlugin->get_help_link('$help_url'); 2096 2097 echo "</div>\n"; 2098 } 2099 2100 if( !$atLeastOneRenderer ) 2101 { 2102 global $admin_url, $mode; 2103 echo '<a title="'.T_('Configure plugins').'" href="'.$admin_url.'?ctrl=plugins"'.'>'.T_('No renderer plugins are installed.').'</a>'; 2104 } 2105 } 2106 2107 2108 /** 2109 * Template function: display status of item 2110 * 2111 * Statuses: 2112 * - published 2113 * - deprecated 2114 * - protected 2115 * - private 2116 * - draft 2117 * 2118 * @param string Output format, see {@link format_to_output()} 2119 */ 2120 function status( $params = array() ) 2121 { 2122 global $post_statuses; 2123 2124 // Make sure we are not missing any param: 2125 $params = array_merge( array( 2126 'before' => '', 2127 'after' => '', 2128 'format' => 'htmlbody', 2129 ), $params ); 2130 2131 echo $params['before']; 2132 2133 if( $params['format'] == 'raw' ) 2134 { 2135 status_raw(); 2136 } 2137 else 2138 { 2139 echo format_to_output( $this->get('t_status'), $params['format'] ); 2140 } 2141 2142 echo $params['after']; 2143 } 2144 2145 2146 function status_raw() 2147 { 2148 echo $this->status; 2149 } 2150 2151 2152 /** 2153 * Template function: display extra status of item 2154 * 2155 * @param string 2156 * @param string 2157 * @param string Output format, see {@link format_to_output()} 2158 */ 2159 function extra_status( $before = '', $after = '', $format = 'htmlbody' ) 2160 { 2161 if( $format == 'raw' ) 2162 { 2163 $this->disp( $this->get('t_extra_status'), 'raw' ); 2164 } 2165 elseif( $extra_status = $this->get('t_extra_status') ) 2166 { 2167 echo $before.format_to_output( $extra_status, $format ).$after; 2168 } 2169 } 2170 2171 2172 2173 /** 2174 * Display tags for Item 2175 * 2176 * @param array of params 2177 * @param string Output format, see {@link format_to_output()} 2178 */ 2179 function tags( $params = array() ) 2180 { 2181 $params = array_merge( array( 2182 'before' => '<div>'.T_('Tags').': ', 2183 'after' => '</div>', 2184 'separator' => ', ', 2185 'links' => true, 2186 ), $params ); 2187 2188 $tags = $this->get_tags(); 2189 2190 if( !empty( $tags ) ) 2191 { 2192 echo $params['before']; 2193 2194 if( $links = $params['links'] ) 2195 { 2196 $this->get_Blog(); 2197 $tag_view_url = url_add_param( $this->Blog->gen_blogurl(), 'tag=' ); 2198 } 2199 2200 $i = 0; 2201 foreach( $tags as $tag ) 2202 { 2203 if( $i++ > 0 ) 2204 { 2205 echo $params['separator']; 2206 } 2207 if( $links ) 2208 { 2209 echo '<a href="'.$tag_view_url.urlencode( $tag ).'">'; 2210 } 2211 echo htmlspecialchars( $tag ); 2212 if( $links ) 2213 { 2214 echo '</a>'; 2215 } 2216 } 2217 2218 echo $params['after']; 2219 } 2220 } 2221 2222 2223 /** 2224 * Template function: Displays trackback autodiscovery information 2225 * 2226 * TODO: build into headers 2227 */ 2228 function trackback_rdf() 2229 { 2230 $this->get_Blog(); 2231 if( ! $this->Blog->get( 'allowtrackbacks' ) ) 2232 { // Trackbacks not allowed on this blog: 2233 return; 2234 } 2235 2236 echo '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" '."\n"; 2237 echo ' xmlns:dc="http://purl.org/dc/elements/1.1/"'."\n"; 2238 echo ' xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">'."\n"; 2239 echo '<rdf:Description'."\n"; 2240 echo ' rdf:about="'; 2241 $this->permanent_url( 'single' ); 2242 echo '"'."\n"; 2243 echo ' dc:identifier="'; 2244 $this->permanent_url( 'single' ); 2245 echo '"'."\n"; 2246 $this->title( ' dc:title="', '"'."\n", false, 'xmlattr' ); 2247 echo ' trackback:ping="'; 2248 $this->trackback_url(); 2249 echo '" />'."\n"; 2250 echo '</rdf:RDF>'; 2251 } 2252 2253 2254 /** 2255 * Template function: displays url to use to trackback this item 2256 */ 2257 function trackback_url() 2258 { 2259 echo $this->get_trackback_url(); 2260 } 2261 2262 2263 /** 2264 * Template function: get url to use to trackback this item 2265 * @return string 2266 */ 2267 function get_trackback_url() 2268 { 2269 global $htsrv_url, $Settings; 2270 2271 // fp> TODO: get a clean (per blog) setting for this 2272 // return $htsrv_url.'trackback.php/'.$this->ID; 2273 2274 return $htsrv_url.'trackback.php?tb_id='.$this->ID; 2275 } 2276 2277 2278 /** 2279 * Template function: Display link to item related url 2280 * 2281 * @param string string to display before the link (if exists) 2282 * @param string string to display after the link (if exists) 2283 * @param string Link text to use (%s gets replaced by the URL). 2284 * @param array Attributes for the <a> tag (if the href attribute is set, %s gets replaced by the URL). 2285 * @param string Output format, see {@link format_to_output()} 2286 */ 2287 function url_link( $before = '', $after = '', $text = '%s', $attribs = array(), $format = 'htmlbody' ) 2288 { 2289 if( ! empty( $this->url ) ) 2290 { 2291 if( isset($attribs['href']) ) 2292 { // We have specified our own href attribute for the link:! 2293 $attribs['href'] = str_replace( '%s', $this->url, $attribs['href'] ); 2294 } 2295 else 2296 { // Use default href: 2297 $attribs['href'] = $this->url; 2298 } 2299 echo $before; 2300 echo format_to_output( '<a'.get_field_attribs_as_string( $attribs ).'>'.str_replace( '%s', $this->url, $text ).'</a>', $format ); 2301 echo $after; 2302 } 2303 } 2304 2305 2306 /** 2307 * Template function: Display the number of words in the post 2308 */ 2309 function wordcount() 2310 { 2311 echo (int)$this->wordcount; // may have been saved as NULL until 1.9 2312 } 2313 2314 2315 /** 2316 * Template function: Display the number of times the Item has been viewed 2317 * 2318 * Note: viewcount is incremented whenever the Item's content is displayed with "MORE" 2319 * (i-e full content), see {@link Item::content()}. 2320 * 2321 * Viewcount is NOT incremented on page reloads and other special cases, see {@link Hit::is_new_view()} 2322 * 2323 * %d gets replaced in all params by the number of views. 2324 * 2325 * @param string Link text to display when there are 0 views 2326 * @param string Link text to display when there is 1 views 2327 * @param string Link text to display when there are >1 views 2328 * @return string The phrase about the number of views. 2329 */ 2330 function get_views( $zero = '#', $one = '#', $more = '#' ) 2331 { 2332 if( !$this->views ) 2333 { 2334 $r = ( $zero == '#' ? T_( 'No views' ) : $zero ); 2335 } 2336 elseif( $this->views == 1 ) 2337 { 2338 $r = ( $one == '#' ? T_( '1 view' ) : $one ); 2339 } 2340 else 2341 { 2342 $r = ( $more == '#' ? T_( '%d views' ) : $more ); 2343 } 2344 2345 return str_replace( '%d', $this->views, $r ); 2346 } 2347 2348 2349 /** 2350 * Template function: Display a phrase about the number of Item views. 2351 * 2352 * @param string Link text to display when there are 0 views 2353 * @param string Link text to display when there is 1 views 2354 * @param string Link text to display when there are >1 views (include %d for # of views) 2355 * @return integer Number of views. 2356 */ 2357 function views( $zero = '#', $one = '#', $more = '#' ) 2358 { 2359 echo $this->get_views( $zero, $one, $more ); 2360 2361 return $this->views; 2362 } 2363 2364 2365 /** 2366 * Set param value 2367 * 2368 * By default, all values will be considered strings 2369 * 2370 * @todo extra_cat_IDs recording 2371 * 2372 * @param string parameter name 2373 * @param mixed parameter value 2374 * @param boolean true to set to NULL if empty value 2375 * @return boolean true, if a value has been set; false if it has not changed 2376 */ 2377 function set( $parname, $parvalue, $make_null = false ) 2378 { 2379 switch( $parname ) 2380 { 2381 case 'pst_ID': 2382 return $this->set_param( $parname, 'number', $parvalue, true ); 2383 2384 case 'content': 2385 $r1 = $this->set_param( 'content', 'string', $parvalue, $make_null ); 2386 // Update wordcount as well: 2387 $r2 = $this->set_param( 'wordcount', 'number', bpost_count_words($this->content), false ); 2388 return ( $r1 || $r2 ); // return true if one changed 2389 2390 case 'wordcount': 2391 return $this->set_param( 'wordcount', 'number', $parvalue, false ); 2392 2393 case 'datedeadline': 2394 return $this->set_param( 'datedeadline', 'date', $parvalue, true ); 2395 2396 case 'renderers': // deprecated 2397 return $this->set_renderers( $parvalue ); 2398 2399 default: 2400 return parent::set( $parname, $parvalue, $make_null ); 2401 } 2402 } 2403 2404 2405 /** 2406 * Set the renderers of the Item. 2407 * 2408 * @param array List of renderer codes. 2409 * @return boolean true, if it has been set; false if it has not changed 2410 */ 2411 function set_renderers( $renderers ) 2412 { 2413 return $this->set_param( 'renderers', 'string', implode( '.', $renderers ) ); 2414 } 2415 2416 2417 /** 2418 * Set the Author of the Item. 2419 * 2420 * @param User (Do NOT set to NULL or you may kill the current_User) 2421 * @return boolean true, if it has been set; false if it has not changed 2422 */ 2423 function set_creator_User( & $creator_User ) 2424 { 2425 $this->creator_User = & $creator_User; 2426 $this->Author = & $this->creator_User; // deprecated fp> TODO: Test and see if this line can be put once and for all in the constructor 2427 return $this->set( $this->creator_field, $creator_User->ID ); 2428 } 2429 2430 2431 /** 2432 * Create a new Item/Post and insert it into the DB 2433 * 2434 * This function has to handle all needed DB dependencies! 2435 * 2436 * @deprecated since EVO_NEXT_VERSION. Use set() + dbinsert() instead 2437 */ 2438 function insert( 2439 $author_user_ID, // Author 2440 $post_title, 2441 $post_content, 2442 $post_timestamp, // 'Y-m-d H:i:s' 2443 $main_cat_ID = 1, // Main cat ID 2444 $extra_cat_IDs = array(), // Table of extra cats 2445 $post_status = 'published', 2446 $post_locale = '#', 2447 $post_urltitle = '', 2448 $post_url = '', 2449 $post_comment_status = 'open', 2450 $post_renderers = array('default'), 2451 $item_typ_ID = 1, 2452 $item_st_ID = NULL ) 2453 { 2454 global $DB, $query, $UserCache; 2455 global $localtimenow, $default_locale; 2456 2457 if( $post_locale == '#' ) $post_locale = $default_locale; 2458 2459 // echo 'INSERTING NEW POST '; 2460 2461 if( isset( $UserCache ) ) // DIRTY HACK 2462 { // If not in install procedure... 2463 $this->set_creator_User( $UserCache->get_by_ID( $author_user_ID ) ); 2464 } 2465 else 2466 { 2467 $this->set( $this->creator_field, $author_user_ID ); 2468 } 2469 $this->set( $this->lasteditor_field, $this->{$this->creator_field} ); 2470 $this->set( 'title', $post_title ); 2471 $this->set( 'urltitle', $post_urltitle ); 2472 $this->set( 'content', $post_content ); 2473 $this->set( 'datestart', $post_timestamp ); 2474 2475 // TODO: dh> $localtimenow is not defined during install! - all sample posts get a last-modified date of 1970-01-01 2476 $this->set( 'datemodified', date('Y-m-d H:i:s',$localtimenow) ); 2477 2478 $this->set( 'main_cat_ID', $main_cat_ID ); 2479 $this->set( 'extra_cat_IDs', $extra_cat_IDs ); 2480 $this->set( 'status', $post_status ); 2481 $this->set( 'locale', $post_locale ); 2482 $this->set( 'url', $post_url ); 2483 $this->set( 'comment_status', $post_comment_status ); 2484 $this->set_renderers( $post_renderers ); 2485 $this->set( 'ptyp_ID', $item_typ_ID ); 2486 $this->set( 'pst_ID', $item_st_ID ); 2487 2488 // INSERT INTO DB: 2489 $this->dbinsert(); 2490 2491 return $this->ID; 2492 } 2493 2494 2495 /** 2496 * Insert object into DB based on previously recorded changes 2497 * 2498 * @return boolean true on success 2499 */ 2500 function dbinsert() 2501 { 2502 global $DB, $current_User, $Plugins; 2503 2504 $DB->begin(); 2505 2506 if( empty($this->creator_user_ID) ) 2507 { // No creator assigned yet, use current user: 2508 $this->set_creator_User( $current_User ); 2509 } 2510 2511 // validate url title 2512 $this->set( 'urltitle', urltitle_validate( $this->urltitle, $this->title, 0, false, $this->dbprefix, $this->dbIDname, $this->dbtablename) ); 2513 2514 $this->update_renderers_from_Plugins(); 2515 2516 // TODO: allow a plugin to cancel update here (by returning false)? 2517 $Plugins->trigger_event( 'PrependItemInsertTransact', $params = array( 'Item' => & $this ) ); 2518 2519 $dbchanges = $this->dbchanges; // we'll save this for passing it to the plugin hook 2520 2521 if( $result = parent::dbinsert() ) 2522 { // We could insert the item object.. 2523 2524 // Let's handle the extracats: 2525 $this->insert_update_extracats( 'insert' ); 2526 2527 // Let's handle the tags: 2528 $this->insert_update_tags( 'insert' ); 2529 2530 $DB->commit(); 2531 2532 $Plugins->trigger_event( 'AfterItemInsert', $params = array( 'Item' => & $this, 'dbchanges' => $dbchanges ) ); 2533 } 2534 else 2535 { 2536 $DB->rollback(); 2537 } 2538 2539 return $result; 2540 } 2541 2542 2543 2544 2545 /** 2546 * Update the DB based on previously recorded changes 2547 * 2548 * @return boolean true on success 2549 */ 2550 function dbupdate() 2551 { 2552 global $DB, $Plugins; 2553 2554 $DB->begin(); 2555 2556 // validate url title 2557 if( empty($this->urltitle) || isset($this->dbchanges['post_urltitle']) ) 2558 { // Url title has changed or is empty 2559 // echo 'updating url title'; 2560 $this->set( 'urltitle', urltitle_validate( $this->urltitle, $this->title, $this->ID, 2561 false, $this->dbprefix, $this->dbIDname, $this->dbtablename ) ); 2562 } 2563 2564 $this->update_renderers_from_Plugins(); 2565 2566 // TODO: dh> allow a plugin to cancel update here (by returning false)? 2567 $Plugins->trigger_event( 'PrependItemUpdateTransact', $params = array( 'Item' => & $this ) ); 2568 2569 $dbchanges = $this->dbchanges; // we'll save this for passing it to the plugin hook 2570 2571 if( $result = parent::dbupdate() ) 2572 { // We could update the item object.. 2573 2574 // Let's handle the extracats: 2575 $this->insert_update_extracats( 'update' ); 2576 2577 // Let's handle the extracats: 2578 $this->insert_update_tags( 'update' ); 2579 2580 // Empty pre-rendered content cache - any item property may have influence on it: 2581 $DB->query( 'DELETE FROM T_items__prerendering WHERE itpr_itm_ID = '.$this->ID ); 2582 $this->content_prerendered = NULL; 2583 2584 $DB->commit(); 2585 2586 $Plugins->trigger_event( 'AfterItemUpdate', $params = array( 'Item' => & $this, 'dbchanges' => $dbchanges ) ); 2587 } 2588 else 2589 { 2590 $DB->commit(); 2591 } 2592 2593 return $result; 2594 } 2595 2596 2597 /** 2598 * Trigger event AfterItemDelete after calling parent method. 2599 * 2600 * @todo fp> delete related stuff: comments, cats, file links... 2601 * 2602 * @return boolean true on success 2603 */ 2604 function dbdelete() 2605 { 2606 global $DB, $Plugins; 2607 2608 // remember ID, because parent method resets it to 0 2609 $old_ID = $this->ID; 2610 2611 $DB->begin(); 2612 2613 if( $r = parent::dbdelete() ) 2614 { 2615 // Empty pre-rendered content cache: 2616 $DB->query( 'DELETE FROM T_items__prerendering 2617 WHERE itpr_itm_ID = '.$this->ID ); 2618 $this->content_prerendered = NULL; 2619 2620 $DB->commit(); 2621 2622 // re-set the ID for the Plugin event 2623 $this->ID = $old_ID; 2624 2625 $Plugins->trigger_event( 'AfterItemDelete', $params = array( 'Item' => & $this ) ); 2626 2627 $this->ID = 0; 2628 } 2629 else 2630 { 2631 $DB->rollback(); 2632 } 2633 2634 return $r; 2635 } 2636 2637 2638 /** 2639 * @param string 'insert' | 'update' 2640 */ 2641 function insert_update_extracats( $mode ) 2642 { 2643 global $DB; 2644 2645 $DB->begin(); 2646 2647 if( ! is_null( $this->extra_cat_IDs ) ) 2648 { // Okay the extra cats are defined: 2649 2650 if( $mode == 'update' ) 2651 { 2652 // delete previous extracats: 2653 $DB->query( 'DELETE FROM T_postcats WHERE postcat_post_ID = '.$this->ID, 'delete previous extracats' ); 2654 } 2655 2656 // insert new extracats: 2657 $query = "INSERT INTO T_postcats( postcat_post_ID, postcat_cat_ID ) VALUES "; 2658 foreach( $this->extra_cat_IDs as $extra_cat_ID ) 2659 { 2660 //echo "extracat: $extracat_ID <br />"; 2661 $query .= "( $this->ID, $extra_cat_ID ),"; 2662 } 2663 $query = substr( $query, 0, strlen( $query ) - 1 ); 2664 $DB->query( $query, 'insert new extracats' ); 2665 } 2666 2667 $DB->commit(); 2668 } 2669 2670 2671 /** 2672 * Save tags to DB 2673 * 2674 * @param string 'insert' | 'update' 2675 */ 2676 function insert_update_tags( $mode ) 2677 { 2678 global $DB; 2679 2680 if( isset( $this->tags ) ) 2681 { // Okay the tags are defined: 2682 2683 $DB->begin(); 2684 2685 if( $mode == 'update' ) 2686 { // delete previous tag associations: 2687 // Note: actual tags never get deleted 2688 $DB->query( 'DELETE FROM T_items__itemtag 2689 WHERE itag_itm_ID = '.$this->ID, 'delete previous tags' ); 2690 } 2691 2692 if( !empty($this->tags) ) 2693 { 2694 // Find the tags that are already in the DB 2695 $query = 'SELECT tag_name 2696 FROM T_items__tag 2697 WHERE tag_name IN ('.$DB->quote($this->tags).')'; 2698 $existing_tags = $DB->get_col( $query, 0, 'Find existing tags' ); 2699 2700 $new_tags = array_diff( $this->tags, $existing_tags ); 2701 //pre_dump($new_tags); 2702 2703 if( !empty( $new_tags ) ) 2704 { // insert new tags: 2705 $query = "INSERT INTO T_items__tag( tag_name ) VALUES "; 2706 foreach( $new_tags as $tag ) 2707 { 2708 $query .= '( '.$DB->quote($tag).' ),'; 2709 } 2710 $query = substr( $query, 0, strlen( $query ) - 1 ); 2711 $DB->query( $query, 'insert new tags' ); 2712 } 2713 2714 // ASSOC: 2715 $query = 'INSERT INTO T_items__itemtag( itag_itm_ID, itag_tag_ID ) 2716 SELECT '.$this->ID.', tag_ID 2717 FROM T_items__tag 2718 WHERE tag_name IN ('.$DB->quote($this->tags).')'; 2719 $DB->query( $query, 'Make tag associations!' ); 2720 } 2721 2722 $DB->commit(); 2723 } 2724 } 2725 2726 2727 /** 2728 * Increment the view count of the item directly in DB (if the item's Author is not $current_User). 2729 * 2730 * This method serves TWO purposes (that would break if we used dbupdate() ) : 2731 * - Increment the viewcount WITHOUT affecting the lastmodified date and user. 2732 * - Increment the viewcount in an ATOMIC manner (even if several hits on the same Item occur simultaneously). 2733 * 2734 * This also triggers the plugin event 'ItemViewsIncreased' if the view count has been increased. 2735 * 2736 * @return boolean Did we increase view count? 2737 */ 2738 function inc_viewcount() 2739 { 2740 global $Plugins, $DB, $current_User, $Debuglog; 2741 2742 if( isset( $current_User ) && ( $current_User->ID == $this->creator_user_ID ) ) 2743 { 2744 $Debuglog->add( 'Not incrementing view count, because viewing user is creator of the item.', 'items' ); 2745 2746 return false; 2747 } 2748 2749 $DB->query( 'UPDATE T_items__item 2750 SET post_views = post_views + 1 2751 WHERE '.$this->dbIDname.' = '.$this->ID ); 2752 2753 // Trigger event that the item's view has been increased 2754 $Plugins->trigger_event( 'ItemViewsIncreased', array( 'Item' => & $this ) ); 2755 2756 return true; 2757 } 2758 2759 2760 /** 2761 * Get the User who is assigned to the Item. 2762 * 2763 * @return User|NULL NULL if no user is assigned. 2764 */ 2765 function get_assigned_User() 2766 { 2767 if( ! isset($this->assigned_User) && isset($this->assigned_user_ID) ) 2768 { 2769 $UserCache = & get_Cache( 'UserCache' ); 2770 $this->assigned_User = & $UserCache->get_by_ID( $this->assigned_user_ID ); 2771 } 2772 2773 return $this->assigned_User; 2774 } 2775 2776 2777 /** 2778 * Get the User who created the Item. 2779 * 2780 * @return User 2781 */ 2782 function & get_creator_User() 2783 { 2784 if( is_null($this->creator_User) ) 2785 { 2786 $UserCache = & get_Cache( 'UserCache' ); 2787 $this->creator_User = & $UserCache->get_by_ID( $this->creator_user_ID ); 2788 $this->Author = & $this->creator_User; // deprecated 2789 } 2790 2791 return $this->creator_User; 2792 } 2793 2794 2795 /** 2796 * Get the Blog object for the Item. 2797 * 2798 * @return Blog 2799 */ 2800 function & get_Blog() 2801 { 2802 if( is_null($this->Blog) ) 2803 { 2804 $this->load_Blog(); 2805 } 2806 2807 return $this->Blog; 2808 } 2809 2810 2811 /** 2812 * Load the Blog object for the Item, without returning it. 2813 * 2814 * This is needed for {@link Results} object callbacks. 2815 */ 2816 function load_Blog() 2817 { 2818 if( is_null($this->Blog) ) 2819 { 2820 $BlogCache = & get_Cache( 'BlogCache' ); 2821 $this->Blog = & $BlogCache->get_by_ID( $this->blog_ID ); 2822 } 2823 } 2824 2825 2826 /** 2827 * Execute or schedule post(=after) processing tasks 2828 * 2829 * Includes notifications & pings 2830 */ 2831 function handle_post_processing() 2832 { 2833 global $Settings, $Messages; 2834 2835 $notifications_mode = $Settings->get('outbound_notifications_mode'); 2836 2837 if( $notifications_mode == 'off' ) 2838 { // Exit silently 2839 return false; 2840 } 2841 2842 if( $this->notifications_status == 'finished' ) 2843 { // pings have been done before 2844 $Messages->add( T_('Post had already pinged: skipping notifications...'), 'note' ); 2845 return false; 2846 } 2847 2848 if( $this->notifications_status != 'noreq' ) 2849 { // pings have been done before 2850 2851 // TODO: Check if issue_date has changed and reschedule 2852 2853 $Messages->add( T_('Post processing already pending...'), 'note' ); 2854 return false; 2855 } 2856 2857 if( $this->status != 'published' ) 2858 { 2859 2860 // TODO: discard any notification that may be pending! 2861 2862 $Messages->add( T_('Post not publicly published: skipping notifications...'), 'note' ); 2863 return false; 2864 } 2865 2866 if( $notifications_mode == 'immediate' ) 2867 { // We want to do the post processing immediately: 2868 // send outbound pings: 2869 $this->send_outbound_pings(); 2870 2871 // Send email notifications now! 2872 $this->send_email_notifications( false ); 2873 2874 // Record that processing has been done: 2875 $this->set( 'notifications_status', 'finished' ); 2876 } 2877 else 2878 { // We want asynchronous post processing: 2879 $Messages->add( T_('Scheduling asynchronous notifications...'), 'note' ); 2880 2881 // CREATE OBJECT: 2882 load_class( '/cron/model/_cronjob.class.php' ); 2883 $edited_Cronjob = & new Cronjob(); 2884 2885 // start datetime. We do not want to ping before the post is effectively published: 2886 $edited_Cronjob->set( 'start_datetime', $this->issue_date ); 2887 2888 // no repeat. 2889 2890 // name: 2891 $edited_Cronjob->set( 'name', sprintf( T_('Send notifications for «%s»'), strip_tags($this->title) ) ); 2892 2893 // controller: 2894 $edited_Cronjob->set( 'controller', 'cron/jobs/_post_notifications.job.php' ); 2895 2896 // params: specify which post this job is supposed to send notifications for: 2897 $edited_Cronjob->set( 'params', array( 'item_ID' => $this->ID ) ); 2898 2899 // Save cronjob to DB: 2900 $edited_Cronjob->dbinsert(); 2901 2902 // Memorize the cron job ID which is going to handle this post: 2903 $this->set( 'notifications_ctsk_ID', $edited_Cronjob->ID ); 2904 2905 // Record that processing has been scheduled: 2906 $this->set( 'notifications_status', 'todo' ); 2907 } 2908 2909 // Save the new processing status to DB 2910 $this->dbupdate(); 2911 2912 return true; 2913 } 2914 2915 2916 /** 2917 * Send email notifications to subscribed users 2918 * 2919 * @todo fp>> shall we notify suscribers of blog were this is in extra-cat? blueyed>> IMHO yes. 2920 */ 2921 function send_email_notifications( $display = true ) 2922 { 2923 global $DB, $admin_url, $debug, $Debuglog; 2924 2925 $edited_Blog = & $this->get_Blog(); 2926 2927 if( ! $edited_Blog->get_setting( 'allow_subscriptions' ) ) 2928 { // Subscriptions not enabled! 2929 return; 2930 } 2931 2932 if( $display ) 2933 { 2934 echo "<div class=\"panelinfo\">\n"; 2935 echo '<h3>', T_('Notifying subscribed users...'), "</h3>\n"; 2936 } 2937 2938 // Get list of users who want to be notfied: 2939 // TODO: also use extra cats/blogs?? 2940 $sql = 'SELECT DISTINCT user_email, user_locale 2941 FROM T_subscriptions INNER JOIN T_users ON sub_user_ID = user_ID 2942 WHERE sub_coll_ID = '.$this->blog_ID.' 2943 AND sub_items <> 0 2944 AND LENGTH(TRIM(user_email)) > 0'; 2945 $notify_list = $DB->get_results( $sql ); 2946 2947 // Preprocess list: (this comes form Comment::send_email_notifications() ) 2948 $notify_array = array(); 2949 foreach( $notify_list as $notification ) 2950 { 2951 $notify_array[$notification->user_email] = $notification->user_locale; 2952 } 2953 2954 if( empty($notify_array) ) 2955 { // No-one to notify: 2956 if( $display ) 2957 { 2958 echo '<p>', T_('No-one to notify.'), "</p>\n</div>\n"; 2959 } 2960 return false; 2961 } 2962 2963 /* 2964 * We have a list of email addresses to notify: 2965 */ 2966 $this->get_creator_User(); 2967 $mail_from = '"'.$this->creator_User->get('preferredname').'" <'.$this->creator_User->get('email').'>'; 2968 2969 // Send emails: 2970 $cache_by_locale = array(); 2971 foreach( $notify_array as $notify_email => $notify_locale ) 2972 { 2973 if( ! isset($cache_by_locale[$notify_locale]) ) 2974 { // No message for this locale generated yet: 2975 locale_temp_switch($notify_locale); 2976 2977 // Calculate length for str_pad to align labels: 2978 $pad_len = max( strlen(T_('Blog')), strlen(T_('Author')), strlen(T_('Title')), strlen(T_('Url')), strlen(T_('Content')) ); 2979 2980 $cache_by_locale[$notify_locale]['subject'] = sprintf( T_('[%s] New post: "%s"'), $edited_Blog->get('shortname'), $this->get('title') ); 2981 2982 $cache_by_locale[$notify_locale]['message'] = 2983 str_pad( T_('Blog'), $pad_len ).': '.$edited_Blog->get('shortname') 2984 .' ( '.str_replace('&', '&', $edited_Blog->gen_blogurl())." )\n" 2985 2986 .str_pad( T_('Author'), $pad_len ).': '.$this->creator_User->get('preferredname').' ('.$this->creator_User->get('login').")\n" 2987 2988 .str_pad( T_('Title'), $pad_len ).': '.$this->get('title')."\n" 2989 2990 // linked URL or "-" if empty: 2991 .str_pad( T_('Url'), $pad_len ).': '.( empty( $this->url ) ? '-' : str_replace('&', '&', $this->get('url')) )."\n" 2992 2993 .str_pad( T_('Content'), $pad_len ).': ' 2994 // TODO: We MAY want to force a short URL and avoid it to wrap on a new line in the mail which may prevent people from clicking 2995 // TODO: might get moved onto a single line, at the end of the content.. 2996 .str_replace('&', '&', $this->get_permanent_url())."\n\n" 2997 2998 .$this->get('content')."\n" 2999 3000 // Footer: 3001 ."\n-- \n" 3002 .T_('Edit/Delete').': '.$admin_url.'?ctrl=items&blog='.$this->blog_ID.'&p='.$this->ID."\n\n" 3003 3004 .T_('Edit your subscriptions/notifications').': '.str_replace('&', '&', url_add_param( $edited_Blog->gen_blogurl(), 'disp=subs' ) )."\n"; 3005 3006 locale_restore_previous(); 3007 } 3008 3009 if( $display ) echo T_('Notifying:').$notify_email."<br />\n"; 3010 if( $debug >= 2 ) 3011 { 3012 echo "<p>Sending notification to $notify_email:<pre>$cache_by_locale[$notify_locale]['message']</pre>"; 3013 } 3014 3015 send_mail( $notify_email, $cache_by_locale[$notify_locale]['subject'], $cache_by_locale[$notify_locale]['message'], $mail_from ); 3016 } 3017 3018 if( $display ) echo '<p>', T_('Done.'), "</p>\n</div>\n"; 3019 } 3020 3021 3022 /** 3023 * Send outbound pings for a post 3024 */ 3025 function send_outbound_pings() 3026 { 3027 global $Plugins, $baseurl, $Messages; 3028 3029 load_funcs('_ext/xmlrpc/_xmlrpc.php' ); 3030 3031 $this->load_Blog(); 3032 $ping_plugins = array_unique(explode(',', $this->Blog->get_setting('ping_plugins'))); 3033 3034 if( preg_match( '#^http://localhost[/:]#', $baseurl) 3035 || preg_match( '~^\w+://[^/]+\.local/~', $baseurl ) /* domain ending in ".local" */ ) 3036 { 3037 $Messages->add( T_('Skipping pings (Running on localhost).'), 'note' ); 3038 } 3039 else foreach( $ping_plugins as $plugin_code ) 3040 { 3041 $Plugin = & $Plugins->get_by_code($plugin_code); 3042 3043 if( $Plugin ) 3044 { 3045 $Messages->add( sprintf(T_('Pinging %s...'), $Plugin->ping_service_name), 'note' ); 3046 $params = array( 'Item' => & $this, 'xmlrpcresp' => NULL, 'display' => false ); 3047 3048 $r = $Plugin->ItemSendPing( $params ); 3049 3050 if( isset($params['xmlrpcresp']) && is_a($params['xmlrpcresp'], 'xmlrpcresp') ) 3051 { 3052 // dh> TODO: let xmlrpc_displayresult() handle $Messages (e.g. "error", but should be connected/after the "Pinging %s..." from above) 3053 ob_start(); 3054 xmlrpc_displayresult( $params['xmlrpcresp'], true ); 3055 $Messages->add( ob_get_contents(), 'note' ); 3056 ob_end_clean(); 3057 } 3058 } 3059 } 3060 } 3061 3062 3063 /** 3064 * Get a member param by its name 3065 * 3066 * @param mixed Name of parameter 3067 * @return mixed Value of parameter 3068 */ 3069 function get( $parname ) 3070 { 3071 global $post_statuses; 3072 3073 switch( $parname ) 3074 { 3075 case 't_author': 3076 // Text: author 3077 $this->get_creator_User(); 3078 return $this->creator_User->get( 'preferredname' ); 3079 3080 case 't_assigned_to': 3081 // Text: assignee 3082 if( ! $this->get_assigned_User() ) 3083 { 3084 return ''; 3085 } 3086 return $this->assigned_User->get( 'preferredname' ); 3087 3088 case 't_status': 3089 // Text status: 3090 return T_( $post_statuses[$this->status] ); 3091 3092 case 't_extra_status': 3093 $ItemStatusCache = & get_Cache( 'ItemStatusCache' ); 3094 if( ! ($Element = & $ItemStatusCache->get_by_ID( $this->pst_ID, true, false ) ) ) 3095 { // No status: 3096 return ''; 3097 } 3098 return $Element->get_name(); 3099 3100 case 't_type': 3101 // Item type (name): 3102 if( empty($this->ptyp_ID) ) 3103 { 3104 return ''; 3105 } 3106 3107 $ItemTypeCache = & get_Cache( 'ItemTypeCache' ); 3108 $type_Element = & $ItemTypeCache->get_by_ID( $this->ptyp_ID ); 3109 return $type_Element->get_name(); 3110 3111 case 't_priority': 3112 return $this->priorities[ $this->priority ]; 3113 3114 case 'pingsdone': 3115 // Deprecated by fp 2006-08-21 3116 return ($this->post_notifications_status == 'finished'); 3117 } 3118 3119 return parent::get( $parname ); 3120 } 3121 3122 3123 /** 3124 * Assign the item to the first category we find in the requested collection 3125 * 3126 * @param integer $collection_ID 3127 */ 3128 function assign_to_first_cat_for_collection( $collection_ID ) 3129 { 3130 global $DB; 3131 3132 // Get the first category ID for the collection ID param 3133 $cat_ID = $DB->get_var( ' 3134 SELECT cat_ID 3135 FROM T_categories 3136 WHERE cat_blog_ID = '.$collection_ID.' 3137 ORDER BY cat_ID ASC 3138 LIMIT 1' ); 3139 3140 // Set to the item the first category we got 3141 $this->set( 'main_cat_ID', $cat_ID ); 3142 } 3143 3144 3145 /** 3146 * Get the list of renderers for this Item. 3147 * @return array 3148 */ 3149 function get_renderers() 3150 { 3151 return explode( '.', $this->renderers ); 3152 } 3153 3154 3155 /** 3156 * Get the list of validated renderers for this Item. This includes stealth plugins etc. 3157 * @return array List of validated renderer codes 3158 */ 3159 function get_renderers_validated() 3160 { 3161 if( ! isset($this->renderers_validated) ) 3162 { 3163 $Plugins_admin = & get_Cache('Plugins_admin'); 3164 $this->renderers_validated = $Plugins_admin->validate_renderer_list( $this->get_renderers() ); 3165 } 3166 return $this->renderers_validated; 3167 } 3168 3169 3170 /** 3171 * Add a renderer (by code) to the Item. 3172 * @param string Renderer code to add for this item 3173 * @return boolean True if renderers have changed 3174 */ 3175 function add_renderer( $renderer_code ) 3176 { 3177 $renderers = $this->get_renderers(); 3178 if( in_array( $renderer_code, $renderers ) ) 3179 { 3180 return false; 3181 } 3182 3183 $renderers[] = $renderer_code; 3184 $this->set_renderers( $renderers ); 3185 3186 $this->renderers_validated = NULL; 3187 //echo 'Added renderer '.$renderer_code; 3188 } 3189 3190 3191 /** 3192 * Remove a renderer (by code) from the Item. 3193 * @param string Renderer code to remove for this item 3194 * @return boolean True if renderers have changed 3195 */ 3196 function remove_renderer( $renderer_code ) 3197 { 3198 $r = false; 3199 $renderers = $this->get_renderers(); 3200 while( ( $key = array_search( $renderer_code, $renderers ) ) !== false ) 3201 { 3202 $r = true; 3203 unset($renderers[$key]); 3204 } 3205 3206 if( $r ) 3207 { 3208 $this->set_renderers( $renderers ); 3209 $this->renderers_validated = NULL; 3210 //echo 'Removed renderer '.$renderer_code; 3211 } 3212 return $r; 3213 } 3214 } 3215 3216 3217 /* 3218 * $Log: _item.class.php,v $ 3219 * Revision 1.15 2007/11/04 01:10:57 fplanque 3220 * skin cleanup continued 3221 * 3222 * Revision 1.14 2007/11/03 23:54:38 fplanque 3223 * skin cleanup continued 3224 * 3225 * Revision 1.13 2007/11/03 21:04:26 fplanque 3226 * skin cleanup 3227 * 3228 * Revision 1.12 2007/11/02 01:54:46 fplanque 3229 * comment ratings 3230 * 3231 * Revision 1.11 2007/09/13 19:16:14 fplanque 3232 * feedback_link() cleanup 3233 * 3234 * Revision 1.10 2007/09/13 02:37:22 fplanque 3235 * special cases 3236 * 3237 * Revision 1.9 2007/09/11 23:10:39 fplanque 3238 * translation updates 3239 * 3240 * Revision 1.8 2007/09/10 14:53:04 fplanque 3241 * cron fix 3242 * 3243 * Revision 1.7 2007/09/09 12:51:58 fplanque 3244 * cleanup 3245 * 3246 * Revision 1.6 2007/09/09 09:15:59 yabs 3247 * validation 3248 * 3249 * Revision 1.5 2007/09/08 19:31:28 fplanque 3250 * cleanup of XML feeds for comments on individual posts. 3251 * 3252 * Revision 1.4 2007/09/04 22:16:33 fplanque 3253 * in context editing of posts 3254 * 3255 * Revision 1.3 2007/08/28 02:43:40 waltercruz 3256 * Template function to get the rss link to the feeds of the comments on each post 3257 * 3258 * Revision 1.2 2007/07/03 23:21:32 blueyed 3259 * Fixed includes/requires in/for tests 3260 * 3261 * Revision 1.1 2007/06/25 11:00:24 fplanque 3262 * MODULES (refactored MVC) 3263 * 3264 * Revision 1.184 2007/06/24 22:26:34 fplanque 3265 * improved feedback template 3266 * 3267 * Revision 1.183 2007/06/21 00:44:37 fplanque 3268 * linkblog now a widget 3269 * 3270 * Revision 1.182 2007/06/20 21:42:13 fplanque 3271 * implemented working widget/plugin params 3272 * 3273 * Revision 1.180 2007/06/18 20:59:55 fplanque 3274 * do not display link to comments if comments are disabled 3275 * 3276 * Revision 1.179 2007/06/13 23:29:02 fplanque 3277 * minor 3278 * 3279 * Revision 1.178 2007/06/11 01:55:57 fplanque 3280 * level based user permissions 3281 * 3282 * Revision 1.177 2007/05/28 15:18:30 fplanque 3283 * cleanup 3284 * 3285 * Revision 1.176 2007/05/28 01:33:22 fplanque 3286 * permissions/fixes 3287 * 3288 * Revision 1.175 2007/05/27 00:35:26 fplanque 3289 * tag display + tag filtering 3290 * 3291 * Revision 1.174 2007/05/20 01:01:35 fplanque 3292 * make trackback silent when it should be 3293 * 3294 * Revision 1.173 2007/05/14 02:47:23 fplanque 3295 * (not so) basic Tags framework 3296 * 3297 * Revision 1.172 2007/05/13 22:02:07 fplanque 3298 * removed bloated $object_def 3299 * 3300 * Revision 1.171 2007/04/26 00:11:11 fplanque 3301 * (c) 2007 3302 * 3303 * Revision 1.170 2007/04/15 13:34:36 blueyed 3304 * Fixed default $url generation in page_links() 3305 * 3306 * Revision 1.169 2007/04/05 22:57:33 fplanque 3307 * Added hook: UnfilterItemContents 3308 * 3309 * Revision 1.168 2007/03/31 22:46:46 fplanque 3310 * FilterItemContent event 3311 * 3312 * Revision 1.167 2007/03/26 12:59:18 fplanque 3313 * basic pages support 3314 * 3315 * Revision 1.166 2007/03/25 10:19:30 fplanque 3316 * cleanup 3317 * 3318 * Revision 1.165 2007/03/24 20:41:16 fplanque 3319 * Refactored a lot of the link junk. 3320 * Made options blog specific. 3321 * Some junk still needs to be cleaned out. Will do asap. 3322 * 3323 * Revision 1.164 2007/03/19 23:59:32 fplanque 3324 * minor 3325 * 3326 * Revision 1.163 2007/03/18 03:43:19 fplanque 3327 * EXPERIMENTAL 3328 * Splitting Item/ItemLight and ItemList/ItemListLight 3329 * Goal: Handle Items with less footprint than with their full content 3330 * (will be even worse with multiple languages/revisions per Item) 3331 * 3332 * Revision 1.162 2007/03/11 23:57:07 fplanque 3333 * item editing: allow setting to 'redirected' status 3334 * 3335 * Revision 1.161 2007/03/06 12:18:08 fplanque 3336 * got rid of dirty Item::content() 3337 * Advantage: the more link is now independant. it can be put werever people want it 3338 * 3339 * Revision 1.160 2007/03/05 04:52:42 fplanque 3340 * better precision for viewcounts 3341 * 3342 * Revision 1.159 2007/03/05 04:49:17 fplanque 3343 * better precision for viewcounts 3344 * 3345 * Revision 1.158 2007/03/05 02:13:26 fplanque 3346 * improved dashboard 3347 * 3348 * Revision 1.157 2007/03/05 01:47:50 fplanque 3349 * splitting up Item::content() - proof of concept. 3350 * needs to be optimized. 3351 * 3352 * Revision 1.156 2007/03/03 01:14:11 fplanque 3353 * new methods for navigating through posts in single item display mode 3354 * 3355 * Revision 1.155 2007/03/02 04:40:38 fplanque 3356 * fixed/commented a lot of stuff with the feeds 3357 * 3358 * Revision 1.154 2007/03/02 03:09:12 fplanque 3359 * rss length doesn't make sense since it doesn't apply to html format anyway. 3360 * clean solutionwould be to handle an "excerpt" field separately 3361 * 3362 * Revision 1.153 2007/02/23 19:16:07 blueyed 3363 * MFB: Fixed handling of Item::content for pre-rendering (it gets passed by reference!) 3364 * 3365 * Revision 1.152 2007/02/18 22:51:26 waltercruz 3366 * Fixing a little confusion with quotes and string concatenation 3367 * 3368 * Revision 1.151 2007/02/08 03:45:40 waltercruz 3369 * Changing double quotes to single quotes 3370 * 3371 * Revision 1.150 2007/02/05 13:32:49 waltercruz 3372 * Changing double quotes to single quotes 3373 * 3374 * Revision 1.149 2007/01/26 04:52:53 fplanque 3375 * clean comment popups (skins 2.0) 3376 * 3377 * Revision 1.148 2007/01/26 02:12:06 fplanque 3378 * cleaner popup windows 3379 * 3380 * Revision 1.147 2007/01/23 03:46:24 fplanque 3381 * cleaned up presentation 3382 * 3383 * Revision 1.146 2007/01/19 10:45:42 fplanque 3384 * images everywhere :D 3385 * At this point the photoblogging code can be considered operational. 3386 * 3387 * Revision 1.145 2007/01/11 19:29:50 blueyed 3388 * Fixed E_NOTICE when using the "excerpt" feature 3389 * 3390 * Revision 1.144 2006/12/26 00:08:29 fplanque 3391 * wording 3392 * 3393 * Revision 1.143 2006/12/21 22:35:28 fplanque 3394 * No regression. But a change in usage. The more link must be configured in the skin. 3395 * Renderers cannot side-effect on the more tag any more and that actually makes the whole thing safer. 3396 * 3397 * Revision 1.142 2006/12/20 13:57:34 blueyed 3398 * TODO about regression because of pre-rendering and the <!--more--> tag 3399 * 3400 * Revision 1.141 2006/12/18 13:31:12 fplanque 3401 * fixed broken more tag 3402 * 3403 * Revision 1.140 2006/12/16 01:30:46 fplanque 3404 * Setting to allow/disable email subscriptions on a per blog basis 3405 * 3406 * Revision 1.139 2006/12/15 22:59:05 fplanque 3407 * doc 3408 * 3409 * Revision 1.138 2006/12/14 22:26:31 blueyed 3410 * Fixed E_NOTICE and displaying of pings into $Messages (though "hackish") 3411 * 3412 * Revision 1.137 2006/12/12 02:53:56 fplanque 3413 * Activated new item/comments controllers + new editing navigation 3414 * Some things are unfinished yet. Other things may need more testing. 3415 * 3416 * Revision 1.136 2006/12/07 23:13:11 fplanque 3417 * @var needs to have only one argument: the variable type 3418 * Otherwise, I can't code! 3419 * 3420 * Revision 1.135 2006/12/06 23:55:53 fplanque 3421 * hidden the dead body of the sidebar plugin + doc 3422 * 3423 * Revision 1.134 2006/12/05 14:28:29 blueyed 3424 * Fixed wordcount==0 handling; has been saved as NULL 3425 * 3426 * Revision 1.133 2006/12/05 06:38:40 blueyed 3427 * doc 3428 * 3429 * Revision 1.132 2006/12/05 00:39:56 fplanque 3430 * fixed some more permalinks/archive links 3431 * 3432 * Revision 1.131 2006/12/05 00:34:39 blueyed 3433 * Implemented custom "None" option text in DataObjectCache; Added for $ItemStatusCache, $GroupCache, UserCache and BlogCache; Added custom text for Item::priority_options() 3434 * 3435 * Revision 1.130 2006/12/04 20:52:40 blueyed 3436 * typo 3437 * 3438 * Revision 1.129 2006/12/04 19:57:58 fplanque 3439 * How often must I fix the weekly archives until they stop bugging me? 3440 * 3441 * Revision 1.128 2006/12/04 19:41:11 fplanque 3442 * Each blog can now have its own "archive mode" settings 3443 * 3444 * Revision 1.127 2006/12/03 18:15:32 fplanque 3445 * doc 3446 * 3447 * Revision 1.126 2006/12/01 20:04:31 blueyed 3448 * Renamed Plugins_admin::validate_list() to validate_renderer_list() 3449 * 3450 * Revision 1.125 2006/12/01 19:46:42 blueyed 3451 * Moved Plugins::validate_list() to Plugins_admin class; added stub in Plugins, because at least the starrating_plugin uses it 3452 * 3453 * Revision 1.124 2006/11/28 20:04:11 blueyed 3454 * No edit link, if ID==0 to avoid confusion in preview, see http://forums.b2evolution.net/viewtopic.php?p=47422#47422 3455 * 3456 * Revision 1.123 2006/11/24 18:27:24 blueyed 3457 * Fixed link to b2evo CVS browsing interface in file docblocks 3458 * 3459 * Revision 1.122 2006/11/22 20:48:58 blueyed 3460 * Added Item::get_Chapters() and Item::get_main_Chapter(); refactorized 3461 * 3462 * Revision 1.121 2006/11/22 20:12:18 blueyed 3463 * Use $format param in Item::categories() 3464 * 3465 * Revision 1.120 2006/11/19 22:17:42 fplanque 3466 * minor / doc 3467 * 3468 * Revision 1.119 2006/11/19 16:07:31 blueyed 3469 * Fixed saving empty renderers list. This should also fix the saving of "default" instead of the explicit renderer list 3470 * 3471 * Revision 1.118 2006/11/17 18:36:23 blueyed 3472 * dbchanges param for AfterItemUpdate, AfterItemInsert, AfterCommentUpdate and AfterCommentInsert 3473 * 3474 * Revision 1.117 2006/11/13 20:49:52 fplanque 3475 * doc/cleanup :/ 3476 * 3477 * Revision 1.116 2006/11/10 20:14:11 blueyed 3478 * doc, fix 3479 * 3480 * Revision 1.115 2006/11/02 16:12:49 blueyed 3481 * MFB 3482 * 3483 * Revision 1.114 2006/11/02 16:01:00 blueyed 3484 * doc 3485 * 3486 * Revision 1.113 2006/10/29 18:33:23 blueyed 3487 * doc fix 3488 * 3489 * Revision 1.112 2006/10/23 22:19:02 blueyed 3490 * Fixed/unified encoding of redirect_to param. Use just rawurlencode() and no funky & replacements 3491 * 3492 * Revision 1.111 2006/10/18 00:03:51 blueyed 3493 * Some forgotten url_rel_to_same_host() additions 3494 */ 3495 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Thu Nov 29 23:58:50 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |