Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_article.php - 1534 lines - 50563 bytes - Summary - Text - Print

   1  <?php
   2  /*
   3      This is Textpattern
   4      Copyright 2005 by Dean Allen
   5      All rights reserved.
   6  
   7      Use of this software indicates acceptance of the Textpattern license agreement
   8  
   9  $HeadURL: https://textpattern.googlecode.com/svn/releases/4.5.4/source/textpattern/include/txp_article.php $
  10  $LastChangedRevision: 4270 $
  11  
  12  */
  13  
  14  if (!defined('txpinterface')) die('txpinterface is undefined.');
  15  
  16  global $vars, $statuses;
  17  
  18  $vars = array(
  19      'ID','Title','Body','Excerpt','textile_excerpt','Image',
  20      'textile_body', 'Keywords','Status','Posted','Expires','Section','Category1','Category2',
  21      'Annotate','AnnotateInvite','publish_now','reset_time','AuthorID','sPosted',
  22      'LastModID','sLastMod','override_form','from_view','year','month','day','hour',
  23      'minute','second','url_title','exp_year','exp_month','exp_day','exp_hour',
  24      'exp_minute','exp_second','sExpires'
  25  );
  26  $cfs = getCustomFields();
  27  foreach($cfs as $i => $cf_name)
  28  {
  29      $vars[] = "custom_$i";
  30  }
  31  
  32  $statuses = array(
  33          STATUS_DRAFT   => gTxt('draft'),
  34          STATUS_HIDDEN  => gTxt('hidden'),
  35          STATUS_PENDING => gTxt('pending'),
  36          STATUS_LIVE    => strong(gTxt('live')),
  37          STATUS_STICKY  => gTxt('sticky'),
  38  );
  39  
  40  if (!empty($event) and $event == 'article') {
  41      require_privs('article');
  42  
  43  
  44      $save = gps('save');
  45      if ($save) $step = 'save';
  46  
  47      $publish = gps('publish');
  48      if ($publish) $step = 'publish';
  49  
  50      if (empty($step)) $step = 'create';
  51  
  52      bouncer($step,
  53          array(
  54              'create'          => false,
  55              'publish'         => true,
  56              'edit'            => false,
  57              'save'            => true,
  58              'save_pane_state' => true
  59          )
  60      );
  61  
  62      switch($step) {
  63          case "create":   article_edit();    break;
  64          case "publish":  article_post();    break;
  65          case "edit":     article_edit();    break;
  66          case "save":     article_save();    break;
  67          case "save_pane_state":     article_save_pane_state();    break;
  68      }
  69  }
  70  
  71  //--------------------------------------------------------------
  72  
  73  	function article_post()
  74      {
  75          global $txp_user, $vars, $prefs;
  76  
  77          extract($prefs);
  78  
  79          $incoming = doSlash(textile_main_fields(array_map('assert_string', psa($vars))));
  80          extract($incoming);
  81  
  82          $msg = '';
  83          if ($Title or $Body or $Excerpt) {
  84  
  85              extract(array_map('assert_int', psa(array( 'Status', 'textile_body', 'textile_excerpt'))));
  86              // comments my be on, off, or disabled.
  87              $Annotate = (int) $Annotate;
  88              // set and validate article timestamp
  89              if ($publish_now == 1) {
  90                  $when = 'now()';
  91                  $when_ts = time();
  92              } else {
  93                  if (!is_numeric($year) || !is_numeric($month) || !is_numeric($day) || !is_numeric($hour)  || !is_numeric($minute) || !is_numeric($second) ) {
  94                      $ts = false;
  95                  } else {
  96                      $ts = strtotime($year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second);
  97                  }
  98  
  99                  if ($ts === false || $ts < 0) { // Tracking the PHP meanders on how to return an error
 100                      article_edit(array(gTxt('invalid_postdate'), E_ERROR));
 101                      return;
 102                  }
 103  
 104                  $when_ts = $ts - tz_offset($ts);
 105                  $when = "from_unixtime($when_ts)";
 106              }
 107  
 108              // Force a reasonable 'last modified' date for future articles, keep recent articles list in order
 109              $lastmod = ($when_ts > time() ? 'now()' : $when);
 110  
 111              // set and validate expiry timestamp
 112              if (empty($exp_year)) {
 113                  $expires = 0;
 114              } else {
 115                  if(empty($exp_month)) $exp_month=1;
 116                  if(empty($exp_day)) $exp_day=1;
 117                  if(empty($exp_hour)) $exp_hour=0;
 118                  if(empty($exp_minute)) $exp_minute=0;
 119                  if(empty($exp_second)) $exp_second=0;
 120  
 121                  $ts = strtotime($exp_year.'-'.$exp_month.'-'.$exp_day.' '.$exp_hour.':'.$exp_minute.':'.$exp_second);
 122                  if ($ts === false || $ts < 0) {
 123                      article_edit(array(gTxt('invalid_expirydate'), E_ERROR));
 124                      return;
 125                  } else {
 126                      $expires = $ts - tz_offset($ts);
 127                  }
 128              }
 129  
 130              if ($expires && ($expires <= $when_ts)) {
 131                  article_edit(array(gTxt('article_expires_before_postdate'), E_ERROR));
 132                  return;
 133              }
 134  
 135              if ($expires) {
 136                  $whenexpires = "from_unixtime($expires)";
 137              } else {
 138                  $whenexpires = NULLDATETIME;
 139              }
 140  
 141              $user = doSlash($txp_user);
 142              $Keywords = doSlash(trim(preg_replace('/( ?[\r\n\t,])+ ?/s', ',', preg_replace('/ +/', ' ', ps('Keywords'))), ', '));
 143              $msg = '';
 144  
 145              if (!has_privs('article.publish') && $Status >= STATUS_LIVE) $Status = STATUS_PENDING;
 146              if (empty($url_title)) $url_title = stripSpace($Title_plain, 1);
 147  
 148              $cfq = array();
 149              $cfs = getCustomFields();
 150              foreach($cfs as $i => $cf_name)
 151              {
 152                  $custom_x = "custom_{$i}";
 153                  $cfq[] = "custom_$i = '".$$custom_x."'";
 154              }
 155              $cfq = join(', ', $cfq);
 156  
 157              $rs = compact($vars);
 158              if (article_validate($rs, $msg)) {
 159                  $ok = safe_insert(
 160                     "textpattern",
 161                     "Title           = '$Title',
 162                      Body            = '$Body',
 163                      Body_html       = '$Body_html',
 164                      Excerpt         = '$Excerpt',
 165                      Excerpt_html    = '$Excerpt_html',
 166                      Image           = '$Image',
 167                      Keywords        = '$Keywords',
 168                      Status          =  $Status,
 169                      Posted          =  $when,
 170                      Expires         =  $whenexpires,
 171                      AuthorID        = '$user',
 172                      LastMod         =  $lastmod,
 173                      LastModID       = '$user',
 174                      Section         = '$Section',
 175                      Category1       = '$Category1',
 176                      Category2       = '$Category2',
 177                      textile_body    =  $textile_body,
 178                      textile_excerpt =  $textile_excerpt,
 179                      Annotate        =  $Annotate,
 180                      override_form   = '$override_form',
 181                      url_title       = '$url_title',
 182                      AnnotateInvite  = '$AnnotateInvite',"
 183                      .(($cfs) ? $cfq.',' : '').
 184                      "uid            = '".md5(uniqid(rand(),true))."',
 185                      feed_time       = now()"
 186                  );
 187  
 188                  if ($ok) {
 189  
 190                      $rs['ID'] = $GLOBALS['ID'] = $ok;
 191  
 192                      if ($Status >= STATUS_LIVE) {
 193                          do_pings();
 194                          update_lastmod();
 195                      }
 196                      callback_event('article_posted', '', false, $rs);
 197  
 198                      $s = check_url_title($url_title);
 199                      $msg = array(get_status_message($Status).' '.$s, ($s ? E_WARNING : 0));
 200                  } else {
 201                      unset($GLOBALS['ID']);
 202                      $msg = array(gTxt('article_save_failed'), E_ERROR);
 203                  }
 204              }
 205          }
 206          article_edit($msg);
 207      }
 208  
 209  //--------------------------------------------------------------
 210  
 211  	function article_save()
 212      {
 213          global $txp_user, $vars, $prefs, $statuses;
 214  
 215          extract($prefs);
 216  
 217          $incoming = array_map('assert_string', psa($vars));
 218  
 219          $oldArticle = safe_row('Status, url_title, Title, '.
 220              'unix_timestamp(LastMod) as sLastMod, LastModID, '.
 221              'unix_timestamp(Posted) as sPosted, '.
 222              'unix_timestamp(Expires) as sExpires',
 223              'textpattern', 'ID = '.(int)$incoming['ID']);
 224  
 225          if (! (    ($oldArticle['Status'] >= STATUS_LIVE and has_privs('article.edit.published'))
 226                  or ($oldArticle['Status'] >= STATUS_LIVE and $incoming['AuthorID']==$txp_user and has_privs('article.edit.own.published'))
 227                  or ($oldArticle['Status'] < STATUS_LIVE and has_privs('article.edit'))
 228                  or ($oldArticle['Status'] < STATUS_LIVE and $incoming['AuthorID']==$txp_user and has_privs('article.edit.own'))))
 229          {
 230                  // Not allowed, you silly rabbit, you shouldn't even be here.
 231                  // Show default editing screen.
 232              article_edit();
 233              return;
 234          }
 235  
 236          if ($oldArticle['sLastMod'] != $incoming['sLastMod'])
 237          {
 238              article_edit(array(gTxt('concurrent_edit_by', array('{author}' => txpspecialchars($oldArticle['LastModID']))), E_ERROR), TRUE , !AJAXALLY_CHALLENGED );
 239              return;
 240          }
 241  
 242          $incoming = textile_main_fields($incoming);
 243  
 244          extract(doSlash($incoming));
 245          extract(array_map('assert_int', psa(array('ID', 'Status', 'textile_body', 'textile_excerpt'))));
 246          // comments my be on, off, or disabled.
 247          $Annotate = (int) $Annotate;
 248  
 249          if (!has_privs('article.publish') && $Status >= STATUS_LIVE) $Status = STATUS_PENDING;
 250  
 251          // set and validate article timestamp
 252          if ($reset_time) {
 253              $whenposted = "Posted=now()";
 254              $when_ts = time();
 255          } else {
 256              if (!is_numeric($year) || !is_numeric($month) || !is_numeric($day) || !is_numeric($hour)  || !is_numeric($minute) || !is_numeric($second) ) {
 257                  $ts = false;
 258              } else {
 259                  $ts = strtotime($year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second);
 260              }
 261  
 262              if ($ts === false || $ts < 0) {
 263                  $when = $when_ts = $oldArticle['sPosted'];
 264                  $msg = array(gTxt('invalid_postdate'), E_ERROR);
 265              } else {
 266                  $when = $when_ts = $ts - tz_offset($ts);
 267              }
 268  
 269              $whenposted = "Posted=from_unixtime($when)";
 270          }
 271  
 272          // set and validate expiry timestamp
 273          if (empty($exp_year)) {
 274              $expires = 0;
 275          } else {
 276              if(empty($exp_month)) $exp_month=1;
 277              if(empty($exp_day)) $exp_day=1;
 278              if(empty($exp_hour)) $exp_hour=0;
 279              if(empty($exp_minute)) $exp_minute=0;
 280              if(empty($exp_second)) $exp_second=0;
 281  
 282              $ts = strtotime($exp_year.'-'.$exp_month.'-'.$exp_day.' '.$exp_hour.':'.$exp_minute.':'.$exp_second);
 283              if ($ts === false || $ts < 0) {
 284                  $expires = $oldArticle['sExpires'];
 285                  $msg = array(gTxt('invalid_expirydate'), E_ERROR);
 286              } else {
 287                  $expires = $ts - tz_offset($ts);
 288              }
 289          }
 290  
 291          if ($expires && ($expires <= $when_ts)) {
 292              $expires = $oldArticle['sExpires'];
 293              $msg = array(gTxt('article_expires_before_postdate'), E_ERROR);
 294          }
 295  
 296          if ($expires) {
 297              $whenexpires = "Expires=from_unixtime($expires)";
 298          } else {
 299              $whenexpires = "Expires=".NULLDATETIME;
 300          }
 301  
 302          //Auto-Update custom-titles according to Title, as long as unpublished and NOT customized
 303          if ( empty($url_title)
 304                || ( ($oldArticle['Status'] < STATUS_LIVE)
 305                      && ($oldArticle['url_title'] == $url_title )
 306                      && ($oldArticle['url_title'] == stripSpace($oldArticle['Title'],1))
 307                      && ($oldArticle['Title'] != $Title)
 308                   )
 309             )
 310          {
 311              $url_title = stripSpace($Title_plain, 1);
 312          }
 313  
 314          $Keywords = doSlash(trim(preg_replace('/( ?[\r\n\t,])+ ?/s', ',', preg_replace('/ +/', ' ', ps('Keywords'))), ', '));
 315  
 316          $user = doSlash($txp_user);
 317  
 318          $cfq = array();
 319          $cfs = getCustomFields();
 320          foreach($cfs as $i => $cf_name)
 321          {
 322              $custom_x = "custom_{$i}";
 323              $cfq[] = "custom_$i = '".$$custom_x."'";
 324          }
 325          $cfq = join(', ', $cfq);
 326  
 327          $rs = compact($vars);
 328          if (article_validate($rs, $msg)) {
 329              if (safe_update("textpattern",
 330                 "Title           = '$Title',
 331                  Body            = '$Body',
 332                  Body_html       = '$Body_html',
 333                  Excerpt         = '$Excerpt',
 334                  Excerpt_html    = '$Excerpt_html',
 335                  Keywords        = '$Keywords',
 336                  Image           = '$Image',
 337                  Status          =  $Status,
 338                  LastMod         =  now(),
 339                  LastModID       = '$user',
 340                  Section         = '$Section',
 341                  Category1       = '$Category1',
 342                  Category2       = '$Category2',
 343                  Annotate        =  $Annotate,
 344                  textile_body    =  $textile_body,
 345                  textile_excerpt =  $textile_excerpt,
 346                  override_form   = '$override_form',
 347                  url_title       = '$url_title',
 348                  AnnotateInvite  = '$AnnotateInvite',"
 349                  .(($cfs) ? $cfq.',' : '').
 350                  "$whenposted,
 351                  $whenexpires",
 352                  "ID = $ID"
 353              )) {
 354                  if ($Status >= STATUS_LIVE && $oldArticle['Status'] < STATUS_LIVE) {
 355                      do_pings();
 356                  }
 357                  if ($Status >= STATUS_LIVE || $oldArticle['Status'] >= STATUS_LIVE) {
 358                      update_lastmod();
 359                  }
 360  
 361                  callback_event('article_saved', '', false, $rs);
 362  
 363                  if (empty($msg)) {
 364                      $s = check_url_title($url_title);
 365                      $msg = array(get_status_message($Status).' '.$s, $s ? E_WARNING : 0);
 366                  }
 367              } else {
 368                  $msg = array(gTxt('article_save_failed'), E_ERROR);
 369              }
 370          }
 371          article_edit($msg, FALSE, !AJAXALLY_CHALLENGED);
 372      }
 373  
 374  //--------------------------------------------------------------
 375  
 376  	function article_edit($message = '', $concurrent = FALSE, $refresh_partials = FALSE)
 377      {
 378          global $vars, $txp_user, $prefs, $event;
 379  
 380          extract($prefs);
 381  
 382          /*
 383          $partials is an array of:
 384          $key => array (
 385              'mode' => {PARTIAL_STATIC | PARTIAL_VOLATILE | PARTIAL_VOLATILE_VALUE},
 386              'selector' => $DOM_selector,
 387               'cb' => $callback_function,
 388               'html' => $return_value_of_callback_function (need not be intialized here)
 389          )
 390          */
 391          $partials = array(
 392              'sLastMod'        => array('mode' => PARTIAL_VOLATILE_VALUE, 'selector' => '[name=sLastMod]', 'cb' => 'article_partial_value'),
 393              'sPosted'         => array('mode' => PARTIAL_VOLATILE_VALUE, 'selector' => '[name=sPosted]', 'cb' => 'article_partial_value'),
 394              'custom_fields'   => array('mode' => PARTIAL_STATIC,         'selector' => '#custom_field_group', 'cb' => 'article_partial_custom_fields'),
 395              'image'           => array('mode' => PARTIAL_STATIC,         'selector' => '#image_group', 'cb' => 'article_partial_image'),
 396              'keywords'        => array('mode' => PARTIAL_STATIC,         'selector' => 'p.keywords', 'cb' => 'article_partial_keywords'),
 397              'keywords_value'  => array('mode' => PARTIAL_VOLATILE_VALUE, 'selector' => '#keywords', 'cb' => 'article_partial_keywords_value'),
 398              'url_title'       => array('mode' => PARTIAL_STATIC,         'selector' => 'p.url-title', 'cb' => 'article_partial_url_title'),
 399              'url_title_value' => array('mode' => PARTIAL_VOLATILE_VALUE, 'selector' => '#url-title', 'cb' => 'article_partial_url_title_value'),
 400              'recent_articles' => array('mode' => PARTIAL_VOLATILE,       'selector' => '#recent_group .recent', 'cb' => 'article_partial_recent_articles'),
 401              'title'           => array('mode' => PARTIAL_STATIC,         'selector' => 'p.title', 'cb' => 'article_partial_title'),
 402              'title_value'     => array('mode' => PARTIAL_VOLATILE_VALUE, 'selector' => '#title', 'cb' => 'article_partial_title_value'),
 403              'article_view'    => array('mode' => PARTIAL_VOLATILE,       'selector' => '#article_partial_article_view', 'cb' => 'article_partial_article_view'),
 404              'body'            => array('mode' => PARTIAL_STATIC,         'selector' => 'p.body', 'cb' => 'article_partial_body'),
 405              'excerpt'         => array('mode' => PARTIAL_STATIC,         'selector' => 'p.excerpt', 'cb' => 'article_partial_excerpt'),
 406              'author'          => array('mode' => PARTIAL_VOLATILE,       'selector' => 'p.author', 'cb' => 'article_partial_author'),
 407              'article_nav'     => array('mode' => PARTIAL_VOLATILE,       'selector' => 'p.nav-tertiary', 'cb' => 'article_partial_article_nav'),
 408              'status'          => array('mode' => PARTIAL_VOLATILE,       'selector' => '#write-status', 'cb' => 'article_partial_status'),
 409              'categories'      => array('mode' => PARTIAL_STATIC,         'selector' => '#categories_group', 'cb' => 'article_partial_categories'),
 410              'section'         => array('mode' => PARTIAL_STATIC,         'selector' => 'p.section', 'cb' => 'article_partial_section'),
 411              'comments'        => array('mode' => PARTIAL_VOLATILE,       'selector' => '#write-comments', 'cb' => 'article_partial_comments'),
 412              'posted'          => array('mode' => PARTIAL_VOLATILE,       'selector' => '#write-timestamp', 'cb' => 'article_partial_posted'),
 413              'expires'         => array('mode' => PARTIAL_VOLATILE,       'selector' => '#write-expires', 'cb' => 'article_partial_expires'),
 414          );
 415  
 416          // add partials for custom fields (and their values which is redundant by design, for plugins)
 417          global $cfs;
 418          foreach ($cfs as $k => $v) {
 419              $partials["custom_field_{$k}"] = array('mode' => PARTIAL_STATIC, 'selector' => "p.custom-field.custom-{$k}", 'cb' => 'article_partial_custom_field');
 420              $partials["custom_{$k}"] = array('mode' => PARTIAL_STATIC, 'selector' => "#custom-{$k}", 'cb' => 'article_partial_value');
 421          }
 422  
 423          extract(gpsa(array('view','from_view','step')));
 424  
 425          if(!empty($GLOBALS['ID'])) { // newly-saved article
 426              $ID = $GLOBALS['ID'];
 427              $step = 'edit';
 428          } else {
 429              $ID = gps('ID');
 430          }
 431  
 432          // switch to 'text' view upon page load and after article post
 433          if(!$view || gps('save') || gps('publish')) {
 434              $view = 'text';
 435          }
 436  
 437          if (!$step) $step = "create";
 438  
 439          if ($step == "edit"
 440              && $view=="text"
 441              && !empty($ID)
 442              && $from_view != 'preview'
 443              && $from_view != 'html'
 444              && !$concurrent)
 445          {
 446              $pull = true;          //-- it's an existing article - off we go to the db
 447              $ID = assert_int($ID);
 448  
 449              $rs = safe_row(
 450                  "*, unix_timestamp(Posted) as sPosted,
 451                  unix_timestamp(Expires) as sExpires,
 452                  unix_timestamp(LastMod) as sLastMod",
 453                  "textpattern",
 454                  "ID=$ID"
 455              );
 456              if (empty($rs)) return;
 457  
 458              $rs['reset_time'] = $rs['publish_now'] = false;
 459  
 460          } else {
 461  
 462              $pull = false;         //-- assume they came from post
 463  
 464              if ($from_view=='preview' or $from_view=='html')
 465              {
 466                  $store_out = array();
 467                  $store = unserialize(base64_decode(ps('store')));
 468  
 469                  foreach($vars as $var)
 470                  {
 471                      if (isset($store[$var])) $store_out[$var] = $store[$var];
 472                  }
 473              }
 474  
 475              else
 476              {
 477                  $store_out = gpsa($vars);
 478  
 479                  if ($concurrent)
 480                  {
 481                      $store_out['sLastMod'] = safe_field('unix_timestamp(LastMod) as sLastMod', 'textpattern', 'ID='.$ID);
 482                  }
 483              }
 484  
 485              $rs = textile_main_fields($store_out);
 486  
 487              if (!empty($rs['exp_year']))
 488              {
 489                  if(empty($rs['exp_month'])) $rs['exp_month']=1;
 490                  if(empty($rs['exp_day'])) $rs['exp_day']=1;
 491                  if(empty($rs['exp_hour'])) $rs['exp_hour']=0;
 492                  if(empty($rs['exp_minute'])) $rs['exp_minute']=0;
 493                  if(empty($rs['exp_second'])) $rs['exp_second']=0;
 494                  $rs['sExpires'] = safe_strtotime($rs['exp_year'].'-'.$rs['exp_month'].'-'.$rs['exp_day'].' '.
 495                      $rs['exp_hour'].':'.$rs['exp_minute'].':'.$rs['exp_second']);
 496              }
 497  
 498              if (!empty($rs['year'])) {
 499                  $rs['sPosted'] = safe_strtotime($rs['year'].'-'.$rs['month'].'-'.$rs['day'].' '.
 500                      $rs['hour'].':'.$rs['minute'].':'.$rs['second']);
 501              }
 502          }
 503  
 504          $validator = new Validator(array(
 505              new SectionConstraint($rs['Section'])
 506          ));
 507          if (!$validator->validate()) {
 508              $rs['Section'] = getDefaultSection();
 509          }
 510  
 511          extract($rs);
 512  
 513          $GLOBALS['step'] = $step;
 514  
 515          if ($step == 'create')
 516          {
 517              $textile_body = $use_textile;
 518              $textile_excerpt = $use_textile;
 519          }
 520  
 521          if ($step != 'create' && isset($sPosted)) {
 522  
 523              // Previous record?
 524              $rs['prev_id'] = checkIfNeighbour('prev',$sPosted);
 525  
 526              // Next record?
 527              $rs['next_id'] = checkIfNeighbour('next',$sPosted);
 528          } else {
 529              $rs['prev_id'] = $rs['next_id'] = 0;
 530          }
 531  
 532          // let plugins chime in on partials meta data
 533          callback_event_ref('article_ui', 'partials_meta', 0, $rs, $partials);
 534          $rs['partials_meta'] = &$partials;
 535  
 536          // get content for volatile partials
 537          foreach ($partials as $k => $p) {
 538              if ($p['mode'] == PARTIAL_VOLATILE || $p['mode'] == PARTIAL_VOLATILE_VALUE) {
 539                  $cb = $p['cb'];
 540                  $partials[$k]['html'] = (is_array($cb) ? call_user_func($cb, $rs, $k): $cb($rs, $k));
 541              }
 542          }
 543  
 544          if ($refresh_partials) {
 545              global $theme;
 546              $response[] = $theme->announce_async($message);
 547  
 548              // update the volatile partials
 549              foreach ($partials as $k => $p) {
 550                  // volatile partials need a target DOM selector
 551                  if (empty($p['selector']) && $p['mode'] != PARTIAL_STATIC) {
 552                      trigger_error("Empty selector for partial '$k'", E_USER_ERROR);
 553                  } else {
 554                      // build response script
 555                      if ($p['mode'] == PARTIAL_VOLATILE) {
 556                          // volatile partials replace *all* of the existing HTML fragment for their selector
 557                          $response[] = '$("'.$p['selector'].'").replaceWith("'.escape_js($p['html']).'")';
 558                      } elseif ($p['mode'] == PARTIAL_VOLATILE_VALUE) {
 559                          // volatile partial values replace the *value* of elements matching their selector
 560                          $response[] = '$("'.$p['selector'].'").val("'.escape_js($p['html']).'")';
 561                      }
 562                  }
 563              }
 564              send_script_response(join(";\n", $response));
 565  
 566              // bail out
 567              return;
 568          }
 569  
 570          foreach ($partials as $k => $p) {
 571              if ($p['mode'] == PARTIAL_STATIC) {
 572                  $cb = $p['cb'];
 573                  $partials[$k]['html'] = (is_array($cb) ? call_user_func($cb, $rs, $k): $cb($rs, $k));
 574              }
 575          }
 576  
 577          $page_title = ($Title) ? $Title : gTxt('write');
 578  
 579          pagetop($page_title, $message);
 580  
 581          echo n.'<div id="'.$event.'_container" class="txp-container">';
 582          echo n.n.'<form id="article_form" name="article_form" method="post" action="index.php" '. ($step=='create' ? '>' : ' class="async">');
 583  
 584          if (!empty($store_out))
 585          {
 586              echo hInput('store', base64_encode(serialize($store_out)));
 587          }
 588  
 589          echo hInput('ID', $ID).
 590              n.eInput('article').
 591              n.sInput($step).
 592              n.hInput('sPosted', $sPosted).
 593              n.hInput('sLastMod', $sLastMod).
 594              n.hInput('AuthorID', $AuthorID).
 595              n.hInput('LastModID', $LastModID).
 596              '<input type="hidden" name="view" />'.
 597  
 598              startTable('', '', 'txp-columntable').
 599  
 600          '<tr>'.n.
 601                  '<td id="article-col-1"><div id="configuration_content">';
 602  
 603          if ($view == 'text')
 604          {
 605  
 606          //-- markup help --------------
 607  
 608              echo pluggable_ui('article_ui', 'sidehelp', side_help($textile_body, $textile_excerpt), $rs);
 609  
 610          //-- custom menu entries --------------
 611  
 612              echo pluggable_ui('article_ui', 'extend_col_1', '', $rs);
 613  
 614          //-- advanced --------------
 615  
 616              echo '<div id="advanced_group"><h3 class="lever'.(get_pref('pane_article_advanced_visible') ? ' expanded' : '').'"><a href="#advanced">'.gTxt('advanced_options').'</a></h3>'.
 617                  '<div id="advanced" class="toggle" style="display:'.(get_pref('pane_article_advanced_visible') ? 'block' : 'none').'">';
 618  
 619              // markup selection
 620              echo pluggable_ui('article_ui', 'markup',
 621                  n.graf('<label for="markup-body">'.gTxt('article_markup').'</label>'.br.
 622                      pref_text('textile_body', $textile_body, 'markup-body'), ' class="markup markup-body"').
 623                  n.graf('<label for="markup-excerpt">'.gTxt('excerpt_markup').'</label>'.br.
 624                      pref_text('textile_excerpt', $textile_excerpt, 'markup-excerpt'), ' class="markup markup-excerpt"'),
 625                  $rs);
 626  
 627              // form override
 628              echo ($allow_form_override)
 629                  ? pluggable_ui('article_ui', 'override', graf('<label for="override-form">'.gTxt('override_default_form').'</label>'.sp.popHelp('override_form').br.
 630                      form_pop($override_form, 'override-form'), ' class="override-form"'), $rs)
 631                  : '';
 632              echo '</div></div>'.n;
 633  
 634          //-- custom fields --------------
 635  
 636              echo $partials['custom_fields']['html'];
 637  
 638          //-- article image --------------
 639  
 640              echo $partials['image']['html'];
 641  
 642          //-- meta info --------------
 643  
 644              echo '<div id="meta_group"><h3 class="lever'.(get_pref('pane_article_meta_visible') ? ' expanded' : '').'"><a href="#meta">'.gTxt('meta').'</a></h3>'.
 645                  '<div id="meta" class="toggle" style="display:'.(get_pref('pane_article_meta_visible') ? 'block' : 'none').'">';
 646              // keywords
 647              echo $partials['keywords']['html'];
 648              // url title
 649              echo $partials['url_title']['html'];
 650              echo '</div></div>'.n;
 651  
 652          //-- recent articles --------------
 653  
 654              echo '<div id="recent_group"><h3 class="lever'.(get_pref('pane_article_recent_visible') ? ' expanded' : '').'"><a href="#recent">'.gTxt('recent_articles').'</a>'.'</h3>'.
 655                  '<div id="recent" class="toggle" style="display:'.(get_pref('pane_article_recent_visible') ? 'block' : 'none').'">';
 656              echo $partials['recent_articles']['html'];
 657              echo '</div></div>';
 658          }
 659  
 660          else
 661          {
 662              echo sp;
 663          }
 664  
 665          echo '</div></td>'.n.'<td id="article-main"><div id="main_content">';
 666  
 667      //-- title input --------------
 668  
 669          if ($view == 'preview')
 670          {
 671              echo '<div class="preview">'.hed(gTxt('preview'), 2).hed($Title, 1, ' class="title"');
 672          }
 673  
 674          elseif ($view == 'html')
 675          {
 676              echo '<div class="html">'.hed('HTML', 2).hed($Title, 1, ' class="title"');
 677          }
 678  
 679          elseif ($view == 'text')
 680          {
 681              echo '<div class="text">'.n.$partials['title']['html'];
 682          }
 683  
 684      //-- body --------------------
 685  
 686          if ($view == 'preview')
 687          {
 688              echo '<div class="body">'.$Body_html.'</div>';
 689          }
 690  
 691          elseif ($view == 'html')
 692          {
 693              echo tag(str_replace(array(n,t), array(br,sp.sp.sp.sp), txpspecialchars($Body_html)), 'code', ' class="body"');
 694          }
 695  
 696          else
 697          {
 698              echo $partials['body']['html'];
 699          }
 700  
 701      //-- excerpt --------------------
 702  
 703          if ($articles_use_excerpts)
 704          {
 705              if ($view == 'preview')
 706              {
 707                  echo n.'<hr /><div class="excerpt">'.$Excerpt_html.'</div>';
 708              }
 709  
 710              elseif ($view == 'html')
 711              {
 712                  echo n.'<hr />'.tag(str_replace(array(n,t), array(br,sp.sp.sp.sp), txpspecialchars($Excerpt_html)), 'code', ' class="excerpt"');
 713              }
 714  
 715              else
 716              {
 717                  echo $partials['excerpt']['html'];
 718              }
 719          }
 720  
 721      //-- author --------------
 722  
 723          if ($view=="text" && $step != "create")
 724          {
 725              echo $partials['author']['html'];
 726          }
 727  
 728          echo hInput('from_view',$view),
 729          '</div></div></td>';
 730  
 731      //-- layer tabs -------------------
 732  
 733          echo '<td id="article-tabs"><div id="view_modes">';
 734  
 735          echo pluggable_ui('article_ui', 'view',
 736              ($use_textile == USE_TEXTILE || $textile_body == USE_TEXTILE)
 737              ? tag((tab('text',$view).tab('html',$view).tab('preview',$view)), 'ul')
 738              : '&#160;',
 739              $rs);
 740          echo '</div></td>';
 741  
 742          echo '<td id="article-col-2"><div id="supporting_content">';
 743  
 744          if ($view == 'text')
 745          {
 746              if ($step != 'create')
 747              {
 748                  echo n.graf(href(gtxt('create_new'), 'index.php?event=article'), ' class="action-create"');
 749              }
 750  
 751          //-- prev/next article links --
 752  
 753              if ($step!='create' and ($rs['prev_id'] or $rs['next_id'])) {
 754                  echo $partials['article_nav']['html'];
 755              }
 756  
 757          //-- status radios --------------
 758  
 759              echo $partials['status']['html'];
 760  
 761          //-- sort and display  -----------
 762  
 763              echo pluggable_ui('article_ui', 'sort_display',
 764                  n.n.tag(
 765                      n.'<legend>'.gTxt('sort_display').'</legend>'.
 766                      //-- section select --------------
 767                      $partials['section']['html'].
 768                      //-- category selects -----------
 769                      $partials['categories']['html'].
 770                      n,
 771                      'fieldset', ' id="write-sort"'),
 772                  $rs);
 773  
 774          //-- "Comments" section
 775              echo n.n.'<div id="comments_group"'.(($use_comments==1) ? '' : ' class="empty"').'><h3 class="lever'.(get_pref('pane_article_comments_visible') ? ' expanded' : '').'"><a href="#comments">'.gTxt('comment_settings').'</a></h3>',
 776                  '<div id="comments" class="toggle" style="display:'.(get_pref('pane_article_comments_visible') ? 'block' : 'none').'">';
 777  
 778              echo $partials['comments']['html'];
 779  
 780              // end "Comments" section
 781              echo '</div></div>';
 782  
 783          //-- "Dates" section
 784              echo n.n.'<div id="dates_group"><h3 class="lever'.(get_pref('pane_article_dates_visible') ? ' expanded' : '').'"><a href="#dates">'.gTxt('date_settings').'</a></h3>',
 785                  '<div id="dates" class="toggle" style="display:'.(get_pref('pane_article_dates_visible') ? 'block' : 'none').'">';
 786  
 787              if ($step == "create" and empty($GLOBALS['ID']))
 788              {
 789          //-- timestamp -------------------
 790  
 791                  //Avoiding modified date to disappear
 792                  $persist_timestamp = (!empty($store_out['year']))?
 793                      safe_strtotime($store_out['year'].'-'.$store_out['month'].'-'.$store_out['day'].' '.$store_out['hour'].':'.$store_out['minute'].':'.$store_out['second'])
 794                      : time();
 795  
 796                  echo pluggable_ui('article_ui', 'timestamp',
 797                      n.n.'<fieldset id="write-timestamp">'.
 798                      n.'<legend>'.gTxt('timestamp').'</legend>'.
 799  
 800                      n.graf(checkbox('publish_now', '1', $publish_now, '', 'publish_now').'<label for="publish_now">'.gTxt('set_to_now').'</label>', ' class="publish-now"').
 801  
 802                      n.graf(gTxt('or_publish_at').sp.popHelp('timestamp'), ' class="publish-at"').
 803  
 804                      n.graf('<span class="label">'.gtxt('date').'</span>'.sp.
 805                          tsi('year', '%Y', $persist_timestamp).' / '.
 806                          tsi('month', '%m', $persist_timestamp).' / '.
 807                          tsi('day', '%d', $persist_timestamp)
 808                      , ' class="date posted created"'
 809                      ).
 810  
 811                      n.graf('<span class="label">'.gTxt('time').'</span>'.sp.
 812                          tsi('hour', '%H', $persist_timestamp).' : '.
 813                          tsi('minute', '%M', $persist_timestamp).' : '.
 814                          tsi('second', '%S', $persist_timestamp)
 815                      , ' class="time posted created"'
 816                      ).
 817  
 818                  n.'</fieldset>',
 819                  array('sPosted' => $persist_timestamp) + $rs);
 820  
 821          //-- expires -------------------
 822  
 823                  $persist_timestamp = (!empty($store_out['exp_year']))?
 824                      safe_strtotime($store_out['exp_year'].'-'.$store_out['exp_month'].'-'.$store_out['exp_day'].' '.$store_out['exp_hour'].':'.$store_out['exp_minute'].':'.$store_out['second'])
 825                      : NULLDATETIME;
 826  
 827                  echo pluggable_ui('article_ui', 'expires',
 828                      n.n.'<fieldset id="write-expires">'.
 829                      n.'<legend>'.gTxt('expires').'</legend>'.
 830  
 831                      n.graf('<span class="label">'.gtxt('date').'</span>'.sp.
 832                          tsi('exp_year', '%Y', $persist_timestamp).' / '.
 833                          tsi('exp_month', '%m', $persist_timestamp).' / '.
 834                          tsi('exp_day', '%d', $persist_timestamp)
 835                      , ' class="date expires"'
 836                      ).
 837  
 838                      n.graf('<span class="label">'.gTxt('time').'</span>'.sp.
 839                          tsi('exp_hour', '%H', $persist_timestamp).' : '.
 840                          tsi('exp_minute', '%M', $persist_timestamp).' : '.
 841                          tsi('exp_second', '%S', $persist_timestamp)
 842                      , ' class="time expires"'
 843                      ).
 844  
 845                  n.'</fieldset>',
 846                  $rs);
 847  
 848                  // end "Dates" section
 849                  echo n.n.'</div></div>';
 850  
 851          //-- publish button --------------
 852  
 853                  echo graf(
 854                      (has_privs('article.publish')) ?
 855                      fInput('submit','publish',gTxt('publish'),"publish", '', '', '', 4) :
 856                      fInput('submit','publish',gTxt('save'),"publish", '', '', '', 4)
 857                  , ' id="write-publish"');
 858              }
 859  
 860              else
 861              {
 862  
 863              //-- timestamp -------------------
 864  
 865                  echo $partials['posted']['html'];
 866  
 867              //-- expires -------------------
 868  
 869                  echo $partials['expires']['html'];;
 870  
 871                  // end "Dates" section
 872                  echo n.n.'</div></div>';
 873  
 874          //-- save button --------------
 875  
 876                  if (   ($Status >= STATUS_LIVE and has_privs('article.edit.published'))
 877                      or ($Status >= STATUS_LIVE and $AuthorID==$txp_user and has_privs('article.edit.own.published'))
 878                      or ($Status < STATUS_LIVE and has_privs('article.edit'))
 879                      or ($Status < STATUS_LIVE and $AuthorID==$txp_user and has_privs('article.edit.own')))
 880                          echo graf(fInput('submit','save',gTxt('save'),"publish", '', '', '', 4), ' id="write-save"');
 881              }
 882          }
 883  
 884          echo '</div></td></tr></table>'.n.
 885              tInput().n.
 886              '</form></div>'.n;
 887          // Assume users would not change the timestamp if they wanted to "publish now"/"reset time"
 888          echo script_js( <<<EOS
 889          $('#write-timestamp input.year,#write-timestamp input.month,#write-timestamp input.day,#write-timestamp input.hour,#write-timestamp input.minute,#write-timestamp input.second').change(
 890              function() {
 891                  $('#publish_now').prop('checked', false);
 892                  $('#reset_time').prop('checked', false);
 893              });
 894  EOS
 895  );
 896  
 897  
 898      }
 899  
 900  // -------------------------------------------------------------
 901  
 902  	function custField($num, $field, $content)
 903      {
 904          return n.n.graf('<label for="custom-'.$num.'">'.$field.'</label>'.br.
 905              n.fInput('text', 'custom_'.$num, $content, '', '', '', INPUT_REGULAR, '', 'custom-'.$num), ' class="custom-field custom-'.$num.'"');
 906      }
 907  
 908  // -------------------------------------------------------------
 909  	function checkIfNeighbour($whichway,$sPosted)
 910      {
 911          $sPosted = assert_int($sPosted);
 912          $dir = ($whichway == 'prev') ? '<' : '>';
 913          $ord = ($whichway == 'prev') ? 'desc' : 'asc';
 914  
 915          return safe_field("ID", "textpattern",
 916              "Posted $dir from_unixtime($sPosted) order by Posted $ord limit 1");
 917      }
 918  
 919  //--------------------------------------------------------------
 920  // remember to show markup help for both body and excerpt
 921  // if they are different
 922  
 923  	function side_help($textile_body, $textile_excerpt)
 924      {
 925          if ($textile_body == USE_TEXTILE or $textile_excerpt == USE_TEXTILE)
 926          {
 927              return n.
 928                  '<div id="textile_group">'.
 929                  hed(
 930                  '<a href="#textile_help">'.gTxt('textile_help').'</a>'
 931              , 3, ' class="lever'.(get_pref('pane_article_textile_help_visible') ? ' expanded' : '').'"').
 932  
 933                  n.'<div id="textile_help" class="toggle" style="display:'.(get_pref('pane_article_textile_help_visible') ? 'block' : 'none').'">'.
 934  
 935                  n.'<ul class="textile plain-list">'.
 936                      n.t.'<li>'.gTxt('header').': <strong>h<em>n</em>.</strong>'.sp.
 937                          popHelpSubtle('header', 400, 400).'</li>'.
 938                      n.t.'<li>'.gTxt('blockquote').': <strong>bq.</strong>'.sp.
 939                          popHelpSubtle('blockquote',400,400).'</li>'.
 940                      n.t.'<li>'.gTxt('numeric_list').': <strong>#</strong>'.sp.
 941                          popHelpSubtle('numeric', 400, 400).'</li>'.
 942                      n.t.'<li>'.gTxt('bulleted_list').': <strong>*</strong>'.sp.
 943                          popHelpSubtle('bulleted', 400, 400).'</li>'.
 944                      n.t.'<li>'.gTxt('definition_list').': <strong>; :</strong>'.sp.
 945                          popHelpSubtle('definition', 400, 400).'</li>'.
 946                  n.'</ul>'.
 947  
 948                  n.'<ul class="textile plain-list">'.
 949                      n.t.'<li>'.'_<em>'.gTxt('emphasis').'</em>_'.sp.
 950                          popHelpSubtle('italic', 400, 400).'</li>'.
 951                      n.t.'<li>'.'*<strong>'.gTxt('strong').'</strong>*'.sp.
 952                          popHelpSubtle('bold', 400, 400).'</li>'.
 953                      n.t.'<li>'.'??<cite>'.gTxt('citation').'</cite>??'.sp.
 954                          popHelpSubtle('cite', 500, 300).'</li>'.
 955                      n.t.'<li>'.'-'.gTxt('deleted_text').'-'.sp.
 956                          popHelpSubtle('delete', 400, 300).'</li>'.
 957                      n.t.'<li>'.'+'.gTxt('inserted_text').'+'.sp.
 958                          popHelpSubtle('insert', 400, 300).'</li>'.
 959                      n.t.'<li>'.'^'.gTxt('superscript').'^'.sp.
 960                          popHelpSubtle('super', 400, 300).'</li>'.
 961                      n.t.'<li>'.'~'.gTxt('subscript').'~'.sp.
 962                          popHelpSubtle('subscript', 400, 400).'</li>'.
 963                  n.'</ul>'.
 964  
 965                  n.graf(
 966                      '"'.gTxt('linktext').'":url'.sp.popHelpSubtle('link', 400, 500)
 967                  , ' class="textile"').
 968  
 969                  n.graf(
 970                      '!'.gTxt('imageurl').'!'.sp.popHelpSubtle('image', 500, 500)
 971                  , ' class="textile"').
 972  
 973                  n.graf(
 974                      '<a id="textile-docs-link" href="http://textpattern.com/textile-sandbox" target="_blank">'.gTxt('More').'</a>').
 975  
 976                  n.'</div></div>';
 977          }
 978      }
 979  
 980  //--------------------------------------------------------------
 981  
 982  	function status_radio($Status)
 983      {
 984          global $statuses;
 985  
 986          $Status = (!$Status) ? STATUS_LIVE : $Status;
 987  
 988          foreach ($statuses as $a => $b)
 989          {
 990              $out[] = n.t.'<li class="status-'.$a.($Status == $a ? ' active' : '').'">'.radio('Status', $a, ($Status == $a) ? 1 : 0, 'status-'.$a).
 991                  '<label for="status-'.$a.'">'.$b.'</label></li>';
 992          }
 993  
 994          return '<ul class="status plain-list">'.join('', $out).n.'</ul>';
 995      }
 996  
 997  //--------------------------------------------------------------
 998  
 999  	function category_popup($name, $val, $id)
1000      {
1001          $rs = getTree('root', 'article');
1002  
1003          if ($rs)
1004          {
1005              return treeSelectInput($name,$rs,$val, $id, 35);
1006          }
1007  
1008          return false;
1009      }
1010  
1011  //--------------------------------------------------------------
1012  
1013  	function section_popup($Section, $id)
1014      {
1015          $rs = safe_column('name', 'txp_section', "name != 'default'");
1016  
1017          if ($rs)
1018          {
1019              return selectInput('Section', $rs, $Section, false, '', $id);
1020          }
1021  
1022          return false;
1023      }
1024  
1025  //--------------------------------------------------------------
1026  	function tab($tabevent,$view)
1027      {
1028          $state = ($view==$tabevent) ? 'up' : 'down';
1029          $out = '<li class="view-mode '.$tabevent.'" id="tab-'.$tabevent.$state.'" title="'.gTxt('view_'.$tabevent).'">';
1030          $out.= ($tabevent!=$view) ? '<a href="javascript:document.article_form.view.value=\''.$tabevent.'\';document.article_form.submit();">'.gTxt($tabevent).'</a>' : gTxt($tabevent);
1031          $out.='</li>';
1032          return $out;
1033      }
1034  
1035  //--------------------------------------------------------------
1036  	function getDefaultSection()
1037      {
1038          return get_pref('default_section');
1039      }
1040  
1041  // -------------------------------------------------------------
1042  
1043  	function form_pop($form, $id)
1044      {
1045          $arr = array(' ');
1046  
1047          $rs = safe_column('name', 'txp_form', "type = 'article' and name != 'default' order by name");
1048  
1049          if ($rs)
1050          {
1051              return selectInput('override_form', $rs, $form, true, '', $id);
1052          }
1053      }
1054  
1055  // -------------------------------------------------------------
1056  
1057  	function check_url_title($url_title)
1058      {
1059          // Check for blank or previously used identical url-titles
1060          if (strlen($url_title) === 0)
1061          {
1062              return gTxt('url_title_is_blank');
1063          }
1064  
1065          else
1066          {
1067              $url_title_count = safe_count('textpattern', "url_title = '$url_title'");
1068  
1069              if ($url_title_count > 1)
1070              {
1071                  return gTxt('url_title_is_multiple', array('{count}' => $url_title_count));
1072              }
1073          }
1074  
1075          return '';
1076      }
1077  // -------------------------------------------------------------
1078  	function get_status_message($Status)
1079      {
1080          switch ($Status){
1081              case STATUS_PENDING: return gTxt("article_saved_pending");
1082              case STATUS_HIDDEN: return gTxt("article_saved_hidden");
1083              case STATUS_DRAFT: return gTxt("article_saved_draft");
1084              default: return gTxt('article_posted');
1085          }
1086      }
1087  // -------------------------------------------------------------
1088  	function textile_main_fields($incoming)
1089      {
1090          global $prefs;
1091  
1092          include_once txpath.'/lib/classTextile.php';
1093          $textile = new Textile($prefs['doctype']);
1094  
1095          $incoming['Title_plain'] = $incoming['Title'];
1096          $incoming['Title_html'] = ''; // not used
1097  
1098          if ($incoming['textile_body'] == LEAVE_TEXT_UNTOUCHED) {
1099  
1100              $incoming['Body_html'] = trim($incoming['Body']);
1101  
1102          }elseif ($incoming['textile_body'] == USE_TEXTILE){
1103  
1104              $incoming['Body_html'] = $textile->TextileThis($incoming['Body']);
1105              $incoming['Title'] = $textile->TextileThis($incoming['Title'],'',1);
1106  
1107          }elseif ($incoming['textile_body'] == CONVERT_LINEBREAKS){
1108  
1109              $incoming['Body_html'] = nl2br(trim($incoming['Body']));
1110          }
1111  
1112          if ($incoming['textile_excerpt'] == LEAVE_TEXT_UNTOUCHED) {
1113  
1114              $incoming['Excerpt_html'] = trim($incoming['Excerpt']);
1115  
1116          }elseif ($incoming['textile_excerpt'] == USE_TEXTILE){
1117  
1118              $incoming['Excerpt_html'] = $textile->TextileThis($incoming['Excerpt']);
1119  
1120          }elseif ($incoming['textile_excerpt'] == CONVERT_LINEBREAKS){
1121  
1122              $incoming['Excerpt_html'] = nl2br(trim($incoming['Excerpt']));
1123          }
1124  
1125          return $incoming;
1126      }
1127  // -------------------------------------------------------------
1128  	function do_pings()
1129      {
1130          global $prefs, $production_status;
1131  
1132          # only ping for Live sites
1133          if ($production_status !== 'live')
1134              return;
1135  
1136          include_once txpath.'/lib/IXRClass.php';
1137  
1138          callback_event('ping');
1139  
1140          if ($prefs['ping_textpattern_com']) {
1141              $tx_client = new IXR_Client('http://textpattern.com/xmlrpc/');
1142              $tx_client->query('ping.Textpattern', $prefs['sitename'], hu);
1143          }
1144  
1145          if ($prefs['ping_weblogsdotcom']==1) {
1146              $wl_client = new IXR_Client('http://rpc.pingomatic.com/');
1147              $wl_client->query('weblogUpdates.ping', $prefs['sitename'], hu);
1148          }
1149      }
1150  
1151  // -------------------------------------------------------------
1152  	function article_save_pane_state()
1153      {
1154          global $event;
1155          $panes = array('textile_help', 'advanced', 'custom_field', 'image', 'meta', 'recent', 'comments', 'dates');
1156          $pane = gps('pane');
1157          if (in_array($pane, $panes))
1158          {
1159              set_pref("pane_{$event}_{$pane}_visible", (gps('visible') == 'true' ? '1' : '0'), $event, PREF_HIDDEN, 'yesnoradio', 0, PREF_PRIVATE);
1160              send_xml_response();
1161          } else {
1162              trigger_error('invalid_pane', E_USER_WARNING);
1163          }
1164      }
1165  
1166  // -------------------------------------------------------------
1167  	function article_partial_title($rs)
1168      {
1169          global $step;
1170          $av_cb = $rs['partials_meta']['article_view']['cb'];
1171          return pluggable_ui('article_ui', 'title',
1172              graf('<label for="title">'.gTxt('title').'</label>'.sp.popHelp('title').br.
1173                  '<input type="text" id="title" name="Title" value="'.escape_title($rs['Title']).'" size="40" tabindex="1" />'.
1174                  ($step != 'create' ?  $av_cb($rs) : '')
1175                  , ' class="title"'),
1176              $rs);
1177      }
1178  
1179  // -------------------------------------------------------------
1180  	function article_partial_title_value($rs)
1181      {
1182          return html_entity_decode($rs['Title'], ENT_QUOTES, 'UTF-8');
1183      }
1184  
1185  // -------------------------------------------------------------
1186  	function article_partial_author($rs)
1187      {
1188          extract($rs);
1189          $out = '<p class="author"><small>'.gTxt('posted_by').': '.txpspecialchars($AuthorID).' &#183; '.safe_strftime('%d %b %Y &#183; %X',$sPosted);
1190          if($sPosted != $sLastMod) {
1191              $out .= br.gTxt('modified_by').': '.txpspecialchars($LastModID).' &#183; '.safe_strftime('%d %b %Y &#183; %X',$sLastMod);
1192          }
1193          $out .= '</small></p>';
1194          return pluggable_ui('article_ui', 'author', $out, $rs);
1195      }
1196  
1197  // -------------------------------------------------------------
1198  	function article_partial_custom_fields($rs)
1199      {
1200          global $cfs;
1201  
1202          $cf = '';
1203          $out = '<div id="custom_field_group"'.(($cfs) ? '' : ' class="empty"').'><h3 class="lever'.(get_pref('pane_article_custom_field_visible') ? ' expanded' : '').'"><a href="#custom_field">'.gTxt('custom').'</a></h3>'.
1204              '<div id="custom_field" class="toggle" style="display:'.(get_pref('pane_article_custom_field_visible') ? 'block' : 'none').'">';
1205  
1206          foreach($cfs as $k => $v)
1207          {
1208              $cf .= article_partial_custom_field($rs, "custom_field_{$k}");
1209          }
1210          $out .= pluggable_ui('article_ui', 'custom_fields', $cf, $rs);
1211          return $out.'</div></div>'.n;
1212  
1213      }
1214  
1215  // -------------------------------------------------------------
1216  	function article_partial_custom_field($rs, $key)
1217      {
1218          global $prefs;
1219          extract ($prefs);
1220  
1221          preg_match('/custom_field_([0-9]+)/', $key, $m);
1222          $custom_x_set = "custom_{$m[1]}_set";
1223          $custom_x = "custom_{$m[1]}";
1224          return ($$custom_x_set !== '' ? custField($m[1], $$custom_x_set,  $rs[$custom_x]) : '');
1225      }
1226  
1227  // -------------------------------------------------------------
1228  	function article_partial_image($rs)
1229      {
1230          $out = '<div id="image_group"><h3 class="lever'.(get_pref('pane_article_image_visible') ? ' expanded' : '').'"><a href="#image">'.gTxt('article_image').'</a></h3>'.
1231              '<div id="image" class="toggle" style="display:'.(get_pref('pane_article_image_visible') ? 'block' : 'none').'">';
1232  
1233          $out .= pluggable_ui('article_ui', 'article_image',
1234              n.graf('<label for="article-image">'.gTxt('article_image').'</label>'.sp.popHelp('article_image').br.
1235                  fInput('text', 'Image', $rs['Image'], '', '', '', INPUT_REGULAR, '', 'article-image'), ' class="article-image"'),
1236              $rs);
1237          return $out.'</div></div>'.n;
1238      }
1239  
1240  // -------------------------------------------------------------
1241  	function article_partial_keywords($rs)
1242      {
1243          return pluggable_ui('article_ui', 'keywords',
1244              n.graf('<label for="keywords">'.gTxt('keywords').'</label>'.sp.popHelp('keywords').br.
1245                  n.'<textarea id="keywords" name="Keywords" cols="'.INPUT_MEDIUM.'" rows="'.INPUT_XSMALL.'">'.txpspecialchars(article_partial_keywords_value($rs)).'</textarea>', ' class="keywords"'),
1246              $rs);
1247      }
1248  
1249  // -------------------------------------------------------------
1250  	function article_partial_keywords_value($rs)
1251      {
1252          // separate keywords by a comma plus at least one space
1253          return preg_replace('/,(\S)/', ', $1', $rs['Keywords']);
1254      }
1255  
1256  // -------------------------------------------------------------
1257  	function article_partial_url_title($rs)
1258      {
1259          return pluggable_ui('article_ui', 'url_title',
1260              n.graf('<label for="url-title">'.gTxt('url_title').'</label>'.sp.popHelp('url_title').br.
1261                  fInput('text', 'url_title', article_partial_url_title_value($rs), '', '', '', INPUT_REGULAR, '', 'url-title'), ' class="url-title"'),
1262              $rs);
1263      }
1264  
1265  // -------------------------------------------------------------
1266  	function article_partial_url_title_value($rs)
1267      {
1268          return $rs['url_title'];
1269      }
1270  
1271  // -------------------------------------------------------------
1272  	function article_partial_recent_articles($rs)
1273      {
1274          $recents = safe_rows_start("Title, ID",'textpattern',"1=1 order by LastMod desc limit 10");
1275          $ra = '';
1276  
1277          if ($recents)
1278          {
1279              $ra = '<ul class="recent plain-list">';
1280  
1281              while($recent = nextRow($recents))
1282              {
1283                  if (!$recent['Title'])
1284                  {
1285                      $recent['Title'] = gTxt('untitled').sp.$recent['ID'];
1286                  }
1287  
1288                  $ra .= n.t.'<li class="recent-article"><a href="?event=article'.a.'step=edit'.a.'ID='.$recent['ID'].'">'.escape_title($recent['Title']).'</a></li>';
1289              }
1290  
1291              $ra .= '</ul>';
1292          }
1293          return pluggable_ui('article_ui', 'recent_articles', $ra, $rs);
1294      }
1295  
1296  // -------------------------------------------------------------
1297  	function article_partial_article_view($rs)
1298      {
1299          extract($rs);
1300          if ($Status != STATUS_LIVE and $Status != STATUS_STICKY)
1301          {
1302              $url = '?txpreview='.intval($ID).'.'.time(); // article ID plus cachebuster
1303          }
1304          else
1305          {
1306              include_once txpath.'/publish/taghandlers.php';
1307              $url = permlinkurl_id($ID);
1308          }
1309          return '<span id="article_partial_article_view"><a href="'.$url.'" class="article-view">'.gTxt('view').'</a></span>';
1310      }
1311  
1312  // -------------------------------------------------------------
1313  	function article_partial_body($rs)
1314      {
1315          return pluggable_ui('article_ui', 'body',
1316              n.graf('<label for="body">'.gTxt('body').'</label>'.sp.popHelp('body').br.
1317                  '<textarea id="body" name="Body" cols="'.INPUT_LARGE.'" rows="'.INPUT_REGULAR.'" tabindex="2">'.txpspecialchars($rs['Body']).'</textarea>', ' class="body"'),
1318              $rs);
1319      }
1320  
1321  // -------------------------------------------------------------
1322  	function article_partial_excerpt($rs)
1323      {
1324          return pluggable_ui('article_ui', 'excerpt',
1325              n.graf('<label for="excerpt">'.gTxt('excerpt').'</label>'.sp.popHelp('excerpt').br.
1326                  '<textarea id="excerpt" name="Excerpt" cols="'.INPUT_LARGE.'" rows="'.INPUT_SMALL.'" tabindex="3">'.txpspecialchars($rs['Excerpt']).'</textarea>', ' class="excerpt"'),
1327              $rs);
1328      }
1329  
1330  // -------------------------------------------------------------
1331  	function article_partial_article_nav($rs)
1332      {
1333          return '<p class="nav-tertiary">'.
1334          ($rs['prev_id']
1335              ?    prevnext_link(gTxt('prev'),'article','edit',
1336                  $rs['prev_id'],'', 'prev')
1337              :    '<span class="navlink-disabled">'.gTxt('prev').'</span>').
1338          ($rs['next_id']
1339              ?    prevnext_link(gTxt('next'),'article','edit',
1340                  $rs['next_id'],'', 'next')
1341              :    '<span class="navlink-disabled">'.gTxt('next').'</span>').n.
1342          '</p>';
1343      }
1344  
1345  // -------------------------------------------------------------
1346  	function article_partial_status($rs)
1347      {
1348          return pluggable_ui('article_ui', 'status',
1349              n.n.'<fieldset id="write-status">'.
1350                  n.'<legend>'.gTxt('status').'</legend>'.
1351                  n.status_radio($rs['Status']).
1352                  n.'</fieldset>',
1353              $rs);
1354      }
1355  
1356  // -------------------------------------------------------------
1357  	function article_partial_categories($rs)
1358      {
1359          return pluggable_ui('article_ui', 'categories',
1360              n.'<div id="categories_group">'.
1361              n.graf('<label for="category-1">'.gTxt('category1').'</label> '.
1362              '<span class="category-edit">['.eLink('category', '', '', '', gTxt('edit')).']</span>'.br.
1363              n.category_popup('Category1', $rs['Category1'], 'category-1'), ' class="category category-1"').
1364  
1365              n.graf('<label for="category-2">'.gTxt('category2').'</label>'.br.
1366              n.category_popup('Category2', $rs['Category2'], 'category-2'), ' class="category category-2"').
1367              n.'</div>',
1368          $rs);
1369      }
1370  
1371  // -------------------------------------------------------------
1372  	function article_partial_section($rs)
1373      {
1374          return pluggable_ui('article_ui', 'section',
1375              n.graf('<label for="section">'.gTxt('section').'</label> '.
1376                  '<span class="section-edit">['.eLink('section', '', '', '', gTxt('edit')).']</span>'.br.
1377                  section_popup($rs['Section'], 'section'), ' class="section"'),
1378              $rs);
1379      }
1380  
1381  // -------------------------------------------------------------
1382  	function article_partial_comments($rs)
1383      {
1384          global $step, $use_comments, $comments_disabled_after, $comments_default_invite, $comments_on_default;
1385  
1386          extract($rs);
1387  
1388          if ($step == "create")
1389          {
1390              //Avoiding invite disappear when previewing
1391              $AnnotateInvite = (!empty($store_out['AnnotateInvite']))? $store_out['AnnotateInvite'] : $comments_default_invite;
1392              if ($comments_on_default==1) { $Annotate = 1; }
1393          }
1394  
1395          if ($use_comments == 1)
1396          {
1397              $comments_expired = false;
1398  
1399              if ($step != 'create' && $comments_disabled_after)
1400              {
1401                  $lifespan = $comments_disabled_after * 86400;
1402                  $time_since = time() - $sPosted;
1403  
1404                  if ($time_since > $lifespan)
1405                  {
1406                      $comments_expired = true;
1407                  }
1408              }
1409  
1410              if ($comments_expired)
1411              {
1412                  $invite = n.n.graf(gTxt('expired'), ' class="comment-annotate" id="write-comments"');
1413              }
1414              else
1415              {
1416                  $invite = n.n.'<div id="write-comments">'.
1417                      n.n.graf(
1418                      onoffRadio('Annotate', $Annotate)
1419                      , ' class="comment-annotate"').
1420  
1421                      n.n.graf(
1422                      '<label for="comment-invite">'.gTxt('comment_invitation').'</label>'.br.
1423                          fInput('text', 'AnnotateInvite', $AnnotateInvite, '', '', '', '', '', 'comment-invite')
1424                      , ' class="comment-invite"').
1425                      n.n.'</div>';
1426              }
1427  
1428              return pluggable_ui('article_ui', 'annotate_invite', $invite, $rs);
1429          }
1430      }
1431  
1432  // -------------------------------------------------------------
1433  	function article_partial_posted($rs)
1434      {
1435          extract($rs);
1436          return pluggable_ui('article_ui', 'timestamp',
1437              n.n.'<fieldset id="write-timestamp">'.
1438                  n.'<legend>'.gTxt('timestamp').'</legend>'.
1439  
1440                  n.graf(checkbox('reset_time', '1', $reset_time, '', 'reset_time').'<label for="reset_time">'.gTxt('reset_time').'</label>', ' class="reset-time"').
1441  
1442                  n.graf(gTxt('published_at').sp.popHelp('timestamp'), ' class="publish-at"').
1443  
1444                  n.graf('<span class="label">'.gtxt('date').'</span>'.sp.
1445                      tsi('year', '%Y', $sPosted).' / '.
1446                      tsi('month', '%m', $sPosted).' / '.
1447                      tsi('day', '%d', $sPosted)
1448                  , ' class="date posted created"'
1449              ).
1450  
1451                  n.graf('<span class="label">'.gTxt('time').'</span>'.sp.
1452                      tsi('hour', '%H', $sPosted).' : ' .
1453                      tsi('minute', '%M', $sPosted).' : '.
1454                      tsi('second', '%S', $sPosted)
1455                  , ' class="time posted created"'
1456              ).
1457              n.'</fieldset>',
1458              $rs);
1459      }
1460  
1461  // -------------------------------------------------------------
1462  	function article_partial_expires($rs)
1463      {
1464          extract($rs);
1465          return pluggable_ui('article_ui', 'expires',
1466              n.n.'<fieldset id="write-expires">'.
1467                  n.'<legend>'.gTxt('expires').'</legend>'.
1468  
1469                  n.graf('<span class="label">'.gtxt('date').'</span>'.sp.
1470                      tsi('exp_year', '%Y', $sExpires).' / '.
1471                      tsi('exp_month', '%m', $sExpires).' / '.
1472                      tsi('exp_day', '%d', $sExpires)
1473                  , ' class="date expires"'
1474              ).
1475  
1476                  n.graf('<span class="label">'.gTxt('time').'</span>'.sp.
1477                      tsi('exp_hour', '%H', $sExpires).' : '.
1478                      tsi('exp_minute', '%M', $sExpires).' : '.
1479                      tsi('exp_second', '%S', $sExpires)
1480                  , ' class="time expires"'
1481              ).
1482                  n.hInput('sExpires', $sExpires).
1483  
1484                  n.'</fieldset>',
1485              $rs);
1486      }
1487  
1488  // -------------------------------------------------------------
1489  	function article_partial_value($rs, $key)
1490      {
1491          return($rs[$key]);
1492      }
1493  
1494  // -------------------------------------------------------------
1495  	function article_validate($rs, &$msg)
1496      {
1497          global $prefs, $step, $statuses;
1498  
1499          $constraints = array(
1500              'Status'    => new ChoiceConstraint($rs['Status'], array('choices' => array_keys($statuses), 'message' => 'invalid_status')),
1501              'Section'   => new SectionConstraint($rs['Section']),
1502              'Category1' => new CategoryConstraint($rs['Category1'], array('type' => 'article')),
1503              'Category2' => new CategoryConstraint($rs['Category2'], array('type' => 'article')),
1504          );
1505  
1506          if (!$prefs['articles_use_excerpts']) {
1507              $constraints['excerpt_blank'] = new BlankConstraint($rs['Excerpt'], array('message' => 'excerpt_not_blank'));
1508          }
1509  
1510          if (!$prefs['use_comments']) {
1511              $constraints['annotate_invite_blank'] = new BlankConstraint($rs['AnnotateInvite'], array('message' => 'invite_not_blank'));
1512              $constraints['annotate_false'] = new FalseConstraint($rs['Annotate'], array('message' => 'comments_are_on'));
1513          }
1514  
1515          if ($prefs['allow_form_override']) {
1516              $constraints['override_form'] = new FormConstraint($rs['override_form'], array('type' => 'article'));
1517          } else {
1518              $constraints['override_form'] = new BlankConstraint($rs['override_form'], array('message' => 'override_form_not_blank'));
1519          }
1520  
1521          callback_event_ref('article_ui', "validate_$step", 0, $rs, $constraints);
1522  
1523          $validator = new Validator($constraints);
1524          if ($validator->validate()) {
1525              $msg = '';
1526              return true;
1527          } else {
1528              $msg = doArray($validator->getMessages(), 'gTxt');
1529              $msg = array(join(', ', $msg), E_ERROR);
1530              return false;
1531          }
1532      }
1533  
1534  ?>

title

Description

title

Description

title

Description

title

title

Body