b2evolution PHP Cross Reference Blogging Systems

Source: /inc/items/model/_item.class.php - 6549 lines - 186390 bytes - Summary - Text - Print

Description: This file implements the Item class. This file is part of the evoCore framework - {@link http://evocore.net/} See also {@link http://sourceforge.net/projects/evocms/}.

   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-2014 by Francois Planque - {@link http://fplanque.com/}
   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 6255 2014-03-19 05:58:21Z yura $
  35   */
  36  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  37  
  38  /**
  39   * Includes:
  40   */
  41  load_funcs( 'items/model/_item.funcs.php');
  42  load_class( 'slugs/model/_slug.class.php', 'Slug' );
  43  load_class( 'links/model/_linkowner.class.php', 'LinkOwner' );
  44  load_class( 'links/model/_linkitem.class.php', 'LinkItem' );
  45  
  46  /**
  47   * Item Class
  48   *
  49   * @package evocore
  50   */
  51  class Item extends ItemLight
  52  {
  53      /**
  54       * Creation date (timestamp)
  55       * @var integer
  56       */
  57      var $datecreated;
  58  
  59      /**
  60       * The User who has created the Item (lazy-filled).
  61       * @see Item::get_creator_User()
  62       * @see Item::set_creator_User()
  63       * @var User
  64       * @access protected
  65       */
  66      var $creator_User;
  67  
  68  
  69      /**
  70       * The User who has edited the Item last time (lazy-filled).
  71       * @see Item::get_lastedit_User()
  72       * @var User
  73       * @access protected
  74       */
  75      var $lastedit_User;
  76  
  77  
  78      /**
  79       * ID of the user who has edited the Item last time
  80       * @var integer
  81       */
  82      var $lastedit_user_ID;
  83  
  84      /**
  85       * Date when comments or links were added/edited/deleted for this Item last time (timestamp)
  86       * @see Item::update_last_touched_date()
  87       * @var integer
  88       */
  89      var $last_touched_ts;
  90  
  91  
  92      /**
  93       * The latest Comment on this Item (lazy-filled).
  94       * @see Item::get_latest_Comment()
  95       * @var Comment
  96       * @access protected
  97       */
  98      var $latest_Comment;
  99  
 100  
 101      /**
 102       * @deprecated by {@link $creator_User}
 103       * @var User
 104       */
 105      var $Author;
 106  
 107  
 108      /**
 109       * ID of the user that created the item
 110       * @var integer
 111       */
 112      var $creator_user_ID;
 113  
 114  
 115      /**
 116       * Login of the user that created the item (lazy-filled)
 117       * @var string
 118       */
 119      var $creator_user_login;
 120  
 121  
 122      /**
 123       * The assigned User to the item.
 124       * Can be NULL
 125       * @see Item::get_assigned_User()
 126       * @see Item::assign_to()
 127       *
 128       * @var User
 129       * @access protected
 130       */
 131      var $assigned_User;
 132  
 133      /**
 134       * ID of the user that created the item
 135       * Can be NULL
 136       *
 137       * @var integer
 138       */
 139      var $assigned_user_ID;
 140  
 141      /**
 142       * The visibility status of the item.
 143       *
 144       * 'published', 'community', 'deprecated', 'protected', 'private', 'review' or 'draft'
 145       *
 146       * @var string
 147       */
 148      var $status;
 149      /**
 150       * Locale code for the Item content.
 151       *
 152       * Examples: en-US, zh-CN-utf-8
 153       *
 154       * @var string
 155       */
 156      var $locale;
 157  
 158      var $content;
 159  
 160      var $titletag;
 161  
 162      /**
 163       * Lazy filled, use split_page()
 164       */
 165      var $content_pages = NULL;
 166  
 167  
 168      var $wordcount;
 169      /**
 170       * The list of renderers, imploded by '.'.
 171       * @var string
 172       * @access protected
 173       */
 174      var $renderers;
 175      /**
 176       * Comments status
 177       *
 178       * "open", "disabled" or "closed
 179       *
 180       * @var string
 181       */
 182      var $comment_status;
 183  
 184      var $pst_ID;
 185      var $datedeadline = '';
 186      var $priority;
 187  
 188      /**
 189       * @var float
 190       */
 191      var $order;
 192      /**
 193       * @var boolean
 194       */
 195      var $featured;
 196  
 197      /**
 198       * Have post processing notifications been handled?
 199       * @var string
 200       */
 201      var $notifications_status;
 202      /**
 203       * Which cron task is responsible for handling notifications?
 204       * @var integer
 205       */
 206      var $notifications_ctsk_ID;
 207  
 208      /**
 209       * array of IDs or NULL if we don't know...
 210       *
 211       * @var array
 212       */
 213      var $extra_cat_IDs = NULL;
 214  
 215      /**
 216       * Array of tags (strings)
 217       *
 218       * Lazy loaded.
 219       * @see Item::get_tags()
 220       * @access protected
 221       * @var array
 222       */
 223      var $tags = NULL;
 224  
 225      /**
 226       * Has the publish date been explicitly set?
 227        *
 228       * @var integer
 229       */
 230      var $dateset = 1;
 231  
 232      var $priorities;
 233  
 234      /**
 235       * @access protected
 236       * @see Item::get_excerpt()
 237       * @var string
 238       */
 239      var $excerpt;
 240  
 241      /**
 242       * Is the excerpt autogenerated?
 243       * @access protected
 244       * @var boolean
 245       */
 246      var $excerpt_autogenerated = true;
 247  
 248      /**
 249       * Location IDs
 250       * @var integer
 251       */
 252      var $ctry_ID = NULL;
 253      var $rgn_ID = NULL;
 254      var $subrg_ID = NULL;
 255      var $city_ID = NULL;
 256  
 257      /**
 258       * Additional settings for the items.  lazy filled.
 259        *
 260       * @see Item::get_setting()
 261       * @see Item::set_setting()
 262       * @see Item::load_ItemSettings()
 263       * Any non vital params should go into there.
 264       *
 265       * @var ItemSettings
 266       */
 267      var $ItemSettings;
 268  
 269      /**
 270       * Constructor
 271       *
 272       * @param object table Database row
 273       * @param string
 274       * @param string
 275       * @param string
 276       * @param string for derived classes
 277       * @param string datetime field name
 278       * @param string datetime field name
 279       * @param string User ID field name
 280       * @param string User ID field name
 281       */
 282  	function Item( $db_row = NULL, $dbtable = 'T_items__item', $dbprefix = 'post_', $dbIDname = 'post_ID', $objtype = 'Item',
 283                     $datecreated_field = 'datecreated', $datemodified_field = 'datemodified',
 284                     $creator_field = 'creator_user_ID', $lasteditor_field = 'lastedit_user_ID' )
 285      {
 286          global $localtimenow, $default_locale, $current_User;
 287  
 288          $this->priorities = array(
 289                  1 => /* TRANS: Priority name */ T_('1 - Highest'),
 290                  2 => /* TRANS: Priority name */ T_('2 - High'),
 291                  3 => /* TRANS: Priority name */ T_('3 - Medium'),
 292                  4 => /* TRANS: Priority name */ T_('4 - Low'),
 293                  5 => /* TRANS: Priority name */ T_('5 - Lowest'),
 294              );
 295  
 296          // Call parent constructor:
 297          parent::ItemLight( $db_row, $dbtable, $dbprefix, $dbIDname, $objtype,
 298                     $datecreated_field, $datemodified_field,
 299                     $creator_field, $lasteditor_field );
 300  
 301          if( is_null($db_row) )
 302          { // New item:
 303              if( isset($current_User) )
 304              { // use current user as default, if available (which won't be the case during install)
 305                  $this->creator_user_login = $current_User->login;
 306                  $this->set_creator_User( $current_User );
 307              }
 308              $this->set( 'dateset', 0 );    // Date not explicitly set yet
 309              $this->set( 'notifications_status', 'noreq' );
 310              // Set the renderer list to 'default' will trigger all 'opt-out' renderers:
 311              $this->set( 'renderers', array('default') );
 312              // we prolluy don't need this: $this->set( 'status', 'published' );
 313              $this->set( 'locale', $default_locale );
 314              $this->set( 'priority', 3 );
 315              $this->set( 'ptyp_ID', 1 /* Post */ );
 316          }
 317          else
 318          {
 319              $this->datecreated = $db_row->post_datecreated;                     // When Item was created in the system
 320              $this->last_touched_ts = $db_row->post_last_touched_ts;        // When Item received last visible change (edit, comment, etc.)
 321              $this->creator_user_ID = $db_row->post_creator_user_ID;     // Needed for history display
 322              $this->lastedit_user_ID = $db_row->post_lastedit_user_ID; // Needed for history display
 323              $this->assigned_user_ID = $db_row->post_assigned_user_ID;
 324              $this->dateset = $db_row->post_dateset;
 325              $this->status = $db_row->post_status;
 326              $this->content = $db_row->post_content;
 327              $this->titletag = $db_row->post_titletag;
 328              $this->pst_ID = $db_row->post_pst_ID;
 329              $this->datedeadline = $db_row->post_datedeadline;
 330              $this->priority = $db_row->post_priority;
 331              $this->locale = $db_row->post_locale;
 332              $this->wordcount = $db_row->post_wordcount;
 333              $this->notifications_status = $db_row->post_notifications_status;
 334              $this->notifications_ctsk_ID = $db_row->post_notifications_ctsk_ID;
 335              $this->comment_status = $db_row->post_comment_status;            // Comments status
 336              $this->order = $db_row->post_order;
 337              $this->featured = $db_row->post_featured;
 338  
 339              // echo 'renderers=', $db_row->post_renderers;
 340              $this->renderers = $db_row->post_renderers;
 341  
 342              $this->views = $db_row->post_views;
 343  
 344              $this->excerpt = $db_row->post_excerpt;
 345              $this->excerpt_autogenerated = $db_row->post_excerpt_autogenerated;
 346  
 347              // Location
 348  
 349              if ( ! empty ( $db_row->post_ctry_ID ) )
 350              {
 351                  $this->ctry_ID = $db_row->post_ctry_ID;
 352              }
 353  
 354              if ( ! empty ( $db_row->post_rgn_ID ) )
 355              {
 356                  $this->rgn_ID = $db_row->post_rgn_ID;
 357              }
 358  
 359              if ( ! empty ( $db_row->post_subrg_ID ) )
 360              {
 361                  $this->subrg_ID = $db_row->post_subrg_ID;
 362              }
 363  
 364              if ( ! empty ( $db_row->post_city_ID ) )
 365              {
 366                  $this->city_ID = $db_row->post_city_ID;
 367              }
 368  
 369          }
 370  
 371          modules_call_method( 'constructor_item', array( 'Item' => & $this ) );
 372      }
 373  
 374  
 375      /**
 376       * Set creator user
 377       *
 378       * @param string login
 379       */
 380  	function set_creator_by_login( $login )
 381      {
 382          $UserCache = & get_UserCache();
 383          if( ( $creator_User = &$UserCache->get_by_login( $login ) ) !== false )
 384          {
 385              $this->set( $this->creator_field, $creator_User->ID );
 386          }
 387      }
 388  
 389  
 390      /**
 391       * @todo use extended dbchange instead of set_param...
 392       * @todo Normalize to set_assigned_User!?
 393       */
 394  	function assign_to( $user_ID, $dbupdate = true /* BLOAT!? */ )
 395      {
 396          // echo 'assigning user #'.$user_ID;
 397          if( ! empty($user_ID) )
 398          {
 399              if( $dbupdate )
 400              { // Record ID for DB:
 401                  $this->set_param( 'assigned_user_ID', 'number', $user_ID, true );
 402              }
 403              else
 404              {
 405                  $this->assigned_user_ID = $user_ID;
 406              }
 407              $UserCache = & get_UserCache();
 408              $this->assigned_User = & $UserCache->get_by_ID( $user_ID );
 409          }
 410          else
 411          {
 412              // fp>> DO NOT set (to null) immediately OR it may KILL the current User object (big problem if it's the Current User)
 413              unset( $this->assigned_User );
 414              if( $dbupdate )
 415              { // Record ID for DB:
 416                  $this->set_param( 'assigned_user_ID', 'number', NULL, true );
 417              }
 418              else
 419              {
 420                  $this->assigned_User = NULL;
 421              }
 422              $this->assigned_user_ID = NULL;
 423          }
 424  
 425      }
 426  
 427  
 428      /**
 429       * Template function: display author/creator of item
 430       *
 431       */
 432  	function author( $params = array() )
 433      {
 434          // Make sure we are not missing any param:
 435          $params = array_merge( array(
 436                  'profile_tab'    => 'user',
 437                  'before'         => ' ',
 438                  'after'          => ' ',
 439                  'format'         => 'htmlbody',
 440                  'link_to'        => 'userpage',
 441                  'link_text'      => 'preferredname', // avatar | only_avatar | login | nickname | firstname | lastname | fullname | preferredname
 442                  'link_rel'       => '',
 443                  'link_class'     => '',
 444                  'thumb_size'     => 'crop-top-32x32',
 445                  'thumb_class'    => '',
 446                  'thumb_zoomable' => false,
 447              ), $params );
 448  
 449          // Load User
 450          $this->get_creator_User();
 451  
 452          $r = $this->creator_User->get_identity_link( $params );
 453  
 454          echo $params['before'].$r.$params['after'];
 455      }
 456  
 457  
 458      /**
 459       * Template function: display user who edited the item last time
 460       *
 461       */
 462  	function lastedit_user( $params = array() )
 463      {
 464          // Make sure we are not missing any param:
 465          $params = array_merge( array(
 466                  'profile_tab'    => 'user',
 467                  'before'         => ' ',
 468                  'after'          => ' ',
 469                  'format'         => 'htmlbody',
 470                  'link_to'        => 'userpage',
 471                  'link_text'      => 'preferredname', // avatar | only_avatar | login | nickname | firstname | lastname | fullname | preferredname
 472                  'link_rel'       => '',
 473                  'link_class'     => '',
 474                  'thumb_size'     => 'crop-top-32x32',
 475                  'thumb_class'    => '',
 476                  'thumb_zoomable' => false,
 477              ), $params );
 478  
 479          // Load User
 480          $this->get_lastedit_User();
 481  
 482          if( $this->lastedit_User )
 483          {    // Get a link to user profile page
 484              $r = $this->lastedit_User->get_identity_link( $params );
 485          }
 486          else
 487          {    // User was deleted
 488              $r = T_('(deleted user)');
 489          }
 490  
 491          echo $params['before'].$r.$params['after'];
 492      }
 493  
 494  
 495      /**
 496       * Load data from Request form fields.
 497       *
 498       * This requires the blog (e.g. {@link $blog_ID} or {@link $main_cat_ID} to be set).
 499       *
 500       * @param boolean true if we are returning to edit mode (new, switchtab...)
 501       * @return boolean true if loaded data seems valid.
 502       */
 503  	function load_from_Request( $editing = false, $creating = false )
 504      {
 505          global $default_locale, $current_User, $localtimenow;
 506          global $posttypes_reserved_IDs, $item_typ_ID;
 507  
 508          // LOCALE:
 509          if( param( 'post_locale', 'string', NULL ) !== NULL )
 510          {
 511              $this->set_from_Request( 'locale' );
 512          }
 513  
 514          // TYPE:
 515          if( param( 'post_type', 'string', NULL ) !== NULL )
 516          { // Set type ID from request type code, happens when e.g. we add an intro from manual skin by url: /blog6.php?disp=edit&cat=25&post_type=intro-cat
 517              $this->set( 'ptyp_ID', get_item_type_ID( get_param( 'post_type' ) ) );
 518          }
 519          elseif( param( 'item_typ_ID', 'integer', NULL ) !== NULL )
 520          { // fp> when does this happen?
 521              // yura>fp: this happens on submit expert form
 522              $this->set_from_Request( 'ptyp_ID', 'item_typ_ID' );
 523  
 524              if( in_array( $item_typ_ID, $posttypes_reserved_IDs ) )
 525              {
 526                  param_error( 'item_typ_ID', T_( 'This post type is reserved and cannot be used. Please choose another one.' ), '' );
 527              }
 528          }
 529  
 530          // URL associated with Item:
 531          if( param( 'post_url', 'string', NULL ) !== NULL )
 532          {
 533              param_check_url( 'post_url', 'posting', '' );
 534              $this->set_from_Request( 'url' );
 535          }
 536  
 537          if( $this->status == 'redirected' && empty($this->url) )
 538          {
 539              // Note: post_url is not part of the simple form, so this message can be a little bit awkward there
 540              param_error( 'post_url', T_('If you want to redirect this post, you must specify an URL! (Expert mode)') );
 541          }
 542  
 543          // ISSUE DATE / TIMESTAMP:
 544          $this->load_Blog();
 545          if( $current_User->check_perm( 'blog_edit_ts', 'edit', false, $this->Blog->ID ) )
 546          {
 547              $this->set( 'dateset', param( 'item_dateset', 'integer', 0 ) );
 548  
 549              if( $editing || $this->dateset == 1 )
 550              { // We can use user date:
 551                  if( param_date( 'item_issue_date', T_('Please enter a valid issue date.'), true )
 552                      && param_time( 'item_issue_time' ) )
 553                  { // only set it, if a (valid) date and time was given:
 554                      $this->set( 'issue_date', form_date( get_param( 'item_issue_date' ), get_param( 'item_issue_time' ) ) ); // TODO: cleanup...
 555                  }
 556              }
 557              elseif( $this->dateset == 0 )
 558              {    // Set date to NOW:
 559                  $this->set( 'issue_date', date('Y-m-d H:i:s', $localtimenow) );
 560              }
 561          }
 562  
 563          // DEADLINE:
 564          if( param_date( 'item_deadline', T_('Please enter a valid deadline.'), false, NULL ) !== NULL ) {
 565              $this->set_from_Request( 'datedeadline', 'item_deadline', true );
 566          }
 567  
 568          // SLUG:
 569          if( param( 'post_urltitle', 'string', NULL ) !== NULL ) {
 570              $this->set_from_Request( 'urltitle' );
 571          }
 572  
 573          // <title> TAG:
 574          if( param( 'titletag', 'string', NULL ) !== NULL ) {
 575              $this->set_from_Request( 'titletag', 'titletag' );
 576          }
 577  
 578          // <meta> DESC:
 579          if( param( 'metadesc', 'string', NULL ) !== NULL ) {
 580              $this->set_setting( 'post_metadesc', get_param( 'metadesc' ) );
 581          }
 582  
 583          // <meta> KEYWORDS:
 584          if( param( 'custom_headers', 'string', NULL ) !== NULL ) {
 585              $this->set_setting( 'post_custom_headers', get_param( 'custom_headers' ) );
 586          }
 587  
 588          // TAGS:
 589          if( param( 'item_tags', 'string', NULL ) !== NULL ) {
 590              $this->set_tags_from_string( get_param('item_tags') );
 591              // pre_dump( $this->tags );
 592          }
 593  
 594          // WORKFLOW stuff:
 595          param( 'item_st_ID', 'integer', NULL );
 596          $this->set_from_Request( 'pst_ID', 'item_st_ID', true );
 597  
 598          param( 'item_assigned_user_ID', 'integer', NULL );
 599          $this->assign_to( get_param('item_assigned_user_ID') );
 600  
 601          param( 'item_priority', 'integer', NULL );
 602          $this->set_from_Request( 'priority', 'item_priority', true );
 603  
 604          // FEATURED checkbox:
 605          $this->set( 'featured', param( 'item_featured', 'integer', 0 ), false );
 606  
 607          // HIDE TEASER checkbox:
 608          $this->set_setting( 'hide_teaser', param( 'item_hideteaser', 'integer', 0 ) );
 609  
 610          // ORDER:
 611          param( 'item_order', 'double', NULL );
 612          $this->set_from_Request( 'order', 'item_order', true );
 613  
 614          // OWNER:
 615          $this->creator_user_login = param( 'item_owner_login', 'string', NULL );
 616          if( $current_User->check_perm( 'users', 'edit' ) && param( 'item_owner_login_displayed', 'string', NULL ) !== NULL )
 617          {    // only admins can change the owner..
 618              if( param_check_not_empty( 'item_owner_login', T_('Please enter valid owner login.') ) && param_check_login( 'item_owner_login', true ) )
 619              {
 620                  $this->set_creator_by_login( $this->creator_user_login );
 621              }
 622          }
 623  
 624          // LOCATION COORDINATES:
 625          if( $this->Blog->get_setting( 'show_location_coordinates' ) )
 626          { // location coordinates are enabled, save map settings
 627              param( 'item_latitude', 'double', NULL ); // get par value
 628              $this->set_setting( 'latitude', get_param( 'item_latitude' ), true );
 629              param( 'item_longitude', 'double', NULL ); // get par value
 630              $this->set_setting( 'longitude', get_param( 'item_longitude' ), true );
 631              param( 'google_map_zoom', 'integer', NULL ); // get par value
 632              $this->set_setting( 'map_zoom', get_param( 'google_map_zoom' ), true );
 633              param( 'google_map_type', 'string', NULL ); // get par value
 634              $this->set_setting( 'map_type', get_param( 'google_map_type' ), true );
 635          }
 636  
 637          // CUSTOM FIELDS:
 638          foreach( array( 'double', 'varchar' ) as $type )
 639          {
 640              $field_count = $this->Blog->get_setting( 'count_custom_'.$type );
 641              for( $i = 1 ; $i <= $field_count; $i++ )
 642              { // update each custom field
 643                  $field_guid = $this->Blog->get_setting( 'custom_'.$type.$i );
 644                  $param_name = 'item_'.$type.'_'.$field_guid;
 645                  if( isset_param( $param_name ) )
 646                  { // param is set
 647                      $param_type = ( $type == 'varchar' ) ? 'string' : $type;
 648                      param( $param_name, $param_type, NULL ); // get par value
 649                      $custom_field_make_null = $type != 'double'; // store '0' values in DB for numeric fields
 650                      $this->set_setting( 'custom_'.$type.'_'.$field_guid, get_param( $param_name ), $custom_field_make_null );
 651                  }
 652              }
 653          }
 654  
 655          // COMMENTS:
 656          if( ( $this->Blog->get_setting( 'allow_comments' ) != 'never' ) && ( $this->Blog->get_setting( 'disable_comments_bypost' ) ) )
 657          {    // Save status of "Allow comments for this item" (only if comments are allowed in this blog, and disable_comments_bypost is enabled):
 658              $post_comment_status = param( 'post_comment_status', 'string', 'open' );
 659              if( !empty( $post_comment_status ) )
 660              { // 'open' or 'closed' or ...
 661                  $this->set_from_Request( 'comment_status' );
 662              }
 663          }
 664  
 665          // EXPIRY DELAY:
 666          $expiry_delay = param_duration( 'expiry_delay' );
 667          if( empty( $expiry_delay ) )
 668          { // Check if we have 'expiry_delay' param set as string from simple or mass form
 669              $expiry_delay = param( 'expiry_delay', 'string', NULL );
 670          }
 671          $this->set_setting( 'post_expiry_delay', $expiry_delay, true );
 672  
 673          // EXTRA PARAMS FROM MODULES:
 674          modules_call_method( 'update_item_settings', array( 'edited_Item' => $this ) );
 675  
 676          // RENDERERS:
 677          if( param( 'renderers_displayed', 'integer', 0 ) )
 678          { // use "renderers" value only if it has been displayed (may be empty)
 679              global $Plugins;
 680              $renderers = $Plugins->validate_renderer_list( param( 'renderers', 'array/string', array() ), array( 'Item' => & $this ) );
 681              $this->set( 'renderers', $renderers );
 682          }
 683          else
 684          {
 685              $renderers = $this->get_renderers_validated();
 686          }
 687  
 688          // CONTENT + TITLE:
 689          if( $this->Blog->get_setting( 'allow_html_post' ) )
 690          {    // HTML is allowed for this post, we'll accept HTML tags:
 691              $text_format = 'html';
 692          }
 693          else
 694          {    // HTML is disallowed for this post, we'll encode all special chars:
 695              $text_format = 'htmlspecialchars';
 696          }
 697  
 698          $editor_code = param( 'editor_code', 'string', NULL );
 699          if( !empty( $editor_code ) )
 700          { // Update item editor code if it was explicitly set
 701              $this->set_setting( 'editor_code', $editor_code );
 702          }
 703  
 704          if( param( 'content', $text_format, NULL ) !== NULL )
 705          {
 706              // Never allow html content on post titles:  (fp> probably so as to not mess up backoffice and all sorts of tools)
 707              param( 'post_title', 'htmlspecialchars', NULL );
 708  
 709              // Do some optional filtering on the content
 710              // Typically stuff that will help the content to validate
 711              // Useful for code display.
 712              // Will probably be used for validation also.
 713              $Plugins_admin = & get_Plugins_admin();
 714              $params = array( 'object_type' => 'Item', 'object_Blog' => & $this->Blog );
 715              $Plugins_admin->filter_contents( $GLOBALS['post_title'] /* by ref */, $GLOBALS['content'] /* by ref */, $renderers, $params /* by ref */ );
 716  
 717              // Title checking:
 718              $require_title = $this->Blog->get_setting('require_title');
 719  
 720              if( ( ! $editing || $creating ) && $require_title == 'required' ) // creating is important, when the action is create_edit
 721              {
 722                  param_check_not_empty( 'post_title', T_('Please provide a title.'), '' );
 723              }
 724  
 725              // Format raw HTML input to cleaned up and validated HTML:
 726              param_check_html( 'content', T_('Invalid content.') );
 727              $this->set( 'content', get_param( 'content' ) );
 728  
 729              $this->set( 'title', get_param( 'post_title' ) );
 730          }
 731  
 732          // EXCERPT: (must come after content (to handle excerpt_autogenerated))
 733          if( param( 'post_excerpt', 'text', NULL ) !== NULL )
 734          {
 735              $this->set( 'excerpt_autogenerated', 0 ); // Set this to the '0' for saving a field 'excerpt' from a request
 736              $this->set_from_Request( 'excerpt' );
 737          }
 738  
 739          // LOCATION (COUNTRY -> CITY):
 740          load_funcs( 'regional/model/_regional.funcs.php' );
 741          if( $this->Blog->country_visible() )
 742          { // Save country
 743              $country_ID = param( 'item_ctry_ID', 'integer', 0 );
 744              $country_is_required = $this->Blog->get_setting( 'location_country' ) == 'required'
 745                      && countries_exist()
 746                      && ! $this->is_special();
 747              param_check_number( 'item_ctry_ID', T_('Please select a country'), $country_is_required );
 748              $this->set_from_Request( 'ctry_ID', 'item_ctry_ID', true );
 749          }
 750  
 751          if( $this->Blog->region_visible() )
 752          { // Save region
 753              $region_ID = param( 'item_rgn_ID', 'integer', 0 );
 754              $region_is_required = $this->Blog->get_setting( 'location_region' ) == 'required'
 755                      && regions_exist( $country_ID )
 756                      && ! $this->is_special();
 757              param_check_number( 'item_rgn_ID', T_('Please select a region'), $region_is_required );
 758              $this->set_from_Request( 'rgn_ID', 'item_rgn_ID', true );
 759          }
 760  
 761          if( $this->Blog->subregion_visible() )
 762          { // Save subregion
 763              $subregion_ID = param( 'item_subrg_ID', 'integer', 0 );
 764              $subregion_is_required = $this->Blog->get_setting( 'location_subregion' ) == 'required'
 765                      && subregions_exist( $region_ID )
 766                      && ! $this->is_special();
 767              param_check_number( 'item_subrg_ID', T_('Please select a sub-region'), $subregion_is_required );
 768              $this->set_from_Request( 'subrg_ID', 'item_subrg_ID', true );
 769          }
 770  
 771          if( $this->Blog->city_visible() )
 772          { // Save city
 773              param( 'item_city_ID', 'integer', 0 );
 774              $city_is_required = $this->Blog->get_setting( 'location_city' ) == 'required'
 775                      && cities_exist( $country_ID, $region_ID, $subregion_ID )
 776                      && ! $this->is_special();
 777              param_check_number( 'item_city_ID', T_('Please select a city'), $city_is_required );
 778              $this->set_from_Request( 'city_ID', 'item_city_ID', true );
 779          }
 780  
 781          return ! param_errors_detected();
 782      }
 783  
 784  
 785      /**
 786       * Template function: display anchor for permalinks to refer to.
 787       */
 788  	function anchor()
 789      {
 790          global $Settings;
 791  
 792          echo '<a id="'.$this->get_anchor_id().'"></a>';
 793      }
 794  
 795  
 796      /**
 797       * @return string
 798       */
 799  	function get_anchor_id()
 800      {
 801          // In case you have old cafelog permalinks, uncomment the following line:
 802          // return preg_replace( '/[^a-zA-Z0-9_\.-]/', '_', $this->title );
 803  
 804          return 'item_'.$this->ID;
 805      }
 806  
 807  
 808      /**
 809       * Template tag
 810       */
 811  	function anchor_id()
 812      {
 813          echo $this->get_anchor_id();
 814      }
 815  
 816  
 817      /**
 818       * Template function: display assignee of item
 819       *
 820       * @param string
 821       * @param string
 822       * @param string Output format, see {@link format_to_output()}
 823       */
 824  	function assigned_to( $before = '', $after = '', $format = 'htmlbody' )
 825      {
 826          if( $this->get_assigned_User() )
 827          {
 828              echo $before;
 829              echo $this->assigned_User->get_identity_link( array(
 830                      'format'    => $format,
 831                      'link_text' => 'login',
 832                  ) );
 833              echo $after;
 834          }
 835      }
 836  
 837  
 838      /**
 839       * Get list of assigned user options
 840       *
 841       * @uses UserCache::get_blog_member_option_list()
 842       * @return string HTML select options list
 843       */
 844  	function get_assigned_user_options()
 845      {
 846          $UserCache = & get_UserCache();
 847          return $UserCache->get_blog_member_option_list( $this->get_blog_ID(), $this->assigned_user_ID,
 848                              true,    ($this->ID != 0) /* if this Item is already serialized we'll load the default anyway */ );
 849      }
 850  
 851  
 852      /**
 853       * Check if user can see comments on this post, which he cannot if they
 854       * are disabled for the Item or never allowed for the blog.
 855       *
 856       * @param boolean true will display why user can't see comments
 857       * @return boolean
 858       */
 859  	function can_see_comments( $display = false )
 860      {
 861          global $Settings;
 862  
 863          $this->load_Blog();
 864          if( $this->Blog->get_setting( 'disable_comments_bypost' ) && ( $this->comment_status == 'disabled' ) )
 865          { // Comments are disabled on this post
 866              return false;
 867          }
 868  
 869          if( $this->check_blog_settings( 'allow_view_comments' ) )
 870          { // User is allowed to see comments
 871              return true;
 872          }
 873  
 874          if( !$display )
 875          {
 876              return false;
 877          }
 878  
 879          $number_of_comments = $this->get_number_of_comments( 'published' );
 880          $allow_view_comments = $this->Blog->get_setting( 'allow_view_comments' );
 881          $user_can_be_validated = check_user_status( 'can_be_validated' );
 882  
 883          if( ( $allow_view_comments != 'any' ) && ( $user_can_be_validated ) )
 884          { // change allow view comments to activated, because user is logged in but the account is not activated, and anomnymous users can't see comments
 885              $allow_view_comments = 'active_users';
 886          }
 887  
 888          // Set display text
 889          switch( $allow_view_comments )
 890          {
 891              case 'active_users':
 892                  // users must activate their accounts before they can see the comments
 893                  if( $number_of_comments == 0 )
 894                  {
 895                      $display_text = T_( 'You must activate your account to see the comments.' );
 896                  }
 897                  elseif ( $number_of_comments == 1 )
 898                  {
 899                      $display_text = T_( 'There is <b>one comment</b> on this post but you must activate your account to see the comments.' );
 900                  }
 901                  else
 902                  {
 903                      $display_text = sprintf( T_( 'There are <b>%s comments</b> on this post but you must activate your account to see the comments.' ), $number_of_comments );
 904                  }
 905                  break;
 906  
 907              case 'registered':
 908                  // only registered users can see this post's comments
 909                  if( $number_of_comments == 0 )
 910                  {
 911                      $display_text = T_( 'You must be logged in to see the comments.' );
 912                  }
 913                  elseif ( $number_of_comments == 1 )
 914                  {
 915                      $display_text = T_( 'There is <b>one comment</b> on this post but you must be logged in to see the comments.' );
 916                  }
 917                  else
 918                  {
 919                      $display_text = sprintf( T_( 'There are <b>%s comments</b> on this post but you must be logged in to see the comments.' ), $number_of_comments );
 920                  }
 921                  break;
 922  
 923              case 'member':
 924                  // only members can see this post's comments
 925                  if( $number_of_comments == 0 )
 926                  {
 927                      $display_text = T_( 'You must be a member of this blog to see the comments.' );
 928                  }
 929                  elseif ( $number_of_comments == 1 )
 930                  {
 931                      $display_text = T_( 'There is one comment on this post but you must be a member of this blog to see the comments.' );
 932                  }
 933                  else
 934                  {
 935                      $display_text = sprintf( T_( 'There are %s comments on this post but you must be a member of this blog to see the comments.' ), $number_of_comments );
 936                  }
 937                  break;
 938  
 939              default:
 940                  // any is already handled, moderators shouldn't get any message
 941                  return false;
 942          }
 943  
 944          echo '<div class="comment_posting_disabled_msg">';
 945  
 946          if( !is_logged_in() )
 947          { // user is not logged in at all
 948              $redirect_to = $this->get_permanent_url().'#comments';
 949              $login_link = '<a href="'.get_login_url( 'cannot see comments', $redirect_to ).'">'.T_( 'Log in now!' ).'</a>';
 950              echo '<p>'.$display_text.' '.$login_link.'</p>';
 951              if( $Settings->get( 'newusers_canregister' ) )
 952              { // needs to display register link
 953                  echo '<p>'.sprintf( T_( 'If you have no account yet, you can <a href="%s">register now</a>...<br />(It only takes a few seconds!)' ),
 954                              get_user_register_url( $redirect_to, 'reg to see comments' ) ).'</p>';
 955              }
 956          }
 957          elseif( $user_can_be_validated )
 958          { // user is logged in but not activated
 959              $activateinfo_link = '<a href="'.get_activate_info_url( $this->get_permanent_url().'#comments' ).'">'.T_( 'More info &raquo;' ).'</a>';
 960              echo '<p>'.$display_text.' '.$activateinfo_link.'</p>';
 961          }
 962          else
 963          { // user is activated, but not allowed to view comments
 964              echo $display_text;
 965          }
 966  
 967          echo '</div>';
 968  
 969          return false;
 970      }
 971  
 972  
 973      /**
 974       * Template function: Check if user can leave comment on this post or display error
 975       *
 976       * @param string|NULL string to display before any error message; NULL to not display anything, but just return boolean
 977       * @param string string to display after any error message
 978       * @param string error message for non published posts, '#' for default
 979       * @param string error message for closed comments posts, '#' for default
 980       * @param string section title
 981       * @param array Skin params
 982       * @return boolean true if user can post, false if s/he cannot
 983       */
 984  	function can_comment( $before_error = '<p><em>', $after_error = '</em></p>', $non_published_msg = '#', $closed_msg = '#', $section_title = '', $params = array() )
 985      {
 986          global $current_User;
 987  
 988          $display = ( ! is_null($before_error) );
 989  
 990          if( $display )
 991          { // display a comment form section even if comment form won't be displayed, "add new comment" links should point to this section
 992              echo '<a id="form_p'.$this->ID.'"></a>';
 993          }
 994  
 995          if( $this->check_blog_settings( 'allow_comments' ) )
 996          {
 997              if( $this->Blog->get_setting( 'disable_comments_bypost' ) && ( $this->comment_status == 'disabled' ) )
 998              { // Comments are disabled on this post
 999                  return false;
1000              }
1001  
1002              if( $this->comment_status == 'closed' || $this->is_locked() )
1003              { // Comments are closed on this post
1004  
1005                  if( $display)
1006                  {
1007                      if( $closed_msg == '#' )
1008                          $closed_msg = T_( 'Comments are closed for this post.' );
1009  
1010                      echo $before_error;
1011                      echo $closed_msg;
1012                      echo $after_error;
1013                  }
1014  
1015                  return false;
1016              }
1017  
1018              if( ($this->status == 'draft') || ($this->status == 'deprecated' ) || ($this->status == 'redirected' ) )
1019              { // Post is not published
1020  
1021                  if( $display )
1022                  {
1023                      if( $non_published_msg == '#' )
1024                          $non_published_msg = T_( 'This post is not published. You cannot leave comments.' );
1025  
1026                      echo $before_error;
1027                      echo $non_published_msg;
1028                      echo $after_error;
1029                  }
1030  
1031                  return false;
1032              }
1033  
1034              if( is_logged_in() && ( $this->Blog->get( 'advanced_perms' ) ) && !$current_User->check_perm( 'blog_comment_statuses', 'create', false, $this->Blog->ID ) )
1035              { // User doesn't have permission to create comments and advanced perms are enabled
1036                  if( $display )
1037                  {
1038                      echo $before_error;
1039                      echo T_('You don\'t have permission to reply on this post.');
1040                      echo $after_error;
1041                  }
1042                  return false;
1043              }
1044              return true; // OK, user can comment!
1045          }
1046  
1047          if( ( $this->Blog->get_setting( 'allow_comments' ) != 'never' ) && $display )
1048          {
1049              if( $this->comment_status == 'closed' || $this->comment_status == 'disabled' )
1050              {    // Don't display the disabled comment form because we cannot create the comments for this post
1051                  return false;
1052              }
1053              echo $section_title;
1054              // set item_url for redirect after login, if login required
1055              $item_url = $this->get_permanent_url().'#form_p'.$this->ID;
1056              // display disabled comment form
1057              echo_disabled_comments( $this->Blog->get_setting( 'allow_comments' ), $item_url, $params );
1058          }
1059  
1060          // Current user not allowed to comment in this blog
1061          return false;
1062      }
1063  
1064  
1065      /**
1066       * Check if current user is allowed for several action in this post's blog
1067       *
1068       * @private function
1069       *
1070       * @param string blog settings name. Param value can be 'allow_comments', 'allow_attachments','allow_rating_items'
1071       * @return boolean  true if user is allowed for the corresponding action
1072       */
1073  	function check_blog_settings( $settings_name )
1074      {
1075          global $current_User;
1076  
1077          $this->load_Blog();
1078  
1079          switch( $this->Blog->get_setting( $settings_name ) )
1080          {
1081              case 'never':
1082                  return false;
1083              case 'any':
1084                  return true;
1085              case 'registered':
1086                  return is_logged_in( false );
1087              case 'member':
1088                  return (is_logged_in( false ) && $current_User->check_perm( 'blog_ismember', 'view', false, $this->get_blog_ID() ) );
1089              case 'moderator':
1090                  return (is_logged_in( false ) && $current_User->check_perm( 'blog_comments', 'edit', false, $this->get_blog_ID() ) );
1091              default:
1092                  debug_die( 'Invalid blog '.$settings_name.' settings!' );
1093          }
1094  
1095          return false;
1096      }
1097  
1098  
1099      /**
1100       * Template function: Check if user can attach files to this post comments
1101       *
1102       * @return boolean true if user can attach files to this post comments, false if s/he cannot
1103       */
1104  	function can_attach()
1105      {
1106          global $Settings;
1107  
1108          $attachments_quota_is_full = false;
1109          if( is_logged_in() )
1110          {    // We can check the attachments quota only for registered users
1111              $this->load_Blog();
1112              $max_attachments = (int)$this->Blog->get_setting( 'max_attachments' );
1113              if( $max_attachments > 0 )
1114              {    // Check attachments quota only when Blog setting "Max # of attachments" is defined
1115                  global $DB, $current_User, $Session;
1116  
1117                  // Get a number of attachments for current user on this post
1118                  $attachments_count = $this->get_attachments_number();
1119  
1120                  // Get the attachments from preview comment
1121                  global $checked_attachments;
1122                  if( !empty( $checked_attachments ) )
1123                  {    // Calculate also the attachments in the PREVIEW mode
1124                      $attachments_count += count( explode( ',', $checked_attachments ) );
1125                  }
1126  
1127                  if( $attachments_count >= $max_attachments )
1128                  {    // Current user already has max number of attachments on this post
1129                      $attachments_quota_is_full = true;
1130                  }
1131              }
1132          }
1133  
1134          return !$attachments_quota_is_full && $this->check_blog_settings( 'allow_attachments' ) && $Settings->get( 'upload_enabled' );
1135      }
1136  
1137  
1138      /**
1139       * Get a number of attachments on this post
1140       *
1141       * @param object User
1142       * @return integer Number of attachments
1143       */
1144  	function get_attachments_number( $User = NULL )
1145      {
1146          global $DB, $cache_item_attachments_number;
1147  
1148          if( is_null( $User ) )
1149          {    // Use current user by default
1150              global $current_User;
1151              $User = $current_User;
1152          }
1153  
1154          if( !isset( $cache_item_attachments_number ) )
1155          {    // Init cache variable at first time
1156              $cache_item_attachments_number = array();
1157          }
1158  
1159          if( isset( $cache_item_attachments_number[$User->ID] ) )
1160          {    // Get a number of attachments from cache variable
1161              return $cache_item_attachments_number[$User->ID];
1162          }
1163  
1164          // Get a number of attachments from DB
1165          $SQL = new SQL();
1166          $SQL->SELECT( 'COUNT( link_ID )' );
1167          $SQL->FROM( 'T_links' );
1168          $SQL->FROM_add( 'INNER JOIN T_comments ON comment_ID = link_cmt_ID' );
1169          $SQL->WHERE( 'link_creator_user_ID = '.$DB->quote( $User->ID ) );
1170          $SQL->WHERE_and( 'comment_post_ID = '.$DB->quote( $this->ID ) );
1171          $cache_item_attachments_number[$User->ID] = (int)$DB->get_var( $SQL->get() );
1172  
1173          return $cache_item_attachments_number[$User->ID];
1174      }
1175  
1176  
1177      /**
1178       * Get how much files user can attach on this post yet
1179       *
1180       * @param object User
1181       * @return integer|string Number of files which current user can attach to this post | 'unlimit'
1182       */
1183  	function get_attachments_limit( $User = NULL )
1184      {
1185          if( is_logged_in() )
1186          {    // We can check the attachments quota only for registered users
1187              $this->load_Blog();
1188              $max_attachments = (int)$this->Blog->get_setting( 'max_attachments' );
1189              if( $max_attachments > 0 )
1190              {    // Get a limit only when Blog setting "Max # of attachments" is defined
1191                  return $max_attachments - $this->get_attachments_number( $User );
1192              }
1193          }
1194  
1195          return 'unlimit';
1196      }
1197  
1198  
1199      /**
1200       * Template function: Check if user can rate this post
1201       *
1202       * @return boolean true if user can post, false if s/he cannot
1203       */
1204  	function can_rate()
1205      {
1206          return $this->check_blog_settings( 'allow_rating_items' );
1207      }
1208  
1209  
1210      /**
1211       * Get the prerendered content. If it has not been generated yet, it will.
1212       *
1213       * NOTE: This calls {@link Item::dbupdate()}, if renderers get changed (from Plugin hook).
1214       *       (not for preview though)
1215       *
1216       * @param string Format, see {@link format_to_output()}.
1217       *        Only "htmlbody", "entityencoded", "xml" and "text" get cached.
1218       * @return string
1219       */
1220  	function get_prerendered_content( $format )
1221      {
1222          global $Plugins;
1223          global $preview;
1224  
1225          if( $preview )
1226          {
1227              $this->update_renderers_from_Plugins();
1228              $post_renderers = $this->get_renderers_validated();
1229  
1230              // Call RENDERER plugins:
1231              $r = $this->content;
1232              $Plugins->render( $r /* by ref */, $post_renderers, $format, array( 'Item' => $this ), 'Render' );
1233  
1234              return $r;
1235          }
1236  
1237  
1238          $r = null;
1239  
1240          $post_renderers = $this->get_renderers_validated();
1241          $cache_key = $format.'/'.implode('.', $post_renderers); // logic gets used below, for setting cache, too.
1242  
1243          $use_cache = $this->ID && in_array( $format, array('htmlbody', 'entityencoded', 'xml', 'text') );
1244  
1245          // $use_cache = false;
1246  
1247          if( $use_cache )
1248          { // the format/item can be cached:
1249              $ItemPrerenderingCache = & get_ItemPrerenderingCache();
1250  
1251              if( isset($ItemPrerenderingCache[$format][$this->ID][$cache_key]) )
1252              { // already in PHP cache.
1253                  $r = $ItemPrerenderingCache[$format][$this->ID][$cache_key];
1254                  // Save memory, typically only accessed once.
1255                  unset($ItemPrerenderingCache[$format][$this->ID][$cache_key]);
1256              }
1257              else
1258              {    // Try loading from DB cache, including all items in MainList/ItemList.
1259                  global $DB;
1260  
1261                  if( ! isset($ItemPrerenderingCache[$format]) )
1262                  { // only do the prefetch loading once.
1263                      $prefetch_IDs = $this->get_prefetch_itemlist_IDs();
1264  
1265                      // Load prerendered content for all items in MainList/ItemList.
1266                      // We load the current $format only, since it's most likely that only one gets used.
1267                      $ItemPrerenderingCache[$format] = array();
1268  
1269                      $rows = $DB->get_results( "
1270                          SELECT itpr_itm_ID, itpr_format, itpr_renderers, itpr_content_prerendered
1271                              FROM T_items__prerendering
1272                           WHERE itpr_itm_ID IN (".$DB->quote( $prefetch_IDs ).")
1273                               AND itpr_format = '".$format."'",
1274                               OBJECT, 'Preload prerendered item content for MainList/ItemList ('.$format.')' );
1275                      foreach($rows as $row)
1276                      {
1277                          $row_cache_key = $row->itpr_format.'/'.$row->itpr_renderers;
1278  
1279                          if( ! isset($ItemPrerenderingCache[$format][$row->itpr_itm_ID]) )
1280                          { // init list
1281                              $ItemPrerenderingCache[$format][$row->itpr_itm_ID] = array();
1282                          }
1283  
1284                          $ItemPrerenderingCache[$format][$row->itpr_itm_ID][$row_cache_key] = $row->itpr_content_prerendered;
1285                      }
1286  
1287                      // Set the value for current Item.
1288                      if( isset($ItemPrerenderingCache[$format][$this->ID][$cache_key]) )
1289                      {
1290                          $r = $ItemPrerenderingCache[$format][$this->ID][$cache_key];
1291                          // Save memory, typically only accessed once.
1292                          unset($ItemPrerenderingCache[$format][$this->ID][$cache_key]);
1293                      }
1294                  }
1295                  else
1296                  { // This item has not been fetched by the initial prefetch query; only get this item.
1297                      // dh> This is quite unlikely to happen, but you never know.
1298                      // This gets not added to ItemPrerenderingCache, since it would only waste
1299                      // memory - an item gets typically only accessed once per page, and even if
1300                      // it would get accessed more often, there is a cache higher in the chain
1301                      // ($this->content_pages).
1302                      $cache = $DB->get_var( "
1303                          SELECT itpr_content_prerendered
1304                              FROM T_items__prerendering
1305                           WHERE itpr_itm_ID = ".$this->ID."
1306                               AND itpr_format = '".$format."'
1307                               AND itpr_renderers = '".implode('.', $post_renderers)."'", 0, 0, 'Check prerendered item content' );
1308                      if( $cache !== NULL ) // may be empty string
1309                      { // Retrieved from cache:
1310                          // echo ' retrieved from prerendered cache';
1311                          $r = $cache;
1312                      }
1313                  }
1314              }
1315          }
1316  
1317          if( ! isset( $r ) )
1318          {    // Not cached yet:
1319              global $Debuglog;
1320  
1321              if( $this->update_renderers_from_Plugins() )
1322              {
1323                  $post_renderers = $this->get_renderers_validated(); // might have changed from call above
1324                  $cache_key = $format.'/'.implode('.', $post_renderers);
1325  
1326                  // Save new renderers with item:
1327                  $this->dbupdate();
1328              }
1329  
1330              // Call RENDERER plugins:
1331              // pre_dump( $this->content );
1332              $r = $this->content;
1333              $Plugins->render( $r /* by ref */, $post_renderers, $format, array( 'Item' => $this ), 'Render' );
1334              // pre_dump( $r );
1335  
1336              $Debuglog->add( 'Generated pre-rendered content ['.$cache_key.'] for item #'.$this->ID, 'items' );
1337  
1338              if( $use_cache )
1339              { // save into DB (using REPLACE INTO because it may have been pre-rendered by another thread since the SELECT above)
1340                  $DB->query( "
1341                      REPLACE INTO T_items__prerendering (itpr_itm_ID, itpr_format, itpr_renderers, itpr_content_prerendered)
1342                       VALUES ( ".$this->ID.", '".$format."', ".$DB->quote(implode('.', $post_renderers)).', '.$DB->quote($r).' )', 'Cache prerendered item content' );
1343              }
1344          }
1345  
1346          return $r;
1347      }
1348  
1349  
1350      /**
1351       * Unset any prerendered content for this item (in PHP cache).
1352       */
1353  	function delete_prerendered_content()
1354      {
1355          global $DB;
1356  
1357          // Delete DB rows.
1358          $DB->query( 'DELETE FROM T_items__prerendering WHERE itpr_itm_ID = '.$this->ID );
1359  
1360          // Delete cache.
1361          $ItemPrerenderingCache = & get_ItemPrerenderingCache();
1362          foreach( array_keys($ItemPrerenderingCache) as $format )
1363          {
1364              unset($ItemPrerenderingCache[$format][$this->ID]);
1365          }
1366  
1367          // Delete derived properties.
1368          unset($this->content_pages);
1369      }
1370  
1371  
1372      /**
1373       * Trigger {@link Plugin::ItemApplyAsRenderer()} event and adjust renderers according
1374       * to return value.
1375       * @return boolean True if renderers got changed.
1376       */
1377  	function update_renderers_from_Plugins()
1378      {
1379          global $Plugins;
1380  
1381          $r = false;
1382  
1383          if( !isset($Plugins) )
1384          {    // This can happen in maintenance modules running with minimal init, during install, or in tests.
1385              return $r;
1386          }
1387  
1388          foreach( $Plugins->get_list_by_event('ItemApplyAsRenderer') as $Plugin )
1389          {
1390              if( empty($Plugin->code) )
1391                  continue;
1392  
1393              $plugin_r = $Plugin->ItemApplyAsRenderer( $tmp_params = array('Item' => & $this) );
1394  
1395              if( is_bool($plugin_r) )
1396              {
1397                  if( $plugin_r )
1398                  {
1399                      $r = $this->add_renderer( $Plugin->code ) || $r;
1400                  }
1401                  else
1402                  {
1403                      $r = $this->remove_renderer( $Plugin->code ) || $r;
1404                  }
1405              }
1406          }
1407  
1408          return $r;
1409      }
1410  
1411  
1412      /**
1413       * Display excerpt of an item.
1414       * @param array Associative list of params
1415       *   - before
1416       *   - after
1417       *   - excerpt_before_more
1418       *   - excerpt_after_more
1419       *   - excerpt_more_text
1420       *   - format
1421       *   - allow_empty: force generation if excert is empty (Default: false)
1422       *   - update_db: update the DB if we generated an excerpt (Default: true)
1423       */
1424  	function excerpt( $params = array() )
1425      {
1426          // Make sure we are not missing any param:
1427          $params = array_merge( array(
1428                  'before'              => '<div class="excerpt">',
1429                  'after'               => '</div>',
1430                  'excerpt_before_more' => ' <span class="excerpt_more">',
1431                  'excerpt_after_more'  => '</span>',
1432                  'excerpt_more_text'   => T_('more').' &raquo;',
1433                  'format'              => 'htmlbody',
1434                  'allow_empty'         => false,
1435                  'update_db'           => true,
1436              ), $params );
1437  
1438          $r = $this->get_excerpt2( $params );
1439  
1440          if( ! empty( $r ) )
1441          {
1442              echo $params['before'];
1443              echo format_to_output( $this->excerpt, $params['format'] );
1444              if( !empty( $params['excerpt_more_text'] ) )
1445              {
1446                  echo $params['excerpt_before_more'];
1447                  echo '<a href="'.$this->get_permanent_url().'">'.$params['excerpt_more_text'].'</a>';
1448                  echo $params['excerpt_after_more'];
1449              }
1450              echo $params['after'];
1451          }
1452      }
1453  
1454  
1455      /**
1456       * Get item excerpt.
1457       *
1458       * @todo fp>blueyed WTF? Same function name as in ItemLight but different params!
1459       * fp> NOTE: I think we can't move this code to ItemLight because we can't update the excerpt there since we don't have the post text there
1460       *
1461       * @param array Associative list of params
1462       *   - allow_empty: force generation if excert is empty (Default: false)
1463       *   - update_db: update the DB if we generated an excerpt (Default: true)
1464       * @return string
1465       */
1466  	function get_excerpt2( $params = array() )
1467      {
1468          $params += array(
1469              'allow_empty' => false,
1470              'update_db' => true,
1471              );
1472  
1473          if( ! $params['allow_empty'] )
1474          {    // Make sure excerpt the excerpt is not empty by updating it automatically if needed:
1475              if( $this->update_excerpt() && $params['update_db'] && $this->ID )
1476              {    // We have updated... let's also update the DB:
1477                  $this->dbupdate( false );        // Do not auto track modification date.
1478              }
1479          }
1480          return $this->excerpt;
1481      }
1482  
1483  
1484      /**
1485       * Make sure, the pages have been obtained (and split up_ from prerendered cache.
1486       *
1487       * @param string Format, used to retrieve the matching cache; see {@link format_to_output()}
1488       */
1489  	function split_pages( $format = 'htmlbody' )
1490      {
1491          if( ! isset( $this->content_pages[$format] ) )
1492          {
1493              // SPLIT PAGES:
1494              $this->content_pages[$format] = explode( '<!--nextpage-->', $this->get_prerendered_content($format) );
1495  
1496              // Balance HTML tags
1497              $this->content_pages[$format] = array_map( 'balance_tags', $this->content_pages[$format] );
1498  
1499              $this->pages = count( $this->content_pages[$format] );
1500              // echo ' Pages:'.$this->pages;
1501          }
1502      }
1503  
1504  
1505      /**
1506       * Get a specific page to display (from the prerendered cache)
1507       *
1508       * @param integer Page number, NULL/"#" for current
1509       * @param string Format, used to retrieve the matching cache; see {@link format_to_output()}
1510       */
1511  	function get_content_page( $page = NULL, $format = 'htmlbody' )
1512      {
1513          // Get requested content page:
1514          if( ! isset($page) || $page === '#' )
1515          { // We want to display the page requested by the user:
1516              $page = isset($GLOBALS['page']) ? $GLOBALS['page'] : 1;
1517          }
1518  
1519          // Make sure, the pages are split up:
1520          $this->split_pages( $format );
1521  
1522          if( $page < 1 )
1523          {
1524              $page = 1;
1525          }
1526  
1527          if( $page > $this->pages )
1528          {
1529              $page = $this->pages;
1530          }
1531  
1532          return $this->content_pages[$format][$page-1];
1533      }
1534  
1535  
1536      /**
1537       * This is like a teaser with no HTML and a cropping.
1538       *
1539       * Note: Excerpt and Teaser are TWO DIFFERENT THINGS.
1540       *
1541       * @param int Max length of excerpt
1542       * @return string
1543       */
1544  	function get_content_excerpt( $crop_at = 200 )
1545      {
1546          // Get teaser for page 1:
1547          $output = $this->get_content_teaser( 1, false, 'text' );
1548  
1549          return excerpt( $output, $crop_at );
1550      }
1551  
1552  
1553      /**
1554       * Display content teaser of item (will stop at "<!-- more -->"
1555       */
1556  	function content_teaser( $params )
1557      {
1558          // Make sure we are not missing any param:
1559          $params = array_merge( array(
1560                  'before'      => '',
1561                  'after'       => '',
1562                  'disppage'    => '#',
1563                  'stripteaser' => '#',
1564                  'format'      => 'htmlbody',
1565              ), $params );
1566  
1567          $r = $this->get_content_teaser( $params['disppage'], $params['stripteaser'], $params['format'], $params );
1568  
1569          if( !empty($r) )
1570          {
1571              echo $params['before'];
1572              echo $r;
1573              echo $params['after'];
1574          }
1575      }
1576  
1577      /**
1578       * Template function: get content teaser of item (will stop at "<!-- more -->")
1579       *
1580       * @param mixed page number to display specific page, # for url parameter
1581       * @param boolean # if you don't want to repeat teaser after more link was pressed and <-- noteaser --> has been found
1582       * @param string filename to use to display more
1583       * @param array Params
1584       * @return string
1585       */
1586  	function get_content_teaser( $disppage = '#', $stripteaser = '#', $format = 'htmlbody', $params = array() )
1587      {
1588          global $Plugins, $preview, $Debuglog;
1589          global $more;
1590  
1591          $params = array_merge( $params, array(
1592                  'disppage' => $disppage,
1593                  'format' => $format
1594              ) );
1595  
1596          $view_type = 'full';
1597          if( $this->has_content_parts($params) )
1598          { // This is an extended post (has a more section):
1599              if( $stripteaser === '#' )
1600              {
1601                  // If we're in "more" mode and we want to strip the teaser, we'll strip:
1602                  $stripteaser = ( $more && $this->get_setting( 'hide_teaser' ) );
1603              }
1604  
1605              if( $stripteaser )
1606              {
1607                  return NULL;
1608              }
1609              $view_type = 'teaser';
1610          }
1611  
1612          $content_parts = $this->get_content_parts( $params );
1613          $output = array_shift( $content_parts );
1614  
1615          // Render Inline Images  [image:123:caption]  :
1616          $params['check_code_block'] = true;
1617          $output = $this->render_inline_images( $output, $params );
1618  
1619          // Trigger Display plugins FOR THE STUFF THAT WOULD NOT BE PRERENDERED:
1620          $output = $Plugins->render( $output, $this->get_renderers_validated(), $format, array(
1621                  'Item' => $this,
1622                  'preview' => $preview,
1623                  'dispmore' => ($more != 0),
1624                  'view_type' => $view_type,
1625              ), 'Display' );
1626  
1627          // Character conversions
1628          $output = format_to_output( $output, $format );
1629  
1630          return $output;
1631      }
1632  
1633  
1634      /**
1635       * Get content parts (split by "<!--more-->").
1636       * @param array 'disppage', 'format'
1637       * @return array Array of content parts
1638       */
1639  	function get_content_parts($params)
1640      {
1641          // Make sure we are not missing any param:
1642          $params = array_merge( array(
1643                  'disppage'    => '#',
1644                  'format'      => 'htmlbody',
1645              ), $params );
1646  
1647          $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"
1648          // pre_dump($content_page);
1649  
1650          $content_parts = explode( '<!--more-->', $content_page );
1651          // echo ' Parts:'.count($content_parts);
1652  
1653          // Balance HTML tags
1654          $content_parts = array_map( 'balance_tags', $content_parts );
1655  
1656          return $content_parts;
1657      }
1658  
1659  
1660      /**
1661       * DEPRECATED
1662       */
1663  	function content()
1664      {
1665          // ---------------------- POST CONTENT INCLUDED HERE ----------------------
1666          skin_include( '_item_content.inc.php', array(
1667                  'image_size'    =>    'fit-400x320',
1668              ) );
1669          // Note: You can customize the default item feedback by copying the generic
1670          // /skins/_item_feedback.inc.php file into the current skin folder.
1671          // -------------------------- END OF POST CONTENT -------------------------
1672      }
1673  
1674  
1675      /**
1676       * Display content extension of item (part after "<!-- more -->")
1677       */
1678  	function content_extension( $params )
1679      {
1680          // Make sure we are not missing any param:
1681          $params = array_merge( array(
1682                  'before'      => '',
1683                  'after'       => '',
1684                  'disppage'    => '#',
1685                  'format'      => 'htmlbody',
1686                  'force_more'  => false,
1687              ), $params );
1688  
1689          $r = $this->get_content_extension( $params['disppage'], $params['force_more'], $params['format'] );
1690  
1691          if( !empty($r) )
1692          {
1693              echo $params['before'];
1694              echo $r;
1695              echo $params['after'];
1696          }
1697      }
1698  
1699  
1700      /**
1701       * Template function: get content extension of item (part after "<!-- more -->")
1702       *
1703       * @param mixed page number to display specific page, # for url parameter
1704       * @param boolean
1705       * @param string filename to use to display more
1706       * @return string
1707       */
1708  	function get_content_extension( $disppage = '#', $force_more = false, $format = 'htmlbody' )
1709      {
1710          global $Plugins, $more, $preview;
1711  
1712          if( ! $more && ! $force_more )
1713          {    // NOT in more mode:
1714              return NULL;
1715          }
1716  
1717          $params = array('disppage' => $disppage, 'format' => $format);
1718          if( ! $this->has_content_parts($params) )
1719          { // This is NOT an extended post
1720              return NULL;
1721          }
1722  
1723          $content_parts = $this->get_content_parts($params);
1724  
1725          // Output everything after <!-- more -->
1726          array_shift($content_parts);
1727          $output = implode('', $content_parts);
1728  
1729          // Render Inline Images  [image:123:caption]  :
1730          $params['check_code_block'] = true;
1731          $output = $this->render_inline_images( $output, $params );
1732  
1733          // Trigger Display plugins FOR THE STUFF THAT WOULD NOT BE PRERENDERED:
1734          $output = $Plugins->render( $output, $this->get_renderers_validated(), $format, array(
1735                  'Item' => $this,
1736                  'preview' => $preview,
1737                  'dispmore' => true,
1738                  'view_type' => 'extension',
1739              ), 'Display' );
1740  
1741          // Character conversions
1742          $output = format_to_output( $output, $format );
1743  
1744          return $output;
1745      }
1746  
1747  
1748      /**
1749       * Increase view counter
1750       *
1751       * @todo merge with inc_viewcount
1752       */
1753  	function count_view( $params = array() )
1754      {
1755          // Make sure we are not missing any param:
1756          $params = array_merge( array(
1757                  'allow_multiple_counts_per_page' => false,
1758              ), $params );
1759  
1760  
1761          global $Hit, $preview, $Debuglog, $Settings;
1762  
1763          if( $preview )
1764          {
1765              // echo 'PREVIEW';
1766              return false;
1767          }
1768  
1769          /*
1770           * Check if we want to increment view count, see {@link Hit::is_new_view()}
1771           */
1772          if( ( $Settings->get( 'smart_view_count' ) ) && ( ! $Hit->is_new_view() ) )
1773          {    // This is a reload
1774              // echo 'RELOAD';
1775              return false;
1776          }
1777  
1778          if( ! $params['allow_multiple_counts_per_page'] )
1779          {    // Check that we don't increase multiple viewcounts on the same page
1780              // This make the assumption that the first post in a list is "viewed" and the other are not (necesarily)
1781              global $view_counts_on_this_page;
1782              if( $view_counts_on_this_page >= 1 )
1783              {    // we already had a count on this page
1784                  // echo 'ALREADY HAD A COUNT';
1785                  return false;
1786              }
1787              $view_counts_on_this_page++;
1788          }
1789  
1790          //echo 'COUNTING VIEW';
1791  
1792          // Increment view counter (only if current User is not the item's author)
1793          return $this->inc_viewcount(); // won't increment if current_User == Author
1794      }
1795  
1796  
1797      /**
1798       * Load item custom field value by index
1799       *
1800       * @param String field index, this is the lowercase value of the trimmed field name ( whitespaces are converted to one '_' character )
1801       * @return boolean true on success false if custom field with this index doesn't exist
1802       */
1803  	function load_custom_field_value( $field_index )
1804      {
1805          if( empty( $this->custom_fields ) )
1806          { // load item custom_fields
1807              $this->custom_fields = get_item_custom_fields();
1808          }
1809  
1810          if( empty( $this->custom_fields[$field_index] ) )
1811          { // there is no such custom field
1812              return false;
1813          }
1814  
1815          if( empty( $this->custom_fields[$field_index]['value'] ) )
1816          { // get custom item field value from the item setting
1817              $this->custom_fields[$field_index]['value'] = $this->get_setting( 'custom_'.$this->custom_fields[$field_index]['name'] );
1818          }
1819          return true;
1820      }
1821  
1822  
1823      /**
1824       * Get item custom field value by index
1825       *
1826       * @param String field index, see {@link load_custom_field_value()}
1827       * @return mixed false if the field doesn't exist Double/String otherwise depending from the custom field type
1828       */
1829  	function get_custom_field_value( $field_index )
1830      {
1831          if( $this->load_custom_field_value( $field_index ) )
1832          {
1833              return $this->custom_fields[$field_index]['value'];
1834          }
1835          return false;
1836      }
1837  
1838  
1839      /**
1840       * Display custom field
1841       */
1842  	function custom( $params )
1843      {
1844          // Make sure we are not missing any param:
1845          $params = array_merge( array(
1846                  'before'        => ' ',
1847                  'after'         => ' ',
1848                  'format'        => 'htmlbody',
1849                  'decimals'      => 2,
1850                  'dec_point'     => '.',
1851                  'thousands_sep' => ',',
1852              ), $params );
1853  
1854          if( empty( $params['field'] ) )
1855          {
1856              return;
1857          }
1858  
1859          // Load custom field by index
1860          $field_index = $params['field'];
1861          if( !$this->load_custom_field_value( $field_index ) )
1862          { // Custom field with this index doesn't exist
1863              echo $params['before']
1864                  .'<span class="red">'.sprintf( T_('The custom field %s does not exist!'), $field_index ).'</span>'
1865                  .$params['after'];
1866              return;
1867          }
1868  
1869          // Get value and type
1870          $value = $this->custom_fields[$field_index]['value'];
1871          $type = $this->custom_fields[$field_index]['type'];
1872  
1873          if( !empty( $params['max'] ) && ( $type == 'double' ) && ( $value == 9999999999 ) )
1874          {
1875              echo $params['max'];
1876          }
1877          elseif( !empty( $value ) )
1878          {
1879              echo $params['before'];
1880              if( $type == 'double' )
1881              {
1882                  echo number_format( $value, $params['decimals'], $params['dec_point'], $params['thousands_sep']  );
1883              }
1884              else
1885              {
1886                  echo format_to_output( $value, $params['format'] );
1887              }
1888              echo $params['after'];
1889          }
1890      }
1891  
1892  
1893      /**
1894       * Display all custom fields of current Item
1895       *
1896       * @param array Params
1897       */
1898  	function custom_fields( $params = array() )
1899      {
1900          // Make sure we are not missing any param:
1901          $params = array_merge( array(
1902                  'before'       => '<table class="item_custom_fields">',
1903                  'field_format' => '<tr><th>$title$:</th><td>$value$</td></tr>', // $title$ $value$
1904                  'after'        => '</table>',
1905              ), $params );
1906  
1907          if( empty( $this->custom_fields ) )
1908          {
1909              $this->custom_fields = get_item_custom_fields();
1910          }
1911  
1912          if( count( $this->custom_fields ) == 0 )
1913          {    // No custom fields
1914              return;
1915          }
1916  
1917          $fields_exist = false;
1918  
1919          $html = $params['before'];
1920  
1921          $mask = array( '$title$', '$value$' );
1922          foreach( $this->custom_fields as $field )
1923          {
1924              $custom_field_value = $this->get_setting( 'custom_'.$field['name'] );
1925              if( !empty( $custom_field_value ) ||
1926                  ( $field['type'] == 'double' && $custom_field_value == '0' ) )
1927              { // Display only the filled field AND also numeric field with '0' value
1928                  $values = array( $field['title'], $custom_field_value );
1929                  $html .= str_replace( $mask, $values, $params['field_format'] );
1930                  $fields_exist = true;
1931              }
1932          }
1933  
1934          $html .= $params['after'];
1935  
1936          if( $fields_exist )
1937          {    // Print out if at least one field is filled for this item
1938              echo $html;
1939          }
1940      }
1941  
1942  
1943      /**
1944       * Template tag
1945       */
1946  	function more_link( $params = array() )
1947      {
1948          echo $this->get_more_link( $params );
1949      }
1950  
1951  
1952      /**
1953       * Display more link
1954       */
1955  	function get_more_link( $params = array() )
1956      {
1957          // Make sure we are not missing any param:
1958          $params = array_merge( array(
1959                  'force_more'  => false,
1960                  'before'      => '<p class="bMore">',
1961                  'after'       => '</p>',
1962                  'link_text'   => '#',        // text to display as the more link
1963                  'anchor_text' => '#',        // text to display as the more anchor (once the more link has been clicked, # defaults to "Follow up:")
1964                  'link_to'     => 'single#anchor',    // target URL for more link, 'single' or 'single#anchor'
1965                  'disppage'    => '#',        // page number to display specific page, # for url parameter
1966                  'format'      => 'htmlbody',
1967                  'link_class'  => '', // class name of the link
1968                  'force_hide_teaser' => false, // Force an item setting 'hide_teaser'
1969              ), $params );
1970  
1971          global $more;
1972  
1973          if( ! $this->has_content_parts($params) )
1974          { // This is NOT an extended post:
1975              return '';
1976          }
1977  
1978          if( ( $more == 0 ) && ( $params[ 'link_to' ] == false ) )
1979          { // Don't display "After more" content
1980              if( !empty( $params[ 'link_text' ] ) )
1981              {
1982                  return format_to_output( $params[ 'before'].$params[ 'link_text' ].$params[ 'after'] );
1983              }
1984              return '';
1985          }
1986  
1987          $content_parts = $this->get_content_parts($params);
1988  
1989          // Init an attribute for class
1990          $class_attr = empty( $params['link_class'] ) ? '' : ' class="'.$params['link_class'].'"';
1991  
1992          if( ! $more && ! $params['force_more'] )
1993          {    // We're NOT in "more" mode:
1994              if( $params['link_text'] == '#' )
1995              { // TRANS: this is the default text for the extended post "more" link
1996                  $params['link_text'] = T_('Full story').' &raquo;';
1997                  // Dummy in order to keep previous translation in the loop:
1998                  $dummy = T_('Read more');
1999              }
2000  
2001              switch( $params['link_to'] )
2002              {
2003                  case 'single':
2004                      $params['link_to'] = $this->get_permanent_url();
2005                      break;
2006  
2007                  case 'single#anchor':
2008                      $params['link_to'] = $this->get_permanent_url().'#more'.$this->ID;
2009                      break;
2010              }
2011  
2012              return format_to_output( $params['before']
2013                          .'<a href="'.$params['link_to'].'"'.$class_attr.'>'
2014                          .$params['link_text'].'</a>'
2015                          .$params['after'], $params['format'] );
2016          }
2017          elseif( ! $params['force_hide_teaser'] && ! $this->get_setting( 'hide_teaser' ) )
2018          {    // We are in more mode and we're not hiding the teaser:
2019              // (if we're hiding the teaser we display this as a normal page ie: no anchor)
2020              if( $params['anchor_text'] == '#' )
2021              { // TRANS: this is the default text displayed once the more link has been activated
2022                  $params['anchor_text'] = '<p class="bMore">'.T_('Follow up:').'</p>';
2023              }
2024  
2025              return format_to_output( '<a id="more'.$this->ID.'" name="more'.$this->ID.'"'.$class_attr.'></a>'
2026                              .$params['anchor_text'], $params['format'] );
2027          }
2028      }
2029  
2030  
2031      /**
2032       * Does the post have different content parts (teaser/extension, divided by "<!--more-->")?
2033       * This is also true for posts that have images with "aftermore" position.
2034       *
2035       * @access public
2036       * @return boolean
2037       */
2038  	function has_content_parts($params)
2039      {
2040          // Make sure we are not missing any param:
2041          $params = array_merge( array(
2042                  'disppage'    => '#',
2043                  'format'      => 'htmlbody',
2044              ), $params );
2045  
2046          $content_page = $this->get_content_page($params['disppage'], $params['format']);
2047  
2048          return strpos($content_page, '<!--more-->') !== false
2049              || $this->get_images( array('restrict_to_image_position'=>'aftermore') );
2050      }
2051  
2052  
2053      /**
2054       * Template function: display deadline date (datetime) of Item
2055       *
2056       * @param string date/time format: leave empty to use locale default date format
2057       * @param boolean true if you want GMT
2058       */
2059  	function deadline_date( $format = '', $useGM = false )
2060      {
2061          if( empty($format) )
2062              echo mysql2date( locale_datefmt(), $this->datedeadline, $useGM);
2063          else
2064              echo mysql2date( $format, $this->datedeadline, $useGM);
2065      }
2066  
2067  
2068      /**
2069       * Template function: display deadline time (datetime) of Item
2070       *
2071       * @param string date/time format: leave empty to use locale default time format
2072       * @param boolean true if you want GMT
2073       */
2074  	function deadline_time( $format = '', $useGM = false )
2075      {
2076          if( empty($format) )
2077              echo mysql2date( locale_timefmt(), $this->datedeadline, $useGM );
2078          else
2079              echo mysql2date( $format, $this->datedeadline, $useGM );
2080      }
2081  
2082  
2083      /**
2084       * Get array of tags.
2085       *
2086       * Load from DB if necessary, prefetching any other tags from MainList/ItemList.
2087       *
2088       * @return array
2089       */
2090      function & get_tags()
2091      {
2092          global $DB;
2093  
2094          if( ! isset( $this->tags ) )
2095          {
2096              $ItemTagsCache = & get_ItemTagsCache();
2097              if( ! isset($ItemTagsCache[$this->ID]) )
2098              {
2099                  /* Only try to fetch tags for items that are not yet in
2100                   * the cache. This will always give at least the ID of
2101                   * this Item.
2102                   */
2103                  $prefetch_item_IDs = array_diff( $this->get_prefetch_itemlist_IDs(), array_keys( $ItemTagsCache ) );
2104                  // Assume these items don't have any tags:
2105                  foreach( $prefetch_item_IDs as $item_ID )
2106                  {
2107                      $ItemTagsCache[$item_ID] = array();
2108                  }
2109  
2110                  // Now fetch the tags:
2111                  foreach( $DB->get_results('
2112                      SELECT itag_itm_ID, tag_name
2113                          FROM T_items__itemtag INNER JOIN T_items__tag ON itag_tag_ID = tag_ID
2114                       WHERE itag_itm_ID IN ('.$DB->quote($prefetch_item_IDs).')
2115                       ORDER BY tag_name', OBJECT, 'Get tags for items' ) as $row )
2116                  {
2117                      $ItemTagsCache[$row->itag_itm_ID][] = $row->tag_name;
2118                  }
2119  
2120                  //pre_dump( $ItemTagsCache );
2121              }
2122  
2123              $this->tags = $ItemTagsCache[$this->ID];
2124          }
2125  
2126          return $this->tags;
2127      }
2128  
2129  
2130      /**
2131       * Get the title for the <title> tag
2132       *
2133       * If it's not specifically entered, use the regular post title instead
2134       */
2135  	function get_titletag()
2136      {
2137          if( empty($this->titletag) )
2138          {
2139              return $this->title;
2140          }
2141  
2142          return $this->titletag;
2143      }
2144  
2145      /**
2146       * Get the meta description tag
2147       *
2148       */
2149  	function get_metadesc()
2150      {
2151          return $this->get_setting( 'post_metadesc' );
2152      }
2153  
2154      /**
2155       * Get the meta keyword tag
2156       *
2157       */
2158  	function get_custom_headers()
2159      {
2160          return $this->get_setting( 'post_custom_headers' );
2161      }
2162  
2163  
2164      /**
2165       * Split tags by comma or semicolon
2166       *
2167       * @param string The tags, separated by comma or semicolon
2168       */
2169  	function set_tags_from_string( $tags )
2170      {
2171          if( $tags === '' )
2172          {
2173              $this->tags = array();
2174              return;
2175          }
2176          $this->tags = preg_split( '/\s*[;,]+\s*/', $tags, -1, PREG_SPLIT_NO_EMPTY );
2177          array_walk( $this->tags, create_function( '& $tag', '$tag = evo_strtolower( $tag );' ) );
2178          $this->tags = array_unique( $this->tags );
2179          // pre_dump( $this->tags );
2180      }
2181  
2182  
2183      /**
2184       * Template function: Provide link to message form for this Item's author.
2185       *
2186       * @param string url of the message form
2187       * @param string to display before link
2188       * @param string to display after link
2189       * @param string link text
2190       * @param string link title
2191       * @param string class name
2192       * @return boolean true, if a link was displayed; false if there's no email address for the Item's author.
2193       */
2194  	function msgform_link( $params = array() )
2195      {
2196          // Make sure we are not missing any param:
2197          $params = array_merge( array(
2198                  'before'      => ' ',
2199                  'after'       => ' ',
2200                  'text'        => '#',
2201                  'title'       => '#',
2202                  'class'       => '',
2203                  'format'      => 'htmlbody',
2204                  'form_url'    => '#current_blog#',
2205              ), $params );
2206  
2207  
2208          if( $params['form_url'] == '#current_blog#' )
2209          {    // Get
2210              global $Blog;
2211              $params['form_url'] = $Blog->get('msgformurl');
2212          }
2213  
2214          $this->get_creator_User();
2215          $redirect_to = url_add_param( $params['form_url'], 'post_id='.$this->ID.'&recipient_id='.$this->creator_User->ID, '&' );
2216          $params['form_url'] = $this->creator_User->get_msgform_url( url_add_param( $params['form_url'], 'post_id='.$this->ID ), $redirect_to );
2217  
2218          if( empty( $params['form_url'] ) )
2219          {
2220              return false;
2221          }
2222  
2223          if( $params['title'] == '#' )
2224          {
2225              if( $this->creator_User->get_msgform_possibility() == 'email' )
2226              {
2227                  $params['title'] = T_('Send email to post author');
2228              }
2229              else
2230              {
2231                  $params['title'] = T_('Send message to post author');
2232              }
2233          }
2234          if( $params['text'] == '#' ) $params['text'] = get_icon( 'email', 'imgtag', array( 'class' => 'middle', 'title' => $params['title'] ) );
2235  
2236          echo $params['before'];
2237          echo '<a href="'.$params['form_url'].'" title="'.$params['title'].'"';
2238          if( !empty( $params['class'] ) ) echo ' class="'.$params['class'].'"';
2239          echo ' rel="nofollow">'.$params['text'].'</a>';
2240          echo $params['after'];
2241  
2242          return true;
2243      }
2244  
2245  
2246      /**
2247       * Template function: Provide link to message form for this Item's assigned User.
2248       *
2249       * @param string url of the message form
2250       * @param string to display before link
2251       * @param string to display after link
2252       * @param string link text
2253       * @param string link title
2254       * @param string class name
2255       * @return boolean true, if a link was displayed; false if there's no email address for the assigned User.
2256       */
2257  	function msgform_link_assigned( $form_url, $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '' )
2258      {
2259          if( ! $this->get_assigned_User() || empty($this->assigned_User->email) )
2260          { // We have no email for this Author :(
2261              return false;
2262          }
2263  
2264          $form_url = url_add_param( $form_url, 'recipient_id='.$this->assigned_User->ID );
2265          $form_url = url_add_param( $form_url, 'post_id='.$this->ID );
2266  
2267          if( $title == '#' ) $title = T_('Send email to assigned user');
2268          if( $text == '#' ) $text = get_icon( 'email', 'imgtag', array( 'class' => 'middle', 'title' => $title ) );
2269  
2270          echo $before;
2271          echo '<a href="'.$form_url.'" title="'.$title.'"';
2272          if( !empty( $class ) ) echo ' class="'.$class.'"';
2273          echo ' rel="nofollow">'.$text.'</a>';
2274          echo $after;
2275  
2276          return true;
2277      }
2278  
2279  
2280      /**
2281       * Display a link to pages for multi-page items
2282       *
2283       * @param array of params
2284       * @param string Output format, see {@link format_to_output()}
2285       */
2286  	function page_links()
2287      {
2288          $num_args = func_num_args();
2289          $args = func_get_args();
2290  
2291          if( $num_args == 1 && is_array($args[0]) )
2292          {
2293              $params = $args[0];
2294              if( !isset($params['format']) ) $params['format'] = 'htmlbody';
2295          }
2296          else
2297          {    // Deprecated since v5, left for compatibility with old skins
2298              $params['before']        = isset($args[0]) ? $args[0] : '<p class="right">'.T_('Pages:').' ';
2299              $params['after']        = isset($args[1]) ? $args[1] : '</p>';
2300              $params['separator']    = isset($args[2]) ? $args[2] : ' ';
2301              $params['single']        = isset($args[3]) ? $args[3] : '';
2302              $params['current_page']    = isset($args[4]) ? $args[4] : '#';
2303              $params['pagelink']        = isset($args[5]) ? $args[5] : '%d';
2304              $params['url']            = isset($args[6]) ? $args[6] : '';
2305              $params['format']        = 'htmlbody';
2306          }
2307  
2308          echo $this->get_page_links( $params, $params['format'] );
2309      }
2310  
2311  
2312      /**
2313       * Get a link to pages for multi-page items
2314       *
2315       * @param array of params
2316       * @param string Output format, see {@link format_to_output()}
2317       */
2318  	function get_page_links( $params = array(), $format = 'htmlbody' )
2319      {
2320          $params = array_merge( array(
2321                      'before'       => '<p class="right">'.T_('Pages:').' ',
2322                      'after'        => '</p>',
2323                      'separator'    => ' ',
2324                      'single'       => '',
2325                      'current_page' => '#',
2326                      'pagelink'     => '%d',
2327                      'url'          => '',
2328                  ), $params );
2329  
2330          // Make sure, the pages are split up:
2331          $this->split_pages();
2332  
2333          if( $this->pages <= 1 )
2334          { // Single page:
2335              return $params['single'];
2336          }
2337  
2338          if( $params['separator'] == NULL )
2339          { // Don't display pages
2340              if( $params['before'] !== NULL )
2341              {
2342                  return format_to_output( $params['before'].$params['after'], $format );
2343              }
2344              return;
2345          }
2346  
2347          if( $params['current_page'] == '#' )
2348          {
2349              global $page;
2350              $params['current_page'] = $page;
2351          }
2352  
2353          if( empty($params['url']) )
2354          {
2355              $params['url'] = $this->get_permanent_url( '', '', '&amp;' );
2356          }
2357  
2358          $page_links = array();
2359  
2360          for( $i = 1; $i <= $this->pages; $i++ )
2361          {
2362              $text = str_replace('%d', $i, $params['pagelink']);
2363  
2364              if( $i != $params['current_page'] )
2365              {
2366                  if( $i == 1 )
2367                  {    // First page special:
2368                      $page_links[] = '<a href="'.$params['url'].'">'.$text.'</a>';
2369                  }
2370                  else
2371                  {
2372                      $page_links[] = '<a href="'.url_add_param( $params['url'], 'page='.$i ).'">'.$text.'</a>';
2373                  }
2374              }
2375              else
2376              {
2377                  $page_links[] = $text;
2378              }
2379          }
2380  
2381          $r = $params['before'].implode( $params['separator'], $page_links ).$params['after'];
2382  
2383          return format_to_output( $r, $format );
2384      }
2385  
2386  
2387      /**
2388       * Get an attached image tag with lightbox reference
2389       *
2390       * @private function
2391       *
2392       * @param object the attached image File
2393       * @param array params
2394       * @return string the attached image tag
2395       */
2396  	function get_attached_image_tag( $File, $params )
2397      {
2398          // Make sure $link_to is set
2399          $link_to = isset( $params['image_link_to'] ) ? $params['image_link_to'] : 'original';
2400  
2401          if( $link_to == 'single' )
2402          { // We're linking to the post (displayed on a single post page):
2403              $link_to = $this->get_permanent_url( $link_to );
2404              $link_title = $this->title;
2405              $link_rel = '';
2406          }
2407          else
2408          { // We're linking to the original image, let lighbox (or clone) quick in:
2409              $link_title = '#title#';    // This title will be used by lightbox (colorbox for instance)
2410              $link_rel = 'lightbox[p'.$this->ID.']';    // Make one "gallery" per post.
2411          }
2412  
2413          // Generate the IMG tag with all the alt, title and desc if available
2414          return $File->get_tag( $params['before_image'], $params['before_image_legend'], $params['after_image_legend'],
2415                  $params['after_image'], $params['image_size'], $link_to, $link_title, $link_rel, '', '', $this->title );
2416      }
2417  
2418  
2419      /**
2420       * Display the images linked to the current Item
2421       *
2422       * @param array of params
2423       * @param string Output format, see {@link format_to_output()}
2424       */
2425  	function images( $params = array(), $format = 'htmlbody' )
2426      {
2427          echo $this->get_images( $params, $format );
2428      }
2429  
2430  
2431      /**
2432       * Get block of images linked to the current Item
2433       *
2434       * @param array of params
2435       * @param string Output format, see {@link format_to_output()}
2436       */
2437  	function get_images( $params = array(), $format = 'htmlbody' )
2438      {
2439          global $Plugins;
2440  
2441          $r = '';
2442  
2443          $params = array_merge( array(
2444                  'before'                     => '<div>',
2445                  'before_image'               => '<div class="image_block">',
2446                  'before_image_legend'        => '<div class="image_legend">',
2447                  'after_image_legend'         => '</div>',
2448                  'after_image'                => '</div>',
2449                  'after'                      => '</div>',
2450                  'image_size'                 => 'fit-720x500',
2451                  'image_link_to'              => 'original', // Can be 'orginal' (image) or 'single' (this post)
2452                  'limit'                      => 1000, // Max # of images displayed
2453                  'before_gallery'             => '<div class="bGallery">',
2454                  'after_gallery'              => '</div>',
2455                  'gallery_image_size'         => 'crop-80x80',
2456                  'gallery_image_limit'        => 1000,
2457                  'gallery_colls'              => 5,
2458                  'gallery_order'              => '', // 'ASC', 'DESC', 'RAND'
2459                  'restrict_to_image_position' => 'teaser,aftermore', // 'teaser'|'aftermore'|'inline'
2460                  'data'                       =>  & $r,
2461              ), $params );
2462  
2463          // Get list of attached files
2464          $LinkOnwer = new LinkItem( $this );
2465          if( ! $FileList = $LinkOnwer->get_attachment_FileList( $params['limit'], $params['restrict_to_image_position'] ) )
2466          {
2467              return '';
2468          }
2469  
2470          $galleries = array();
2471  
2472          /**
2473           * @var File
2474           */
2475          $File = NULL;
2476  
2477          while( $File = & $FileList->get_next() )
2478          {
2479              $params['File'] = $File;
2480  
2481              if( ! $File->exists() )
2482              {
2483                  global $Debuglog;
2484                  $Debuglog->add(sprintf('File linked to item #%d does not exist (%s)!', $this->ID, $File->get_full_path()), array('error', 'files'));
2485                  continue;
2486              }
2487  
2488              if( $File->is_dir() && $params['gallery_image_limit'] > 0 )
2489              {    // This is a directory/gallery
2490                  if( ($gallery = $File->get_gallery($params)) != '' )
2491                  {    // Got gallery code
2492                      $galleries[] = $gallery;
2493                  }
2494                  continue;
2495              }
2496  
2497              if( count($Plugins->trigger_event_first_true('RenderItemAttachment', $params)) != 0 )
2498              {
2499                  continue;
2500              }
2501  
2502              if( ! $File->is_image() )
2503              {    // Skip anything that is not an image
2504                  // fp> TODO: maybe this property should be stored in link_ltype_ID
2505  
2506                  //$r .= $this->attachment_files($params);
2507                  continue;
2508              }
2509  
2510              // Generate the IMG tag with all the alt, title and desc if available
2511              $r .= $this->get_attached_image_tag( $File, $params );
2512          }
2513  
2514          if( !empty($r) )
2515          {
2516              $r = $params['before'].$r.$params['after'];
2517  
2518              // Character conversions
2519              $r = format_to_output( $r, $format );
2520          }
2521  
2522          if( !empty($galleries) )
2523          {    // Append galleries
2524              // sam2kb> It's done like that only until we figure out a better way to display galleries.
2525  
2526              /*
2527              sam2kb> TODO: use shortcode [gallery option1="value1" option2="value2"]
2528                  'columns' - table columns
2529                  'limit' - a number of images,
2530                  'size' - selected/large image size
2531                  'thumbsize' - thumbnails image size
2532                  'order' - files order ASC/DESC/RAND
2533              */
2534  
2535              // Character conversions
2536              $r .= "\n".format_to_output( implode("\n", $galleries), $format );
2537          }
2538  
2539          return $r;
2540      }
2541  
2542  
2543      /**
2544       * Display the attachments/files linked to the current Item
2545       *
2546       * @param array Array of params
2547       * @param string Output format, see {@link format_to_output()}
2548       */
2549  	function files( $params = array(), $format = 'htmlbody' )
2550      {
2551          echo $this->get_files( $params, $format );
2552      }
2553  
2554  
2555      /**
2556       * Get block of attachments/files linked to the current Item
2557       *
2558       * @param array Array of params
2559       * @param string Output format, see {@link format_to_output()}
2560       * @return string HTML
2561       */
2562  	function get_files( $params = array(), $format = 'htmlbody' )
2563      {
2564          global $Plugins;
2565          $params = array_merge( array(
2566                  'before' =>              '<div class="item_attachments"><h3>'.T_('Attachments').':</h3><ul class="bFiles">',
2567                  'before_attach' =>         '<li>',
2568                  'before_attach_size' =>    '<span class="file_size">(',
2569                  'after_attach_size' =>     ')</span>',
2570                  'after_attach' =>          '</li>',
2571                  'after' =>               '</ul></div>',
2572              // fp> TODO: we should only have one limit param. Or is there a good reason for having two?
2573              // sam2kb> It's needed only for flexibility, in the meantime if user attaches 200 files he expects to see all of them in skin, I think.
2574                  'limit_attach' =>        1000, // Max # of files displayed
2575                  'limit' =>               1000,
2576                  'restrict_to_image_position' => '',    // Optionally restrict to files/images linked to specific position: 'teaser'|'aftermore'
2577                  'data'                       =>  '',
2578                  'attach_format'              => '$icon_link$ $file_link$ $file_size$', // $icon_link$ $icon$ $file_link$ $file_size$
2579                  'file_link_format'           => '$file_name$', // $icon$ $file_name$ $file_size$
2580                  'file_link_class'            => '',
2581                  'download_link_icon'         => 'download',
2582                  'download_link_title'        => T_('Download file'),
2583                  'file_size_abbr'             => true, // Use HTML <abbr> tag for type of the file size
2584                  'file_size_info'             => true, // Display full text of size type when $params['file_size_abbr'] == false
2585              ), $params );
2586  
2587          // Get list of attached files
2588          $LinkOnwer = new LinkItem( $this );
2589          if( ! $FileList = $LinkOnwer->get_attachment_FileList( $params['limit'], $params['restrict_to_image_position'] ) )
2590          {
2591              return '';
2592          }
2593  
2594          load_funcs('files/model/_file.funcs.php');
2595  
2596          $r = '';
2597          $i = 0;
2598          $r_file = array();
2599          /**
2600           * @var File
2601           */
2602          $File = NULL;
2603          while( ( $File = & $FileList->get_next() ) && $params['limit_attach'] > $i )
2604          {
2605              $params['File'] = $File;
2606  
2607              if( ! $File->exists() )
2608              { // File doesn't exist
2609                  global $Debuglog;
2610                  $Debuglog->add( sprintf( 'File linked to item #%d does not exist (%s)!', $this->ID, $File->get_full_path() ), array( 'error', 'files' ) );
2611                  continue;
2612              }
2613  
2614              if( count( $Plugins->trigger_event_first_true( 'RenderItemAttachment', $params ) ) != 0 )
2615              {
2616                  continue;
2617              }
2618  
2619              if( $File->is_image() )
2620              { // Skip images because these are displayed inline already
2621                  // fp> TODO: have a setting for each linked file to decide whether it should be displayed inline or as an attachment
2622                  continue;
2623              }
2624              elseif( $File->is_dir() )
2625              { // Skip directories/galleries
2626                  continue;
2627              }
2628  
2629              if( $File->is_audio() )
2630              { // Player for audio file
2631                  $r_file[$i]  = '<div class="podplayer">';
2632                  $r_file[$i] .= $this->get_player( $File->get_url() );
2633                  $r_file[$i] .= '</div>';
2634              }
2635              else
2636              { // A link to download a file
2637  
2638                  // Just icon with download icon
2639                  $icon = ( strpos( $params['attach_format'].$params['file_link_format'], '$icon$' ) !== false ) ?
2640                          get_icon( $params['download_link_icon'], 'imgtag', array( 'title' => $params['download_link_title'] ) ) : '';
2641  
2642                  // A link with icon to download
2643                  $icon_link = ( strpos( $params['attach_format'], '$icon_link$' ) !== false ) ?
2644                          action_icon( $params['download_link_title'], $params['download_link_icon'], $File->get_url(), '', 5 ) : '';
2645  
2646                  // File size info
2647                  $file_size = ( strpos( $params['attach_format'].$params['file_link_format'], '$file_size$' ) !== false ) ?
2648                          $params['before_attach_size'].bytesreadable( $File->get_size(), $params['file_size_abbr'], $params['file_size_info'] ).$params['after_attach_size'] : '';
2649  
2650                  // A link with file name to download
2651                  $file_link_format = str_replace( array( '$icon$', '$file_name$', '$file_size$' ),
2652                      array( $icon, '$text$', $file_size ),
2653                      $params['file_link_format'] );
2654                  $file_link = ( strpos( $params['attach_format'], '$file_link$' ) !== false ) ?
2655                          $File->get_view_link( $File->get_name(), NULL, NULL, $file_link_format, $params['file_link_class'] ) : '';
2656  
2657                  $r_file[$i] = $params['before_attach'];
2658                  $r_file[$i] .= str_replace( array( '$icon$', '$icon_link$', '$file_link$', '$file_size$' ),
2659                      array( $icon, $icon_link, $file_link, $file_size ),
2660                      $params['attach_format'] );
2661                  $r_file[$i] .= $params['after_attach'];
2662              }
2663  
2664              $i++;
2665          }
2666  
2667          if( !empty($r_file) )
2668          {
2669              $r = $params['before'].implode( "\n", $r_file ).$params['after'];
2670  
2671              // Character conversions
2672              $r = format_to_output( $r, $format );
2673          }
2674  
2675          return $r;
2676      }
2677  
2678  
2679      /**
2680       * @param array Associative array of parameters
2681       * @return string Output
2682       */
2683  	function attachment_files( & $params/* = array()*/ )
2684      {
2685          global $Plugins;
2686  
2687          $r = '';
2688  
2689          $ItemAttachment_plugins = $Plugins->get_list_by_event( 'RenderItemAttachment' );
2690  
2691                  $Plugins->trigger_event_first_true('RenderItemAttachment', $params);
2692  
2693          return $r;
2694      }
2695  
2696  
2697      /**
2698       * Template function: Displays link to the feed for comments on this item
2699       *
2700       * @param string Type of feedback to link to (rss2/atom)
2701       * @param string String to display before the link (if comments are to be displayed)
2702       * @param string String to display after the link (if comments are to be displayed)
2703       * @param string Link title
2704       */
2705  	function feedback_feed_link( $skin = '_rss2', $before = '', $after = '', $title='#' )
2706      {
2707          if( ! $this->can_see_comments() )
2708          {    // Comments disabled
2709              return;
2710          }
2711  
2712          $this->load_Blog();
2713  
2714          if( $this->Blog->get_setting( 'comment_feed_content' ) == 'none' )
2715          {    // Comment feeds disabled
2716              return;
2717          }
2718  
2719          if( $title == '#' )
2720          {
2721              $title = get_icon( 'feed' ).' '.T_('Comment feed for this post');
2722          }
2723  
2724          $url = $this->get_feedback_feed_url($skin);
2725  
2726          echo $before;
2727          echo '<a href="'.$url.'">'.format_to_output($title).'</a>';
2728          echo $after;
2729      }
2730  
2731  
2732      /**
2733       * Get URL to display the post comments in an XML feed.
2734       *
2735       * @param string
2736       */
2737  	function get_feedback_feed_url( $skin_folder_name )
2738      {
2739          $this->load_Blog();
2740  
2741          return url_add_param( $this->Blog->get_tempskin_url( $skin_folder_name ), 'disp=comments&amp;p='.$this->ID );
2742      }
2743  
2744  
2745      /**
2746       * Get URL to display the post comments.
2747       *
2748       * @return string
2749       */
2750  	function get_feedback_url( $popup = false, $glue = '&amp;' )
2751      {
2752          $url = $this->get_single_url( 'auto', '', $glue );
2753          if( $popup )
2754          {
2755              $url = url_add_param( $url, 'disp=feedback-popup', $glue );
2756          }
2757  
2758          return $url;
2759      }
2760  
2761  
2762      /**
2763       * Template function: Displays link to feedback page (under some conditions)
2764       *
2765       * @param array
2766       */
2767  	function feedback_link( $params )
2768      {
2769          echo $this->get_feedback_link( $params );
2770      }
2771  
2772  
2773      /**
2774       * Get a link to feedback page (under some conditions)
2775       *
2776       * @param array
2777       */
2778  	function get_feedback_link( $params )
2779      {
2780          global $ReqURL;
2781  
2782          if( ! $this->can_see_comments() )
2783          {    // Comments disabled
2784              return;
2785          }
2786  
2787          $params = array_merge( array(
2788                                      'type' => 'feedbacks',        // Kind of feedbacks to count
2789                                      'status' => 'published',    // Status of feedbacks to count
2790                                      'link_before' => '',
2791                                      'link_after' => '',
2792                                      'link_text_zero' => '#',
2793                                      'link_text_one' => '#',
2794                                      'link_text_more' => '#',
2795                                      'link_anchor_zero' => '#',
2796                                      'link_anchor_one' => '#',
2797                                      'link_anchor_more' => '#',
2798                                      'link_title' => '#',
2799                                      'use_popup' => false,
2800                                      'show_in_single_mode' => false,        // Do we want to show this link even if we are viewing the current post in single view mode
2801                                      'url' => '#',
2802                                  ), $params );
2803  
2804          if( $params['show_in_single_mode'] == false && is_same_url( $this->get_permanent_url('','','&'), $ReqURL ) )
2805          {    // We are viewing the single page for this pos, which (typically) )contains comments, so we dpn't want to display this link
2806              return;
2807          }
2808  
2809          // dh> TODO:    Add plugin hook, where a Pingback plugin could hook and provide "pingbacks"
2810          switch( $params['type'] )
2811          {
2812              case 'feedbacks':
2813                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display feedback / Leave a comment');
2814                  if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('Send feedback').' &raquo;';
2815                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 feedback').' &raquo;';
2816                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d feedbacks').' &raquo;';
2817                  break;
2818  
2819              case 'comments':
2820                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display comments / Leave a comment');
2821                  if( $params['link_text_zero'] == '#' )
2822                  {
2823                      if( $this->can_comment( NULL ) ) // NULL, because we do not want to display errors here!
2824                      {
2825                          $params['link_text_zero'] = T_('Leave a comment').' &raquo;';
2826                      }
2827                      else
2828                      {
2829                          $params['link_text_zero'] = '';
2830                      }
2831                  }
2832                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 comment').' &raquo;';
2833                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d comments').' &raquo;';
2834                  break;
2835  
2836              case 'trackbacks':
2837                  $this->get_Blog();
2838                  if( ! $this->can_receive_pings() )
2839                  { // Trackbacks not allowed on this blog:
2840                      return;
2841                  }
2842                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display trackbacks / Get trackback address for this post');
2843                  if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('Send a trackback').' &raquo;';
2844                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 trackback').' &raquo;';
2845                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d trackbacks').' &raquo;';
2846                  break;
2847  
2848              case 'pingbacks':
2849                  // Obsolete, but left for skin compatibility
2850                  $this->get_Blog();
2851                  if( ! $this->can_receive_pings() )
2852                  { // Trackbacks not allowed on this blog:
2853                      // We'll consider pingbacks to follow the same restriction
2854                      return;
2855                  }
2856                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display pingbacks');
2857                  if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('No pingback yet').' &raquo;';
2858                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 pingback').' &raquo;';
2859                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d pingbacks').' &raquo;';
2860                  break;
2861  
2862              default:
2863                  debug_die( "Unknown feedback type [{$params['type']}]" );
2864          }
2865  
2866          $link_text = $this->get_feedback_title( $params['type'], $params['link_text_zero'], $params['link_text_one'], $params['link_text_more'], $params['status'] );
2867  
2868          if( empty($link_text) )
2869          {    // No link, no display...
2870              return false;
2871          }
2872  
2873          if( $params['url'] == '#' )
2874          { // We want a link to single post:
2875              $params['url'] = $this->get_feedback_url();
2876          }
2877  
2878          // Anchor position
2879          $number = generic_ctp_number( $this->ID, $params['type'], $params['status'] );
2880  
2881          if( $number == 0 )
2882              $anchor = $params['link_anchor_zero'];
2883          elseif( $number == 1 )
2884              $anchor = $params['link_anchor_one'];
2885          elseif( $number > 1 )
2886              $anchor = $params['link_anchor_more'];
2887          if( $anchor == '#' )
2888          {
2889              $anchor = '#'.$params['type'];
2890          }
2891  
2892          $r = $params['link_before'];
2893  
2894          if( !empty( $params['url'] ) )
2895          {
2896              $r .= '<a href="'.$params['url'].$anchor.'" ';    // Position on feedback
2897              $r .= 'title="'.$params['link_title'].'"';
2898              if( $params['use_popup'] )
2899              { // Special URL if we can open a popup (i-e if JS is enabled):
2900                  $popup_url = url_add_param( $params['url'], 'disp=feedback-popup' );
2901                  $r .= ' onclick="return pop_up_window( \''.$popup_url.'\', \'evo_comments\' )"';
2902              }
2903              $r .= '>';
2904              $r .= $link_text;
2905              $r .= '</a>';
2906          }
2907          else
2908          {
2909              $r .= $link_text;
2910          }
2911  
2912          $r .= $params['link_after'];
2913  
2914          return $r;
2915      }
2916  
2917  
2918      /**
2919       * Return true if there is any feedback of given type.
2920       *
2921       * @param array
2922       * @return boolean
2923       */
2924  	function has_feedback( $params )
2925      {
2926          $params = array_merge( array(
2927                              'type' => 'feedbacks',
2928                              'status' => 'published'
2929                          ), $params );
2930  
2931          // Check is a given type is allowed
2932          switch( $params['type'] )
2933          {
2934              case 'feedbacks':
2935              case 'comments':
2936              case 'trackbacks':
2937              case 'pingbacks':
2938                  break;
2939              default:
2940                  debug_die( "Unknown feedback type [{$params['type']}]" );
2941          }
2942  
2943          $number = generic_ctp_number( $this->ID, $params['type'], $params['status'] );
2944  
2945          return $number > 0;
2946      }
2947  
2948  
2949      /**
2950       * Return true if trackbacks and pingbacks are allowed
2951       *
2952       * @return boolean
2953       */
2954  	function can_receive_pings()
2955      {
2956          $this->load_Blog();
2957          return $this->Blog->get( 'allowtrackbacks' ) && $this->can_comment( NULL );
2958      }
2959  
2960  
2961      /**
2962       * Get text depending on number of comments
2963       *
2964       * @param string Type of feedback to link to (feedbacks (all)/comments/trackbacks/pingbacks)
2965       * @param string Link text to display when there are 0 comments
2966       * @param string Link text to display when there is 1 comment
2967       * @param string Link text to display when there are >1 comments (include %d for # of comments)
2968       * @param string Status of feedbacks to count
2969       */
2970  	function get_feedback_title( $type = 'feedbacks',    $zero = '#', $one = '#', $more = '#', $status = 'published', $filter_by_perm = true )
2971      {
2972          if( ! $this->can_see_comments() )
2973          {    // Comments disabled
2974              return NULL;
2975          }
2976  
2977          // dh> TODO:    Add plugin hook, where a Pingback plugin could hook and provide "pingbacks"
2978          switch( $type )
2979          {
2980              case 'feedbacks':
2981                  if( $zero == '#' ) $zero = '';
2982                  if( $one == '#' ) $one = T_('1 feedback');
2983                  if( $more == '#' ) $more = T_('%d feedbacks');
2984                  break;
2985  
2986              case 'comments':
2987                  if( $zero == '#' ) $zero = '';
2988                  if( $one == '#' ) $one = T_('1 comment');
2989                  if( $more == '#' ) $more = T_('%d comments');
2990                  break;
2991  
2992              case 'trackbacks':
2993                  if( $zero == '#' ) $zero = '';
2994                  if( $one == '#' ) $one = T_('1 trackback');
2995                  if( $more == '#' ) $more = T_('%d trackbacks');
2996                  break;
2997  
2998              case 'pingbacks':
2999                  // Obsolete, but left for skin compatibility
3000                  if( $zero == '#' ) $zero = '';
3001                  if( $one == '#' ) $one = T_('1 pingback');
3002                  if( $more == '#' ) $more = T_('%d pingbacks');
3003                  break;
3004  
3005              default:
3006                  debug_die( "Unknown feedback type [$type]" );
3007          }
3008  
3009          $number = generic_ctp_number( $this->ID, $type, $status, false, $filter_by_perm );
3010          if( !$filter_by_perm )
3011          { // This is the case when we are only counting comments awaiting moderation, return only not visible feedbacks number
3012              // count feedbacks with the same statuses where user has permission
3013              $visible_number = generic_ctp_number( $this->ID, $type, $status, false, true );
3014              $number = $number - $visible_number;
3015          }
3016  
3017          if( $number == 0 )
3018              return $zero;
3019          elseif( $number == 1 )
3020              return $one;
3021          elseif( $number > 1 )
3022              return str_replace( '%d', $number, $more );
3023      }
3024  
3025  
3026      /**
3027       * Get table from ratings data
3028       *
3029       * @param array ratings data
3030       * @param array params
3031       */
3032  	function get_rating_table( $ratings, $params )
3033      {
3034          $ratings_count = $ratings['all_ratings'];
3035          $average_real = ( $ratings_count > 0 ) ? number_format( $ratings["summary"] / $ratings_count, 1, ".", "" ) : 0;
3036          $average = ceil( ( $average_real ) / 5 * 100 );
3037  
3038          $table = '<table class="rating_summary" cellspacing="1">';
3039          foreach ( $ratings as $r => $count )
3040          {    // Print a row for each star with formed data
3041              if( !is_int($r) )
3042              {
3043                  continue;
3044              }
3045  
3046              $star_average = ( $ratings_count > 0 ) ? ceil( ( $count / $ratings_count ) * 100 ) : 0;
3047              switch( $params['rating_summary_star_totals'] )
3048              {
3049                  case 'count':
3050                      $star_value = '('.$count.')';
3051                  break;
3052                  case 'percent':
3053                      $star_value = '('.$star_average.'%)';
3054                  break;
3055                  case 'none':
3056                  default:
3057                      $star_value = "";
3058                  break;
3059              }
3060              $table .= '<tr><th>'.$r.' '.T_('star').':</th>
3061                  <td class="progress"><div style="width:'.$star_average.'%">&nbsp;</div></td>
3062                  <td>'.$star_value.'</td><tr>';
3063          }
3064          $table .= '</table>';
3065  
3066          $table .= '<div class="rating_summary_total">
3067              '.$ratings_count.' '.( $ratings_count > 1 ? T_('ratings') : T_('rating') ).'
3068              <div class="average_rating">'.T_('Average user rating').':<br />
3069              '.get_star_rating( $average_real ).'<span>('.$average_real.')</span>
3070              </div></div><div class="clear"></div>';
3071  
3072          return $table;
3073      }
3074  
3075  
3076      /**
3077       * Get table with rating summary
3078       *
3079       * @param array of params
3080       */
3081  	function get_rating_summary( $params = array() )
3082      {
3083          // Make sure we are not missing any param:
3084          $params = array_merge( array(
3085              'rating_summary_star_totals' => 'count' // Possible values: 'count', 'percent' and 'none'
3086          ), $params );
3087  
3088          // get ratings and active ratings ( active ratings are younger then post_expiry_delay )
3089          list( $ratings, $active_ratings ) = $this->get_ratings();
3090          $ratings_count = $ratings['all_ratings'];
3091          $active_ratings_count = $active_ratings['all_ratings'];
3092          if( $ratings_count == 0 )
3093          {    // No Comments
3094              return NULL;
3095          }
3096  
3097          $average_real = number_format( $ratings["summary"] / $ratings_count, 1, ".", "" );
3098          $active_average_real = ( $active_ratings_count == 0 ) ? 0 : ( number_format( $active_ratings["summary"] / $active_ratings_count, 1, ".", "" ) );
3099  
3100          $result = '<table class="ratings_table" cellspacing="2">';
3101          $result .= '<tr>';
3102          $expiry_delay = $this->get_setting( 'post_expiry_delay' );
3103          if( empty( $expiry_delay ) )
3104          {
3105              $all_ratings_title = T_('User ratings');
3106          }
3107          else
3108          {
3109              $all_ratings_title = T_('Overall user ratings');
3110              $result .= '<td><div><strong>'.get_duration_title( $expiry_delay ).'</strong></div>';
3111              $result .= $this->get_rating_table( $active_ratings, $params );
3112              $result .= '</td>';
3113              $result .= '<td width="2px"></td>';
3114          }
3115  
3116          $result .= '<td><div><strong>'.$all_ratings_title.'</strong></div>';
3117          $result .= $this->get_rating_table( $ratings, $params );
3118          $result .= '</td>';
3119          $result .= '</tr></table>';
3120  
3121          return $result;
3122      }
3123  
3124  
3125      /**
3126       * Template function: Displays feeback moderation info
3127       *
3128       * @param string Type of feedback to link to (feedbacks (all)/comments/trackbacks/pingbacks)
3129       * @param string String to display before the link (if comments are to be displayed)
3130       * @param string String to display after the link (if comments are to be displayed)
3131       * @param string Link text to display when there are 0 comments
3132       * @param string Link text to display when there is 1 comment
3133       * @param string Link text to display when there are >1 comments (include %d for # of comments)
3134       * @param string Link
3135       * @param boolean true to hide if no feedback
3136       */
3137  	function feedback_moderation( $type = 'feedbacks', $before = '', $after = '',
3138              $zero = '', $one = '#', $more = '#', $edit_comments_link = '#', $params = array() )
3139      {
3140          /**
3141           * @var User
3142           */
3143          global $current_User;
3144  
3145          /* TODO: finish this...
3146          $params = array_merge( array(
3147                                      'type' => 'feedbacks',
3148                                      'block_before' => '',
3149                                      'blo_after' => '',
3150                                      'link_text_zero' => '#',
3151                                      'link_text_one' => '#',
3152                                      'link_text_more' => '#',
3153                                      'link_title' => '#',
3154                                      'use_popup' => false,
3155                                      'url' => '#',
3156                                      'type' => 'feedbacks',
3157                                  ), $params );
3158          */
3159  
3160          if( isset($current_User) &&    $current_User->check_perm( 'blog_comment!draft', 'moderate', false, $this->get_blog_ID() ) )
3161          {    // We have permission to edit comments:
3162              if( $edit_comments_link == '#' )
3163              {    // Use default link:
3164                  global $admin_url;
3165                  $edit_comments_link = '<a href="'.$admin_url.'?ctrl=items&amp;blog='.$this->get_blog_ID().'&amp;p='.$this->ID.'#comments" title="'.T_('Moderate these feedbacks').'">'.get_icon( 'edit' ).' '.T_('Moderate...').'</a>';
3166              }
3167          }
3168          else
3169          { // User has no right to edit comments:
3170              $edit_comments_link = '';
3171          }
3172  
3173          // Inject Edit/moderate link as relevant:
3174          $zero = str_replace( '%s', $edit_comments_link, $zero );
3175          $one = str_replace( '%s', $edit_comments_link, $one );
3176          $more = str_replace( '%s', $edit_comments_link, $more );
3177  
3178          $r = $this->get_feedback_title( $type, $zero, $one, $more, array( 'draft', 'review' ), false );
3179  
3180          if( !empty( $r ) )
3181          {
3182              echo $before.$r.$after;
3183          }
3184      }
3185  
3186  
3187  
3188      /**
3189       * Template tag: display footer for the current Item.
3190       *
3191       * @param array
3192       * @return boolean true if something has been displayed
3193       */
3194  	function footer( $params )
3195      {
3196          // Make sure we are not missing any param:
3197          $params = array_merge( array(
3198                  'mode'        => '#',                // Will detect 'single' from $disp automatically
3199                  'block_start' => '<div class="item_footer">',
3200                  'block_end'   => '</div>',
3201                  'format'      => 'htmlbody',
3202              ), $params );
3203  
3204          if( $params['mode'] == '#' )
3205          {
3206              global $disp;
3207              $params['mode'] = $disp;
3208          }
3209  
3210          // pre_dump( $params['mode'] );
3211  
3212          $this->get_Blog();
3213          switch( $params['mode'] )
3214          {
3215              case 'xml':
3216                  $text = $this->Blog->get_setting( 'xml_item_footer_text' );
3217                  break;
3218  
3219              case 'single':
3220                  $text = $this->Blog->get_setting( 'single_item_footer_text' );
3221                  break;
3222  
3223              default:
3224                  // Do NOT display!
3225                  $text = '';
3226          }
3227  
3228          $text = preg_replace_callback( '#\$([a-z_]+)\$#', array( $this, 'replace_callback' ), $text );
3229  
3230          if( empty($text) )
3231          {
3232              return false;
3233          }
3234  
3235          echo format_to_output( $params['block_start'].$text.$params['block_end'], $params['format'] );
3236  
3237          return true;
3238      }
3239  
3240  
3241      /**
3242       * Gets button for deleting the Item if user has proper rights
3243       *
3244       * @param string to display before link
3245       * @param string to display after link
3246       * @param string link text
3247       * @param string link title
3248       * @param string class name
3249       * @param boolean true to make this a button instead of a link
3250       * @param string page url for the delete action
3251       * @param string confirmation text
3252       */
3253  	function get_delete_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $button = false, $actionurl = '#', $confirm_text = '#', $redirect_to = '' )
3254      {
3255          global $current_User, $admin_url;
3256  
3257          if( ! is_logged_in( false ) ) return false;
3258  
3259          if( ! $current_User->check_perm( 'item_post!CURSTATUS', 'delete', false, $this ) )
3260          { // User has right to delete this post
3261              return false;
3262          }
3263  
3264          if( $text == '#' )
3265          {
3266              if( ! $button )
3267              {
3268                  $text = get_icon( 'delete', 'imgtag' ).' '.T_('Delete!');
3269              }
3270              else
3271              {
3272                  $text = T_('Delete!');
3273              }
3274          }
3275  
3276          if( $title == '#' ) $title = T_('Delete this post');
3277  
3278          if( $actionurl == '#' )
3279          {
3280              $actionurl = $admin_url.'?ctrl=items&amp;action=delete&amp;post_ID=';
3281          }
3282          $url = $actionurl.$this->ID.'&amp;'.url_crumb('item');
3283  
3284          if( !empty( $redirect_to ) )
3285          {
3286              $url = $url.'&amp;redirect_to='.rawurlencode( $redirect_to );
3287          }
3288  
3289          if( $confirm_text == '#' )
3290          {
3291              $confirm_text = TS_('You are about to delete this post!\\nThis cannot be undone!');
3292          }
3293  
3294          $r = $before;
3295          if( $button )
3296          { // Display as button
3297              $r .= '<input type="button"';
3298              $r .= ' value="'.$text.'" title="'.$title.'" onclick="if ( confirm(\'';
3299              $r .= $confirm_text;
3300              $r .= '\') ) { document.location.href=\''.$url.'\' }"';
3301              if( !empty( $class ) ) $r .= ' class="'.$class.'"';
3302              $r .= '/>';
3303          }
3304          else
3305          { // Display as link
3306              $r .= '<a href="'.$url.'" title="'.$title.'" onclick="return confirm(\'';
3307              $r .= $confirm_text;
3308              $r .= '\')"';
3309              if( !empty( $class ) ) $r .= ' class="'.$class.'"';
3310              $r .= '>'.$text.'</a>';
3311          }
3312          $r .= $after;
3313  
3314          return $r;
3315      }
3316  
3317  
3318      /**
3319       * Displays button for deleting the Item if user has proper rights
3320       *
3321       * @param string to display before link
3322       * @param string to display after link
3323       * @param string link text
3324       * @param string link title
3325       * @param string class name
3326       * @param boolean true to make this a button instead of a link
3327       * @param string page url for the delete action
3328       * @param string confirmation text
3329       */
3330  	function delete_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $button = false, $actionurl = '#', $confirm_text = '#', $redirect_to = '' )
3331      {
3332          echo $this->get_delete_link( $before, $after, $text, $title, $class, $button, $actionurl, $confirm_text, $redirect_to );
3333      }
3334  
3335  
3336      /**
3337       * Provide link to copy a post if user has edit rights
3338       *
3339       * @param array Params:
3340       *  - 'before': to display before link
3341       *  - 'after':    to display after link
3342       *  - 'text': link text
3343       *  - 'title': link title
3344       *  - 'class': CSS class name
3345       *  - 'save_context': redirect to current URL?
3346       */
3347  	function get_copy_link( $params = array() )
3348      {
3349          global $current_User, $admin_url;
3350  
3351          $actionurl = $this->get_copy_url($params);
3352          if( ! $actionurl )
3353          {
3354              return false;
3355          }
3356  
3357          // Make sure we are not missing any param:
3358          $params = array_merge( array(
3359                  'before'       => ' ',
3360                  'after'        => ' ',
3361                  'text'         => '#', // '#' - icon + text, '#icon#' - only icon, '#text#' - only text
3362                  'title'        => '#',
3363                  'class'        => '',
3364                  'save_context' => true,
3365              ), $params );
3366  
3367          switch( $params['text'] )
3368          {
3369              case '#':
3370                  $params['text'] = get_icon( 'copy', 'imgtag', array( 'title' => T_('Duplicate this post...') ) ).' '.T_('Duplicate...');
3371                  break;
3372  
3373              case '#icon#':
3374                  $params['text'] = get_icon( 'copy', 'imgtag', array( 'title' => T_('Duplicate this post...') ) );
3375                  break;
3376  
3377              case '#text#':
3378                  $params['text'] = T_('Duplicate...');
3379                  break;
3380          }
3381  
3382          if( $params['title'] == '#' ) $params['title'] = T_('Duplicate this post...');
3383  
3384          $r = $params['before'];
3385          $r .= '<a href="'.$actionurl;
3386          $r .= '" title="'.$params['title'].'"';
3387          if( !empty( $params['class'] ) ) $r .= ' class="'.$params['class'].'"';
3388          $r .=  '>'.$params['text'].'</a>';
3389          $r .= $params['after'];
3390  
3391          return $r;
3392      }
3393  
3394  
3395      /**
3396       * Get URL to copy a post if user has edit rights.
3397       *
3398       * @param array Params:
3399       *  - 'save_context': redirect to current URL?
3400       */
3401  	function get_copy_url( $params = array() )
3402      {
3403          global $admin_url, $current_User;
3404  
3405          if( ! is_logged_in( false ) ) return false;
3406  
3407          if( ! $this->ID )
3408          { // preview..
3409              return false;
3410          }
3411  
3412          $this->load_Blog();
3413          $write_item_url = $this->Blog->get_write_item_url();
3414          if( empty( $write_item_url ) )
3415          { // User has no right to copy this post
3416              return false;
3417          }
3418  
3419          // default params
3420          $params += array('save_context' => true);
3421  
3422          $url = false;
3423          if( $this->Blog->get_setting( 'in_skin_editing' ) && !is_admin_page() )
3424          {    // We have a mode 'In-skin editing' for the current Blog
3425              if( check_item_perm_edit( 0, false ) )
3426              {    // Current user can copy this post from Front-office
3427                  $url = url_add_param( $this->Blog->get( 'url' ), 'disp=edit&cp='.$this->ID );
3428              }
3429              else if( $current_User->check_perm( 'admin', 'restricted' ) )
3430              {    // Current user can copy this post from Back-office
3431                  $url = $admin_url.'?ctrl=items&amp;action=copy&amp;blog='.$this->Blog->ID.'&amp;p='.$this->ID;
3432              }
3433          }
3434          else if( $current_User->check_perm( 'admin', 'restricted' ) )
3435          {    // Copy a post from Back-office
3436              $url = $admin_url.'?ctrl=items&amp;action=copy&amp;blog='.$this->Blog->ID.'&amp;p='.$this->ID;
3437              if( $params['save_context'] )
3438              {
3439                  $url .= '&amp;redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ).'#'.$this->get_anchor_id() );
3440              }
3441          }
3442          return $url;
3443      }
3444  
3445  
3446      /**
3447       * Template tag
3448       * @see Item::get_copy_link()
3449       */
3450  	function copy_link( $params = array() )
3451      {
3452          echo $this->get_copy_link( $params );
3453      }
3454  
3455  
3456      /**
3457       * Provide link to edit a post if user has edit rights
3458       *
3459       * @param array Params:
3460       *  - 'before': to display before link
3461       *  - 'after':    to display after link
3462       *  - 'text': link text
3463       *  - 'title': link title
3464       *  - 'class': CSS class name
3465       *  - 'save_context': redirect to current URL?
3466       */
3467  	function get_edit_link( $params = array() )
3468      {
3469          global $current_User, $admin_url;
3470  
3471          $actionurl = $this->get_edit_url($params);
3472          if( ! $actionurl )
3473          {
3474              return false;
3475          }
3476  
3477          // Make sure we are not missing any param:
3478          $params = array_merge( array(
3479                  'before'       => ' ',
3480                  'after'        => ' ',
3481                  'text'         => '#',
3482                  'title'        => '#',
3483                  'class'        => '',
3484                  'save_context' => true,
3485              ), $params );
3486  
3487  
3488          if( $params['text'] == '#' ) $params['text'] = get_icon( 'edit' ).' '.T_('Edit...');
3489          if( $params['title'] == '#' ) $params['title'] = T_('Edit this post...');
3490  
3491          $r = $params['before'];
3492          $r .= '<a href="'.$actionurl;
3493          $r .= '" title="'.$params['title'].'"';
3494          if( !empty( $params['class'] ) ) $r .= ' class="'.$params['class'].'"';
3495          $r .=  '>'.$params['text'].'</a>';
3496          $r .= $params['after'];
3497  
3498          return $r;
3499      }
3500  
3501  
3502      /**
3503       * Get URL to edit a post if user has edit rights.
3504       *
3505       * @param array Params:
3506       *  - 'save_context': redirect to current URL?
3507       */
3508  	function get_edit_url($params = array())
3509      {
3510          global $admin_url, $current_User;
3511  
3512          if( ! is_logged_in( false ) ) return false;
3513  
3514          if( ! $this->ID )
3515          { // preview..
3516              return false;
3517          }
3518  
3519          if( ! $current_User->check_perm( 'item_post!CURSTATUS', 'edit', false, $this ) )
3520          { // User has no right to edit this post
3521              return false;
3522          }
3523  
3524          // default params
3525          $params += array('save_context' => true);
3526  
3527          $this->load_Blog();
3528          $url = false;
3529          if( $this->Blog->get_setting( 'in_skin_editing' ) && ! is_admin_page() )
3530          {    // We have a mode 'In-skin editing' for the current Blog
3531              if( check_item_perm_edit( $this->ID, false ) )
3532              {    // Current user can edit this post
3533                  $url = url_add_param( $this->Blog->get( 'url' ), 'disp=edit&p='.$this->ID );
3534              }
3535          }
3536          else if( $current_User->check_perm( 'admin', 'restricted' ) )
3537          {    // Edit a post from Back-office
3538              $url = $admin_url.'?ctrl=items&amp;action=edit&amp;p='.$this->ID;
3539              if( $params['save_context'] )
3540              {
3541                  $url .= '&amp;redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ).'#'.$this->get_anchor_id() );
3542              }
3543          }
3544          return $url;
3545      }
3546  
3547  
3548      /**
3549       * Template tag
3550       * @see Item::get_edit_link()
3551       */
3552  	function edit_link( $params = array() )
3553      {
3554          echo $this->get_edit_link( $params );
3555      }
3556  
3557  
3558      /**
3559       * Get next status to publish/restrict to this item
3560       * TODO: asimo>Refactor this with Comment->get_next_status()
3561       *
3562       * @param boolean true to get next publish status, and false to get next restrict status
3563       * @return mixed false if user has no permission | array( status, status_text, icon_color ) otherwise
3564       */
3565  	function get_next_status( $publish )
3566      {
3567          global $current_User;
3568  
3569          if( !is_logged_in( false ) )
3570          {
3571              return false;
3572          }
3573  
3574          $status_order = get_visibility_statuses( 'ordered-array' );
3575          $status_index = get_visibility_statuses( 'ordered-index' );
3576  
3577          $curr_index = $status_index[$this->status];
3578          if( ( !$publish ) && ( $curr_index == 0 ) && ( $this->status != 'deprecated' ) )
3579          {
3580              $curr_index = $curr_index + 1;
3581          }
3582          $has_perm = false;
3583          while( !$has_perm && ( $publish ? ( $curr_index < 4 ) : ( $curr_index > 0 ) ) )
3584          {
3585              $curr_index = $publish ? ( $curr_index + 1 ) : ( $curr_index - 1 );
3586              $has_perm = $current_User->check_perm( 'item_post!'.$status_order[$curr_index][0], 'moderate', false, $this );
3587          }
3588          if( $has_perm )
3589          {
3590              $label_index = $publish ? 1 : 2;
3591              return array( $status_order[$curr_index][0], $status_order[$curr_index][$label_index], $status_order[$curr_index][3] );
3592          }
3593          return false;
3594      }
3595  
3596  
3597      /**
3598       * Provide link to publish a post if user has edit rights
3599       *
3600       * Note: publishing date will be updated
3601       *
3602       * @param string to display before link
3603       * @param string to display after link
3604       * @param string link text
3605       * @param string link title
3606       * @param string class name
3607       * @param string glue between url params
3608       */
3609  	function get_publish_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;', $save_context = true )
3610      {
3611          global $current_User, $admin_url;
3612  
3613          if( $this->status != 'draft' )
3614          {
3615              return false;
3616          }
3617  
3618          if( ! is_logged_in( false ) ) return false;
3619  
3620          $this->load_Blog();
3621          if( ! ($current_User->check_perm( 'item_post!published', 'edit', false, $this ))
3622              || ! ($current_User->check_perm( 'blog_edit_ts', 'edit', false, $this->Blog->ID ) ) )
3623          { // User has no right to publish this post now:
3624              return false;
3625          }
3626  
3627          if( $text == '#' ) $text = get_icon( 'publish', 'imgtag' ).' '.T_('Publish NOW!');
3628          if( $title == '#' ) $title = T_('Publish now using current date and time.');
3629  
3630          $r = $before;
3631          $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action=publish_now'.$glue.'post_ID='.$this->ID.$glue.url_crumb('item');
3632          if( $save_context )
3633          {
3634              $r .= $glue.'redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ) );
3635          }
3636          $r .= '" title="'.$title.'"';
3637          if( !empty( $class ) ) $r .= ' class="'.$class.'"';
3638          $r .= '>'.$text.'</a>';
3639          $r .= $after;
3640  
3641          return $r;
3642      }
3643  
3644  
3645      /**
3646       * Provide link to publish a post to the highest available public status for the current User
3647       *
3648       * @param $params
3649       * @return boolean true if link was displayed false otherwise
3650       */
3651  	function highest_publish_link( $params = array() )
3652      {
3653          global $current_User, $admin_url;
3654  
3655          if( !is_logged_in( false ) )
3656          {
3657              return false;
3658          }
3659  
3660          $params = array_merge( array(
3661                  'before'       => '',
3662                  'after'        => '',
3663                  'text'         => '#',
3664                  'before_text'  => '',
3665                  'after_text'   => '',
3666                  'title'        => '',
3667                  'class'        => '',
3668                  'glue'         => '&amp;',
3669                  'save_context' => true,
3670                  'redirect_to'  => '',
3671              ), $params );
3672  
3673          $curr_status_permvalue = get_status_permvalue( $this->status );
3674          // get the current User highest publish status for this item Blog
3675          list( $highest_status, $publish_text ) = get_highest_publish_status( 'post', $this->get_blog_ID() );
3676          // Get binary value of the highest available status
3677          $highest_status_permvalue = get_status_permvalue( $highest_status );
3678          if( $curr_status_permvalue >= $highest_status_permvalue || ( $highest_status_permvalue <= get_status_permvalue( 'private' ) ) )
3679          { // Current User has no permission to change this comment status to a more public status
3680              return false;
3681          }
3682  
3683          if( ! ($current_User->check_perm( 'item_post!'.$highest_status, 'edit', false, $this ) ) )
3684          { // User has no right to edit this post
3685              return false;
3686          }
3687  
3688          $glue = $params[ 'glue' ];
3689          $text = ( $params[ 'text' ] == '#' ) ? $publish_text : $params[ 'text' ];
3690  
3691          $r = $params[ 'before' ];
3692          $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action=publish'.$glue.'post_status='.$highest_status.$glue.'post_ID='.$this->ID.$glue.url_crumb('item');
3693          if( $params[ 'redirect_to' ] )
3694          {
3695              $r .= $glue.'redirect_to='.rawurlencode( $params[ 'redirect_to' ] );
3696          }
3697          elseif( $params[ 'save_context' ] )
3698          {
3699              $r .= $glue.'redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ) );
3700          }
3701          $r .= '" title="'.$params[ 'title' ].'"';
3702          if( !empty( $params[ 'class' ] ) ) $r .= ' class="'.$params[ 'class' ].'"';
3703          $r .= '>'.$params[ 'before_text' ].$text.$params[ 'after_text' ].'</a>';
3704          $r .= $params[ 'after' ];
3705  
3706          echo $r;
3707          return true;
3708      }
3709  
3710  
3711  	function publish_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;', $save_context = true )
3712      {
3713          $publish_link = $this->get_publish_link( $before, $after, $text, $title, $class, $glue, $save_context );
3714  
3715          if( $publish_link === false )
3716          {    // The publish link is unavailable for current user and for this item
3717              return false;
3718          }
3719  
3720          // Display the publish link
3721          echo $publish_link;
3722  
3723          return true;
3724      }
3725  
3726  
3727      /**
3728       * Display next Publish/Restrict to link
3729       *
3730       * @param array link params
3731       * @param boolean true to display next publish status, and false to display next restrict status link
3732       * @return boolean true if link was displayed | false otherwise
3733       */
3734  	function next_status_link( $params, $publish )
3735      {
3736          global $admin_url;
3737  
3738          $params = array_merge( array(
3739                  'before'      => '',
3740                  'after'       => '',
3741                  'before_text' => '',
3742                  'after_text'  => '',
3743                  'text'        => '#',
3744                  'title'       => '',
3745                  'class'       => '',
3746                  'glue'        => '&amp;',
3747                  'redirect_to' => '',
3748                  'post_navigation' => 'same_blog',
3749                  'nav_target'  => NULL,
3750              ), $params );
3751  
3752          if( $publish )
3753          {
3754              $next_status_in_row = $this->get_next_status( true );
3755              $action = 'publish';
3756              $button_default_icon = 'move_up_'.$next_status_in_row[2];
3757          }
3758          else
3759          {
3760              $next_status_in_row =  $this->get_next_status( false );
3761              $action = 'restrict';
3762              $button_default_icon = 'move_down_'.$next_status_in_row[2];
3763          }
3764  
3765          if( $next_status_in_row === false )
3766          { // Next status is not allowed for current user
3767              return false;
3768          }
3769  
3770          $next_status = $next_status_in_row[0];
3771          $next_status_label = $next_status_in_row[1];
3772  
3773          if( isset( $params['text_'.$next_status] ) )
3774          { // Set text from params for next status
3775              $text = $params['text_'.$next_status];
3776          }
3777          elseif( $params['text' ] != '#' )
3778          { // Set text from params for any atatus
3779              $text = $params['text'];
3780          }
3781          else
3782          { // Default text
3783              $text = get_icon( $button_default_icon, 'imgtag', array( 'title' => '' ) ).' '.$next_status_label;
3784          }
3785  
3786          if( empty( $params['title'] ) )
3787          {
3788              $status_title = get_visibility_statuses( 'moderation-titles' );
3789              $params['title'] = $status_title[$next_status];
3790          }
3791          $glue = $params['glue'];
3792  
3793          $r = $params['before'];
3794          $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action='.$action.$glue.'post_status='.$next_status.$glue.'post_ID='.$this->ID.$glue.url_crumb('item');
3795  
3796          // set redirect_to
3797          $redirect_to = $params['redirect_to'];
3798          if( empty( $redirect_to ) && ( !is_admin_page() ) )
3799          { // we are in front office
3800              if( $next_status == 'deprecated' )
3801              {
3802                  if( $params['post_navigation'] == 'same_category' )
3803                  {
3804                      $redirect_to = get_caturl( $params['nav_target'] );
3805                  }
3806                  else
3807                  {
3808                      $this->get_Blog();
3809                      $redirect_to = $this->Blog->gen_blogurl();
3810                  }
3811              }
3812              else
3813              {
3814                  $redirect_to = $this->add_navigation_param( $this->get_permanent_url(), $params['post_navigation'], $params['nav_target'] );
3815              }
3816          }
3817          if( !empty( $redirect_to ) )
3818          {
3819              $r .= $glue.'redirect_to='.rawurlencode( $redirect_to );
3820          }
3821  
3822          $r .= '" title="'.$params['title'].'"';
3823          if( empty( $params['class_'.$next_status] ) )
3824          { // Set class for all statuses
3825              $class = empty( $params['class'] ) ? '' : $params['class'];
3826          }
3827          else
3828          { // Set special class for next status
3829              $class = $params['class_'.$next_status];
3830          }
3831          if( !empty( $class ) ) $r .= ' class="'.$class.'"';
3832          $r .= '>'.$params['before_text'].$text.$params['after_text'].'</a>';
3833          $r .= $params['after'];
3834  
3835          echo $r;
3836          return true;
3837      }
3838  
3839  
3840      /**
3841       * Provide link to deprecate a post if user has edit rights
3842       *
3843       * @param string to display before link
3844       * @param string to display after link
3845       * @param string link text
3846       * @param string link title
3847       * @param string class name
3848       * @param string glue between url params
3849       */
3850  	function get_deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;', $redirect_to = '' )
3851      {
3852          global $current_User, $admin_url;
3853  
3854          if( ! is_logged_in( false ) ) return false;
3855  
3856          if( ($this->status == 'deprecated') // Already deprecated!
3857              || ! ($current_User->check_perm( 'item_post!deprecated', 'edit', false, $this )) )
3858          { // User has no right to deprecated this post:
3859              return false;
3860          }
3861  
3862          if( $text == '#' ) $text = get_icon( 'deprecate', 'imgtag' ).' '.T_('Deprecate!');
3863          if( $title == '#' ) $title = T_('Deprecate this post!');
3864  
3865          if( !empty( $redirect_to ) )
3866          {
3867              $redirect_to = $glue.'redirect_to='.rawurlencode( $redirect_to );
3868          }
3869  
3870          $r = $before;
3871          $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action=deprecate'.$glue.'post_ID='.$this->ID.$glue.url_crumb('item').$redirect_to;
3872          $r .= '" title="'.$title.'"';
3873          if( !empty( $class ) ) $r .= ' class="'.$class.'"';
3874          $r .= '>'.$text.'</a>';
3875          $r .= $after;
3876  
3877          return $r;
3878      }
3879  
3880  
3881      /**
3882       * Display link to deprecate a post if user has edit rights
3883       *
3884       * @param string to display before link
3885       * @param string to display after link
3886       * @param string link text
3887       * @param string link title
3888       * @param string class name
3889       * @param string glue between url params
3890       */
3891  	function deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;', $redirect_to = '' )
3892      {
3893          $deprecate_link = $this->get_deprecate_link( $before, $after, $text, $title, $class, $glue, $redirect_to );
3894  
3895          if( $deprecate_link === false )
3896          {    // The deprecate link is unavailable for current user and for this item
3897              return false;
3898          }
3899  
3900          // Display the deprecate link
3901          echo $deprecate_link;
3902  
3903          return true;
3904      }
3905  
3906  
3907      /**
3908       * Template function: display priority of item
3909       *
3910       * @param string
3911       * @param string
3912       */
3913  	function priority( $before = '', $after = '' )
3914      {
3915          if( isset($this->priority) )
3916          {
3917              echo $before;
3918              echo $this->priority;
3919              echo $after;
3920          }
3921      }
3922  
3923  
3924      /**
3925       * Template function: display list of priority options
3926       */
3927  	function priority_options( $field_value, $allow_none )
3928      {
3929          $priority = isset($field_value) ? $field_value : $this->priority;
3930  
3931          $r = '';
3932          if( $allow_none )
3933          {
3934              $r = '<option value="">'./* TRANS: "None" select option */T_('No priority').'</option>';
3935          }
3936  
3937          foreach( $this->priorities as $i => $name )
3938          {
3939              $r .= '<option value="'.$i.'"';
3940              if( $priority == $i )
3941              {
3942                  $r .= ' selected="selected"';
3943              }
3944              $r .= '>'.$name.'</option>';
3945          }
3946  
3947          return $r;
3948      }
3949  
3950  
3951      /**
3952       * Get checkable list of renderers
3953       *
3954       * @param array|NULL If given, assume these renderers to be checked.
3955       * @return string Renderer checkboxes
3956       */
3957  	function get_renderer_checkboxes( $item_renderers = NULL )
3958      {
3959          global $Plugins;
3960  
3961          if( is_null( $item_renderers ) )
3962          {
3963              $item_renderers = $this->get_renderers();
3964          }
3965  
3966          return $Plugins->get_renderer_checkboxes( $item_renderers, array( 'Item' => & $this ) );
3967      }
3968  
3969  
3970      /**
3971       * Template function: display checkable list of renderers
3972       *
3973       * @param array|NULL If given, assume these renderers to be checked.
3974       */
3975  	function renderer_checkboxes( $item_renderers = NULL )
3976      {
3977          echo $this->get_renderer_checkboxes( $item_renderers );
3978      }
3979  
3980  
3981      /**
3982       * Get status of item
3983       *
3984       * Statuses:
3985       * - published
3986       * - deprecated
3987       * - protected
3988       * - private
3989       * - draft
3990       *
3991       * @param array Params
3992       */
3993  	function get_status( $params = array() )
3994      {
3995          // Make sure we are not missing any param:
3996          $params = array_merge( array(
3997                  'before' => '',
3998                  'after'  => '',
3999                  'format' => 'htmlbody', // Output format, see {@link format_to_output()}
4000              ), $params );
4001  
4002          $r = $params['before'];
4003  
4004          switch( $params['format'] )
4005          {
4006              case 'raw':
4007                  $r .= $this->get_status_raw();
4008                  break;
4009  
4010              case 'styled':
4011                  $r .= get_styled_status( $this->status, $this->get('t_status') );
4012                  break;
4013  
4014              default:
4015                  $r .= format_to_output( $this->get('t_status'), $params['format'] );
4016                  break;
4017          }
4018  
4019          $r .= $params['after'];
4020  
4021          return $r;
4022      }
4023  
4024  
4025      /**
4026       * Template function: display status of item
4027       *
4028       * Statuses:
4029       * - published
4030       * - deprecated
4031       * - protected
4032       * - private
4033       * - draft
4034       *
4035       * @param array Params
4036       */
4037  	function status( $params = array() )
4038      {
4039          // Make sure we are not missing any param:
4040          $params = array_merge( array(
4041                  'before'      => '',
4042                  'after'       => '',
4043                  'format'      => 'htmlbody', // Output format, see {@link format_to_output()}
4044              ), $params );
4045  
4046          echo $this->get_status( $params );
4047      }
4048  
4049  
4050      /**
4051       * Output classes for the Item <div>
4052       */
4053  	function div_classes( $params = array(), $output = true )
4054      {
4055          global $disp;
4056  
4057          // Make sure we are not missing any param:
4058          $params = array_merge( array(
4059                  'item_class'        => 'bPost',
4060                  'item_type_class'   => 'bPost_ptyp',
4061                  'item_status_class' => 'bPost',
4062                  'item_disp_class'   => 'bPost_disp_',
4063              ), $params );
4064  
4065          $classes = array( $params['item_class'],
4066                            $params['item_type_class'].$this->ptyp_ID,
4067                            $params['item_status_class'].$this->status,
4068                            $params['item_disp_class'].$disp,
4069                          );
4070  
4071          $r = implode( ' ', $classes );
4072  
4073          if( ! $output ) return $r;
4074  
4075          echo $r;
4076      }
4077  
4078  
4079      /**
4080       * Get raw status
4081       *
4082       * @return string Status
4083       */
4084  	function get_status_raw()
4085      {
4086          return $this->status;
4087      }
4088  
4089  
4090      /**
4091       * Output raw status.
4092       */
4093  	function status_raw()
4094      {
4095          echo $this->get_status_raw();
4096      }
4097  
4098  
4099      /**
4100       * Template function: display extra status of item
4101       *
4102       * @param string
4103       * @param string
4104       * @param string Output format, see {@link format_to_output()}
4105       */
4106  	function extra_status( $before = '', $after = '', $format = 'htmlbody' )
4107      {
4108          if( $format == 'raw' )
4109          {
4110              $this->disp( $this->get('t_extra_status'), 'raw' );
4111          }
4112          elseif( $extra_status = $this->get('t_extra_status') )
4113          {
4114              echo $before.format_to_output( $extra_status, $format ).$after;
4115          }
4116      }
4117  
4118  
4119       /**
4120       * Display tags for Item
4121       *
4122       * @param array of params
4123       * @param string Output format, see {@link format_to_output()}
4124       */
4125  	function tags( $params = array() )
4126      {
4127          $params = array_merge( array(
4128                  'before' =>           '<div>'.T_('Tags').': ',
4129                  'after' =>            '</div>',
4130                  'separator' =>        ', ',
4131                  'links' =>            true,
4132              ), $params );
4133  
4134          $tags = $this->get_tags();
4135  
4136          if( !empty( $tags ) )
4137          {
4138              echo $params['before'];
4139  
4140              if( $links = $params['links'] )
4141              {
4142                  $this->get_Blog();
4143              }
4144  
4145              $i = 0;
4146              foreach( $tags as $tag )
4147              {
4148                  if( $i++ > 0 )
4149                  {
4150                      echo $params['separator'];
4151                  }
4152  
4153                  if( $links )
4154                  {    // We want links
4155                      echo $this->Blog->get_tag_link( $tag );
4156                  }
4157                  else
4158                  {
4159                      echo htmlspecialchars($tag);
4160                  }
4161              }
4162  
4163              echo $params['after'];
4164          }
4165      }
4166  
4167  
4168      /**
4169       * Template function: Displays trackback autodiscovery information
4170       *
4171       * TODO: build into headers
4172       */
4173  	function trackback_rdf()
4174      {
4175          $this->get_Blog();
4176          if( ! $this->can_receive_pings() )
4177          { // Trackbacks not allowed on this blog:
4178              return;
4179          }
4180  
4181          echo '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" '."\n";
4182          echo '  xmlns:dc="http://purl.org/dc/elements/1.1/"'."\n";
4183          echo '  xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">'."\n";
4184          echo '<rdf:Description'."\n";
4185          echo '  rdf:about="';
4186          $this->permanent_url( 'single' );
4187          echo '"'."\n";
4188          echo '  dc:identifier="';
4189          $this->permanent_url( 'single' );
4190          echo '"'."\n";
4191          $this->title( array(
4192              'before'    => ' dc:title="',
4193              'after'     => '"'."\n",
4194              'link_type' => 'none',
4195              'format'    => 'xmlattr',
4196              ) );
4197          echo '  trackback:ping="';
4198          $this->trackback_url();
4199          echo '" />'."\n";
4200          echo '</rdf:RDF>';
4201      }
4202  
4203  
4204      /**
4205       * Template function: displays url to use to trackback this item
4206       */
4207  	function trackback_url()
4208      {
4209          echo $this->get_trackback_url();
4210      }
4211  
4212  
4213      /**
4214       * Template function: get url to use to trackback this item
4215       * @return string
4216       */
4217  	function get_trackback_url()
4218      {
4219          global $htsrv_url, $Settings;
4220  
4221          // fp> TODO: get a clean (per blog) setting for this
4222          //    return $htsrv_url.'trackback.php/'.$this->ID;
4223  
4224          return $htsrv_url.'trackback.php?tb_id='.$this->ID;
4225      }
4226  
4227  
4228      /**
4229       * Get HTML code to display a flash audio player for playback of a
4230       * given URL.
4231       *
4232       * @param string The URL of a MP3 audio file.
4233       * @return string The HTML code.
4234       */
4235  	function get_player( $url )
4236      {
4237          global $rsc_url;
4238  
4239          return '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="200" height="20" id="dewplayer" align="middle"><param name="wmode" value="transparent"><param name="allowScriptAccess" value="sameDomain" /><param name="movie" value="'.$rsc_url.'swf/dewplayer.swf?mp3='.$url.'&amp;showtime=1" /><param name="quality" value="high" /><param name="bgcolor" value="" /><embed src="'.$rsc_url.'swf/dewplayer.swf?mp3='.$url.'&amp;showtime=1" quality="high" bgcolor="" width="200" height="20" name="dewplayer" wmode="transparent" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed></object>';
4240      }
4241  
4242  
4243      /**
4244       * Template function: Display link to item related url.
4245       *
4246       * By default the link is displayed as a link.
4247       * Optionally some smart stuff may happen.
4248       */
4249  	function url_link( $params = array() )
4250      {
4251  
4252          if( empty( $this->url ) )
4253          {
4254              return;
4255          }
4256  
4257          // Make sure we are not missing any param:
4258          $params = array_merge( array(
4259                  'before'        => ' ',
4260                  'after'         => ' ',
4261                  'text_template' => '$url$',        // If evaluates to empty, nothing will be displayed (except player if podcast)
4262                  'url_template'  => '$url$',
4263                  'target'        => '',
4264                  'format'        => 'htmlbody',
4265                  'podcast'       => '#',                        // handle as podcast. # means depending on post type
4266                  'before_podplayer' => '<div class="podplayer">',
4267                  'after_podplayer'  => '</div>',
4268              ), $params );
4269  
4270          if( $params['podcast'] == '#' )
4271          {    // Check if this post is a podcast
4272              $params['podcast'] = ( $this->ptyp_ID == 2000 );
4273          }
4274  
4275          if( $params['podcast'] && $params['format'] == 'htmlbody' )
4276          {    // We want podcast display:
4277  
4278              echo $params['before_podplayer'];
4279  
4280              echo $this->get_player( $this->url );
4281  
4282              echo $params['after_podplayer'];
4283  
4284          }
4285          else
4286          { // Not displaying podcast player:
4287  
4288              $text = str_replace( '$url$', $this->url, $params['text_template'] );
4289              if( empty($text) )
4290              {    // Nothing to display
4291                  return;
4292              }
4293  
4294              $r = $params['before'];
4295  
4296              $r .= '<a href="'.str_replace( '$url$', $this->url, $params['url_template'] ).'"';
4297  
4298              if( !empty( $params['target'] ) )
4299              {
4300                  $r .= ' target="'.$params['target'].'"';
4301              }
4302  
4303              $r .= '>'.$text.'</a>';
4304  
4305              $r .= $params['after'];
4306  
4307              echo format_to_output( $r, $params['format'] );
4308          }
4309      }
4310  
4311  
4312      /**
4313       * Template function: Display the number of words in the post
4314       */
4315  	function wordcount()
4316      {
4317          echo (int)$this->wordcount; // may have been saved as NULL until 1.9
4318      }
4319  
4320  
4321      /**
4322       * Template function: Display the number of times the Item has been viewed
4323       *
4324       * Note: viewcount is incremented whenever the Item's content is displayed with "MORE"
4325       * (i-e full content), see {@link Item::content()}.
4326       *
4327       * Viewcount is NOT incremented on page reloads and other special cases, see {@link Hit::is_new_view()}
4328       *
4329       * %d gets replaced in all params by the number of views.
4330       *
4331       * @param string Link text to display when there are 0 views
4332       * @param string Link text to display when there is 1 views
4333       * @param string Link text to display when there are >1 views
4334       * @return string The phrase about the number of views.
4335       */
4336  	function get_views( $zero = '#', $one = '#', $more = '#' )
4337      {
4338          if( !$this->views )
4339          {
4340              $r = ( $zero == '#' ? T_( 'No views' ) : $zero );
4341          }
4342          elseif( $this->views == 1 )
4343          {
4344              $r = ( $one == '#' ? T_( '1 view' ) : $one );
4345          }
4346          else
4347          {
4348              $r = ( $more == '#' ? T_( '%d views' ) : $more );
4349          }
4350  
4351          return str_replace( '%d', $this->views, $r );
4352      }
4353  
4354  
4355      /**
4356       * Template function: Display a phrase about the number of Item views.
4357       *
4358       * @param string Link text to display when there are 0 views
4359       * @param string Link text to display when there is 1 views
4360       * @param string Link text to display when there are >1 views (include %d for # of views)
4361       * @return integer Number of views.
4362       */
4363  	function views( $zero = '#', $one = '#', $more = '#' )
4364      {
4365          echo $this->get_views( $zero, $one, $more );
4366  
4367          return $this->views;
4368      }
4369  
4370  
4371      /**
4372       * Set param value
4373       *
4374       * By default, all values will be considered strings
4375       *
4376       * @todo extra_cat_IDs recording
4377       *
4378       * @param string parameter name
4379       * @param mixed parameter value
4380       * @param boolean true to set to NULL if empty value
4381       * @return boolean true, if a value has been set; false if it has not changed
4382       */
4383  	function set( $parname, $parvalue, $make_null = false )
4384      {
4385          switch( $parname )
4386          {
4387              case 'pst_ID':
4388                  return $this->set_param( $parname, 'number', $parvalue, true );
4389  
4390              case 'content':
4391                  $r1 = $this->set_param( 'content', 'string', $parvalue, $make_null );
4392                  // Update wordcount as well:
4393                  $r2 = $this->set_param( 'wordcount', 'number', bpost_count_words($this->content), false );
4394                  return ( $r1 || $r2 ); // return true if one changed
4395  
4396              case 'wordcount':
4397              case 'featured':
4398                  return $this->set_param( $parname, 'number', $parvalue, false );
4399  
4400              case 'datedeadline':
4401                  return $this->set_param( 'datedeadline', 'date', $parvalue, true );
4402  
4403              case 'order':
4404                  return $this->set_param( 'order', 'number', $parvalue, true );
4405  
4406              case 'renderers': // deprecated
4407                  return $this->set_renderers( $parvalue );
4408  
4409              case 'excerpt':
4410                  if( $this->excerpt_autogenerated )
4411                  {    // Check if the excerpt needs to keep getting autogenerated...
4412                      $autovalue = $this->get_autogenerated_excerpt();
4413                      $post_excerpt_previous_md5 = param('post_excerpt_previous_md5', 'string');
4414                      // TODO: this is itemform specific and should not be like that.
4415                      if( $post_excerpt_previous_md5 == md5($parvalue) || empty($post_excerpt_previous_md5) /* empty in simple form */ )
4416                      { // old value has not changed, it keeps getting autogenerated:
4417                          $parvalue = $autovalue;
4418                      }
4419                  }
4420  
4421                  if( parent::set( 'excerpt', $parvalue, $make_null ) )
4422                  { // mark excerpt as not being autogenerated anymore, if user has changed it from the autogenerated value.
4423                      if( isset($autovalue) && $parvalue != $autovalue )
4424                      {
4425                          $this->set('excerpt_autogenerated', 0);
4426                      }
4427                  }
4428                  break;
4429  
4430              default:
4431                  return parent::set( $parname, $parvalue, $make_null );
4432          }
4433      }
4434  
4435  
4436      /**
4437       * Set the renderers of the Item.
4438       *
4439       * @param array List of renderer codes.
4440       * @return boolean true, if it has been set; false if it has not changed
4441       */
4442  	function set_renderers( $renderers )
4443      {
4444          return $this->set_param( 'renderers', 'string', implode( '.', $renderers ) );
4445      }
4446  
4447  
4448      /**
4449       * Set the Author of the Item.
4450       *
4451       * @param User (Do NOT set to NULL or you may kill the current_User)
4452       * @return boolean true, if it has been set; false if it has not changed
4453       */
4454  	function set_creator_User( & $creator_User )
4455      {
4456          $this->creator_User = & $creator_User;
4457          $this->Author = & $this->creator_User; // deprecated  fp> TODO: Test and see if this line can be put once and for all in the constructor
4458          return $this->set( $this->creator_field, $creator_User->ID );
4459      }
4460  
4461  
4462      /**
4463       * Set the Item location from the current user. Use to create a new post.
4464       *
4465       * @param string Location (country | region | subregion | city)
4466       */
4467  	function set_creator_location( $location )
4468      {
4469          global $current_User;
4470  
4471          if( !isset( $current_User ) )
4472          {    // No logged in user
4473              return;
4474          }
4475  
4476          $locations = array(
4477                  'country'   => 'ctry_ID',
4478                  'region'    => 'rgn_ID',
4479                  'subregion' => 'subrg_ID',
4480                  'city'      => 'city_ID',
4481              );
4482  
4483          $field_ID = $locations[$location];
4484  
4485          $this->load_Blog();
4486          if( $this->Blog->{$location.'_visible'}() )
4487          {    // Location is visible
4488              if( empty( $this->$field_ID ) )
4489              {    // Set default location
4490                  $this->set( $field_ID, $current_User->$field_ID );
4491              }
4492          }
4493      }
4494  
4495  
4496      /**
4497       * Create a new Item/Post and insert it into the DB
4498       *
4499       * This function has to handle all needed DB dependencies!
4500       *
4501       * @deprecated Use set() + dbinsert() instead
4502       */
4503  	function insert(
4504          $author_user_ID,              // Author
4505          $post_title,
4506          $post_content,
4507          $post_timestamp,              // 'Y-m-d H:i:s'
4508          $main_cat_ID = 1,             // Main cat ID
4509          $extra_cat_IDs = array(),     // Table of extra cats
4510          $post_status = 'published',
4511          $post_locale = '#',
4512          $post_urltitle = '',
4513          $post_url = '',
4514          $post_comment_status = 'open',
4515          $post_renderers = array('default'),
4516          $item_typ_ID = 1,
4517          $item_st_ID = NULL,
4518          $post_order = NULL )
4519      {
4520          global $DB, $query, $UserCache;
4521          global $default_locale;
4522  
4523          if( $post_locale == '#' ) $post_locale = $default_locale;
4524  
4525          // echo 'INSERTING NEW POST ';
4526  
4527          if( isset( $UserCache ) )    // DIRTY HACK
4528          { // If not in install procedure...
4529              $this->set_creator_User( $UserCache->get_by_ID( $author_user_ID ) );
4530          }
4531          else
4532          {
4533              $this->set( $this->creator_field, $author_user_ID );
4534          }
4535          $this->set( $this->lasteditor_field, $this->{$this->creator_field} );
4536          $this->set( 'title', $post_title );
4537          $this->set( 'urltitle', $post_urltitle );
4538          $this->set( 'content', $post_content );
4539          $this->set( 'datestart', $post_timestamp );
4540  
4541          $this->set( 'main_cat_ID', $main_cat_ID );
4542          $this->set( 'extra_cat_IDs', $extra_cat_IDs );
4543          $this->set( 'status', $post_status );
4544          $this->set( 'locale', $post_locale );
4545          $this->set( 'url', $post_url );
4546          $this->set( 'comment_status', $post_comment_status );
4547          $this->set_renderers( $post_renderers );
4548          $this->set( 'ptyp_ID', $item_typ_ID );
4549          $this->set( 'pst_ID', $item_st_ID );
4550          $this->set( 'order', $post_order );
4551  
4552          // INSERT INTO DB:
4553          $this->dbinsert();
4554  
4555          return $this->ID;
4556      }
4557  
4558  
4559      /**
4560       * Insert object into DB based on previously recorded changes
4561       *
4562       * @param string Source of item creation ( 'through_admin', 'through_xmlrpc', 'through_email' )
4563       * @return boolean true on success
4564       */
4565  	function dbinsert( $created_through = 'through_admin' )
4566      {
4567          global $DB, $current_User, $Plugins;
4568  
4569          $DB->begin( 'SERIALIZABLE' );
4570  
4571          if( $this->status != 'draft' )
4572          {    // The post is getting published in some form, set the publish date so it doesn't get auto updated in the future:
4573              $this->set( 'dateset', 1 );
4574          }
4575  
4576          if( empty($this->creator_user_ID) )
4577          { // No creator assigned yet, use current user:
4578              $this->set_creator_User( $current_User );
4579          }
4580  
4581          // Create new slug with validated title
4582          $new_Slug = new Slug();
4583          $new_Slug->set( 'title', urltitle_validate( $this->urltitle, $this->title, $this->ID, false, $new_Slug->dbprefix.'title', $new_Slug->dbprefix.'itm_ID', $new_Slug->dbtablename, $this->locale ) );
4584          $new_Slug->set( 'type', 'item' );
4585          $this->set( 'urltitle', $new_Slug->get( 'title' ) );
4586  
4587          $this->update_renderers_from_Plugins();
4588  
4589          $this->update_excerpt();
4590  
4591          if( isset($Plugins) )
4592          {    // Note: Plugins may not be available during maintenance, install or test cases
4593              // TODO: allow a plugin to cancel update here (by returning false)?
4594              $Plugins->trigger_event( 'PrependItemInsertTransact', $params = array( 'Item' => & $this ) );
4595          }
4596  
4597          global $localtimenow;
4598          $this->set_param( 'last_touched_ts', 'date', date('Y-m-d H:i:s',$localtimenow) );
4599  
4600          $dbchanges = $this->dbchanges; // we'll save this for passing it to the plugin hook
4601  
4602          if( $result = parent::dbinsert() )
4603          { // We could insert the item object..
4604  
4605              // Let's handle the extracats:
4606              $result = $this->insert_update_extracats( 'insert' );
4607  
4608              if( $result )
4609              { // Let's handle the tags:
4610                  $this->insert_update_tags( 'insert' );
4611              }
4612  
4613              // save Item settings
4614              if( $result && isset( $this->ItemSettings ) && isset( $this->ItemSettings->cache[0] ) )
4615              {
4616                  // update item ID in the ItemSettings cache
4617                  $this->ItemSettings->cache[$this->ID] = $this->ItemSettings->cache[0];
4618                  unset( $this->ItemSettings->cache[0] );
4619  
4620                  $this->ItemSettings->dbupdate();
4621              }
4622  
4623              if( $result )
4624              {
4625                  modules_call_method( 'update_item_after_insert', array( 'edited_Item' => $this ) );
4626              }
4627  
4628              // Let's handle the slugs:
4629              // set slug item ID:
4630              $new_Slug->set( 'itm_ID', $this->ID );
4631  
4632              // Create tiny slug:
4633              $new_tiny_Slug = new Slug();
4634              load_funcs( 'slugs/model/_slug.funcs.php' );
4635              $tinyurl = getnext_tinyurl();
4636              $new_tiny_Slug->set( 'title', $tinyurl );
4637              $new_tiny_Slug->set( 'type', 'item' );
4638              $new_tiny_Slug->set( 'itm_ID', $this->ID );
4639  
4640              if( $result && ( $result = ( $new_Slug->dbinsert() && $new_tiny_Slug->dbinsert() ) ) )
4641              {
4642                  $this->set( 'canonical_slug_ID', $new_Slug->ID );
4643                  $this->set( 'tiny_slug_ID', $new_tiny_Slug->ID );
4644                  if( $result = parent::dbupdate() )
4645                  {
4646                      $DB->commit();
4647  
4648                      // save the last tinyurl
4649                      global $Settings;
4650                      $Settings->set( 'tinyurl', $tinyurl );
4651                      $Settings->dbupdate();
4652  
4653                      if( isset($Plugins) )
4654                      {    // Note: Plugins may not be available during maintenance, install or test cases
4655                          $Plugins->trigger_event( 'AfterItemInsert', $params = array( 'Item' => & $this, 'dbchanges' => $dbchanges ) );
4656                      }
4657                  }
4658              }
4659          }
4660  
4661          if( ! $result )
4662          {    // Rollback current transaction
4663              $DB->rollback();
4664          }
4665          else
4666          {    // Log a creating of new item on success result
4667              log_new_item_create( $created_through );
4668          }
4669  
4670          return