| Textpattern | PHP Cross Reference | Content Management Systems |
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 : ' ', 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).' · '.safe_strftime('%d %b %Y · %X',$sPosted); 1190 if($sPosted != $sLastMod) { 1191 $out .= br.gTxt('modified_by').': '.txpspecialchars($LastModID).' · '.safe_strftime('%d %b %Y · %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
Body
title
Description
Body
title
Description
Body
title
Body
title