Textpattern | PHP Cross Reference | Content Management Systems |
Description: Tools for page routing and handling article data.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * http://textpattern.com 6 * 7 * Copyright (C) 2016 The Textpattern Development Team 8 * 9 * This file is part of Textpattern. 10 * 11 * Textpattern is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation, version 2. 14 * 15 * Textpattern is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with Textpattern. If not, see <http://www.gnu.org/licenses/>. 22 */ 23 24 /** 25 * Tools for page routing and handling article data. 26 * 27 * @since 4.5.0 28 * @package Routing 29 */ 30 31 /** 32 * Build a query qualifier to remove non-frontpage articles from the result set. 33 * 34 * @return string An SQL qualifier for a query's 'WHERE' part 35 */ 36 37 function filterFrontPage() 38 { 39 static $filterFrontPage; 40 41 if (isset($filterFrontPage)) { 42 return $filterFrontPage; 43 } 44 45 $filterFrontPage = false; 46 47 $rs = safe_column("name", 'txp_section', "on_frontpage != '1'"); 48 49 if ($rs) { 50 $filters = array(); 51 52 foreach ($rs as $name) { 53 $filters[] = " and Section != '".doSlash($name)."'"; 54 } 55 56 $filterFrontPage = join('', $filters); 57 } 58 59 return $filterFrontPage; 60 } 61 62 /** 63 * Populates the current article data. 64 * 65 * Fills members of $thisarticle global from a database row. 66 * 67 * Keeps all article tag-related values in one place, in order to do easy 68 * bugfixing and ease the addition of new article tags. 69 * 70 * @param array $rs An article as an assocative array 71 * @example 72 * if ($rs = safe_rows_start("*, 73 * UNIX_TIMESTAMP(Posted) AS uPosted, 74 * UNIX_TIMESTAMP(Expires) AS uExpires, 75 * UNIX_TIMESTAMP(LastMod) AS uLastMod", 76 * 'textpattern', 77 * "1 = 1" 78 * )) 79 * { 80 * global $thisarticle; 81 * while ($row = nextRow($rs)) 82 * { 83 * populateArticleData($row); 84 * echo $thisarticle['title']; 85 * } 86 * } 87 */ 88 89 function populateArticleData($rs) 90 { 91 global $thisarticle, $trace; 92 93 $trace->log("[Article: '{$rs['ID']}']"); 94 95 foreach (article_column_map() as $key => $column) { 96 $thisarticle[$key] = $rs[$column]; 97 } 98 } 99 100 /** 101 * Formats article info and populates the current article data. 102 * 103 * Fills members of $thisarticle global from a database row. 104 * 105 * Basically just converts an article's date values to UNIX timestamps. 106 * Convenience for those who prefer doing conversion in application end instead 107 * of in the SQL statement. 108 * 109 * @param array $rs An article as an assocative array 110 * @example 111 * article_format_info( 112 * safe_row('*', 'textpattern', 'Status = 4 LIMIT 1') 113 * ) 114 */ 115 116 function article_format_info($rs) 117 { 118 $rs['uPosted'] = (($unix_ts = @strtotime($rs['Posted'])) !== false) ? $unix_ts : null; 119 $rs['uLastMod'] = (($unix_ts = @strtotime($rs['LastMod'])) !== false) ? $unix_ts : null; 120 $rs['uExpires'] = (($unix_ts = @strtotime($rs['Expires'])) !== false) ? $unix_ts : null; 121 populateArticleData($rs); 122 } 123 124 /** 125 * Maps 'textpattern' table's columns to article data values. 126 * 127 * This function returns an array of 'data-value' => 'column' pairs. 128 * 129 * @return array 130 */ 131 132 function article_column_map() 133 { 134 $custom = getCustomFields(); 135 $custom_map = array(); 136 137 if ($custom) { 138 foreach ($custom as $i => $name) { 139 $custom_map[$name] = 'custom_'.$i; 140 } 141 } 142 143 return array( 144 'thisid' => 'ID', 145 'posted' => 'uPosted', // Calculated value! 146 'expires' => 'uExpires', // Calculated value! 147 'modified' => 'uLastMod', // Calculated value! 148 'annotate' => 'Annotate', 149 'comments_invite' => 'AnnotateInvite', 150 'authorid' => 'AuthorID', 151 'title' => 'Title', 152 'url_title' => 'url_title', 153 'description' => 'description', 154 'category1' => 'Category1', 155 'category2' => 'Category2', 156 'section' => 'Section', 157 'keywords' => 'Keywords', 158 'article_image' => 'Image', 159 'comments_count' => 'comments_count', 160 'body' => 'Body_html', 161 'excerpt' => 'Excerpt_html', 162 'override_form' => 'override_form', 163 'status' => 'Status', 164 ) + $custom_map; 165 } 166 167 /** 168 * Find an adjacent article relative to a provided threshold level. 169 * 170 * @param scalar $threshold The value to compare against 171 * @param string $s Optional section restriction 172 * @param string $type Lesser or greater neighbour? Either '<' (previous) or '>' (next) 173 * @param array $atts Attribute of article at threshold 174 * @param string $threshold_type 'cooked': Use $threshold as SQL clause; 'raw': Use $threshold as an escapable scalar 175 * @return array|bool An array populated with article data, or 'false' in case of no matches 176 */ 177 178 function getNeighbour($threshold, $s, $type, $atts = array(), $threshold_type = 'raw') 179 { 180 global $prefs; 181 static $cache = array(); 182 183 $key = md5($threshold.$s.$type.join(n, $atts)); 184 185 if (isset($cache[$key])) { 186 return $cache[$key]; 187 } 188 189 extract($atts); 190 $expired = ($expired && ($prefs['publish_expired_articles'])); 191 $customFields = getCustomFields(); 192 $thisid = isset($thisid) ? intval($thisid) : 0; 193 194 // Building query parts; lifted from publish.php. 195 $ids = array_map('intval', do_list($id)); 196 $id = (!$id) ? '' : " AND ID IN (".join(',', $ids).")"; 197 switch ($time) { 198 case 'any': 199 $time = ""; 200 break; 201 case 'future': 202 $time = " AND Posted > ".now('posted'); 203 break; 204 default: 205 $time = " AND Posted <= ".now('posted'); 206 } 207 208 if (!$expired) { 209 $time .= " AND (".now('expires')." <= Expires OR Expires IS NULL)"; 210 } 211 212 $custom = ''; 213 214 if ($customFields) { 215 foreach ($customFields as $cField) { 216 if (isset($atts[$cField])) { 217 $customPairs[$cField] = $atts[$cField]; 218 } 219 } 220 221 if (!empty($customPairs)) { 222 $custom = buildCustomSql($customFields, $customPairs); 223 } 224 } 225 226 if ($keywords) { 227 $keys = doSlash(do_list($keywords)); 228 229 foreach ($keys as $key) { 230 $keyparts[] = "FIND_IN_SET('".$key."', Keywords)"; 231 } 232 233 $keywords = " AND (".join(" OR ", $keyparts).")"; 234 } 235 236 $sortdir = strtolower($sortdir); 237 238 // Invert $type for ascending sortdir. 239 $types = array( 240 '>' => array('desc' => '>', 'asc' => '<'), 241 '<' => array('desc' => '<', 'asc' => '>'), 242 ); 243 244 $type = ($type == '>') ? $types['>'][$sortdir] : $types['<'][$sortdir]; 245 246 // Escape threshold and treat it as a string unless explicitly told otherwise. 247 if ($threshold_type != 'cooked') { 248 $threshold = "'".doSlash($threshold)."'"; 249 } 250 251 $safe_name = safe_pfx('textpattern'); 252 $q = array( 253 "SELECT ID AS thisid, Section AS section, Title AS title, url_title, UNIX_TIMESTAMP(Posted) AS posted FROM $safe_name 254 WHERE ($sortby $type $threshold OR ".($thisid ? "$sortby = $threshold AND ID $type $thisid" : "0").")", 255 ($s != '' && $s != 'default') ? "AND Section = '".doSlash($s)."'" : filterFrontPage(), 256 $id, 257 $time, 258 $custom, 259 $keywords, 260 "AND Status = 4", 261 "ORDER BY $sortby", 262 ($type == '<') ? "DESC" : "ASC", 263 ', ID '.($type == '<' ? 'DESC' : 'ASC'), 264 "LIMIT 1", 265 ); 266 267 $cache[$key] = getRow(join(n.' ', $q)); 268 269 return (is_array($cache[$key])) ? $cache[$key] : false; 270 } 271 272 /** 273 * Find next and previous articles relative to a provided threshold level. 274 * 275 * @param int $id The "pivot" article's id; use zero (0) to indicate $thisarticle 276 * @param scalar $threshold The value to compare against if $id != 0 277 * @param string $s Optional section restriction if $id != 0 278 * @return array An array populated with article data 279 */ 280 281 function getNextPrev($id = 0, $threshold = null, $s = '') 282 { 283 $threshold_type = 'raw'; 284 285 if ($id !== 0) { 286 // Pivot is specific article by ID: In lack of further information, 287 // revert to default sort order 'Posted desc'. 288 $atts = filterAtts() + array('sortby' => 'Posted', 'sortdir' => 'DESC', 'thisid' => $id); 289 } else { 290 // Pivot is $thisarticle: Use article attributes to find its neighbours. 291 assert_article(); 292 global $thisarticle; 293 if (!is_array($thisarticle)) { 294 return array(); 295 } 296 297 $s = $thisarticle['section']; 298 $atts = filterAtts() + array('thisid' => $thisarticle['thisid'], 'sort' => 'Posted DESC'); 299 $m = preg_split('/\s+/', $atts['sort']); 300 301 // If in doubt, fall back to chronologically descending order. 302 if (empty($m[0]) // No explicit sort attribute 303 || count($m) > 2 // Complex clause, e.g. 'foo asc, bar desc' 304 || !preg_match('/^(?:[0-9a-zA-Z$_\x{0080}-\x{FFFF}]+|`[\x{0001}-\x{FFFF}]+`)$/u', $m[0]) // The clause's first verb is not a MySQL column identifier. 305 ) { 306 $atts['sortby'] = "Posted"; 307 $atts['sortdir'] = "DESC"; 308 } else { 309 // Sort is like 'foo asc'. 310 $atts['sortby'] = $m[0]; 311 $atts['sortdir'] = (isset($m[1]) && strtolower($m[1]) == 'desc' ? "DESC" : "ASC"); 312 } 313 314 // Attributes with special treatment. 315 switch ($atts['sortby']) { 316 case 'Posted': 317 $threshold = "FROM_UNIXTIME(".doSlash($thisarticle['posted']).")"; 318 $threshold_type = 'cooked'; 319 break; 320 case 'Expires': 321 $threshold = "FROM_UNIXTIME(".doSlash($thisarticle['expires']).")"; 322 $threshold_type = 'cooked'; 323 break; 324 case 'LastMod': 325 $threshold = "FROM_UNIXTIME(".doSlash($thisarticle['modified']).")"; 326 $threshold_type = 'cooked'; 327 break; 328 default: 329 // Retrieve current threshold value per sort column from $thisarticle. 330 $acm = array_flip(article_column_map()); 331 $key = $acm[$atts['sortby']]; 332 $threshold = $thisarticle[$key]; 333 break; 334 } 335 } 336 337 $out['next'] = getNeighbour($threshold, $s, '>', $atts, $threshold_type); 338 $out['prev'] = getNeighbour($threshold, $s, '<', $atts, $threshold_type); 339 340 return $out; 341 } 342 343 /** 344 * Gets the site last modification date. 345 * 346 * @return string 347 * @package Pref 348 */ 349 350 function lastMod() 351 { 352 $last = safe_field("UNIX_TIMESTAMP(val)", 'txp_prefs', "name = 'lastmod' AND prefs_id = 1"); 353 354 return gmdate("D, d M Y H:i:s \G\M\T", $last); 355 } 356 357 /** 358 * Parse a string and replace any Textpattern tags with their actual value. 359 * 360 * @param string $thing The raw string 361 * @param null|bool $condition Process true/false part 362 * @return string The parsed string 363 * @package TagParser 364 */ 365 366 function parse($thing, $condition = null) 367 { 368 global $production_status, $trace, $txp_parsed, $txp_else, $txp_current_tag; 369 370 if (isset($condition)) { 371 if ($production_status === 'debug') { 372 $trace->log("[$txp_current_tag: ".($condition ? 'true' : 'false') .']'); 373 } 374 } else { 375 $condition = true; 376 } 377 378 if (false === strpos($thing, '<txp:') and false === strpos($thing, '::')) { 379 return $condition ? $thing : ''; 380 } 381 382 $hash = sha1($thing); 383 384 if (!isset($txp_parsed[$hash])) { 385 $tags = array(array()); 386 $tag = array(); 387 $outside = array(); 388 $inside = array(''); 389 $else = array(-1); 390 $count = array(-1); 391 $level = 0; 392 393 $f = '@(</?(?:txp|[a-z]{3}:):\w+(?:\s+\w+\s*=\s*(?:"(?:[^"]|"")*"|\'(?:[^\']|\'\')*\'|[^\s\'"/>]+))*\s*/?'.chr(62).')@s'; 394 $t = '@^./?(txp|[a-z]{3}:):(\w+)(.*?)/?.$@s'; 395 396 $parsed = preg_split($f, $thing, -1, PREG_SPLIT_DELIM_CAPTURE); 397 398 foreach ($parsed as $i => $chunk) { 399 if ($i&1) { 400 preg_match($t, $chunk, $tag[$level]); 401 $count[$level] += 2; 402 403 if ($tag[$level][2] === 'else') { 404 $else[$level] = $count[$level]; 405 } 406 407 // Handle short tags. 408 if (strlen($tag[$level][1]) !== 3 and $tag[$level][1] !== 'txp:' and $tag[$level][2] !== 'else') { 409 $tag[$level][2] = $tag[$level][1] . $tag[$level][2]; 410 $tag[$level][2][3] = '_'; 411 } 412 413 if ($chunk[strlen($chunk) - 2] === '/') { 414 // Self closed tag. 415 $tags[$level][] = array($chunk, $tag[$level][2], $tag[$level][3], null, null); 416 $inside[$level] .= $chunk; 417 } elseif ($chunk[1] !== '/') { 418 // Opening tag. 419 $inside[$level] .= $chunk; 420 $level++; 421 $outside[$level] = $chunk; 422 $inside[$level] = ''; 423 $else[$level] = $count[$level] = -1; 424 $tags[$level] = array(); 425 } else { 426 // Closing tag. 427 $sha = sha1($inside[$level]); 428 $txp_parsed[$sha] = $count[$level] > 2 ? $tags[$level] : false; 429 $txp_else[$sha] = array($else[$level] > 0 ? $else[$level] : $count[$level], $count[$level] - 2); 430 $level--; 431 $tags[$level][] = array($outside[$level+1], $tag[$level][2], $tag[$level][3], $inside[$level+1], $chunk); 432 $inside[$level] .= $inside[$level+1] . $chunk; 433 } 434 } else { 435 $tags[$level][] = $chunk; 436 $inside[$level] .= $chunk; 437 } 438 } 439 440 $txp_parsed[$hash] = $count[0] > 0 ? $tags[0] : false; 441 $txp_else[$hash] = array($else[0] > 0 ? $else[0] : $count[0] + 2, $count[0]); 442 } 443 444 $tag = $txp_parsed[$hash]; 445 446 if (empty($tag)) { 447 return $condition ? $thing : ''; 448 } 449 450 list($first, $last) = $txp_else[$hash]; 451 452 if ($condition) { 453 $last = $first - 2; 454 $first = 1; 455 } elseif ($first <= $last) { 456 $first += 2; 457 } else { 458 return ''; 459 } 460 461 for ($out = $tag[$first - 1]; $first <= $last; $first++) { 462 $t = $tag[$first]; 463 $out .= processTags($t[1], $t[2], $t[3]) . $tag[++$first]; 464 } 465 466 return $out; 467 } 468 469 /** 470 * Guesstimate whether a given function name may be a valid tag handler. 471 * 472 * @param string $tag function name 473 * @return bool FALSE if the function name is not a valid tag handler 474 * @package TagParser 475 */ 476 477 function maybe_tag($tag) 478 { 479 static $tags = null; 480 481 if ($tags === null) { 482 $tags = get_defined_functions(); 483 $tags = array_flip($tags['user']); 484 } 485 486 return isset($tags[$tag]); 487 } 488 489 /** 490 * Parse a tag for attributes and hand over to the tag handler function. 491 * 492 * @param string $tag The tag name 493 * @param string $atts The attribute string 494 * @param string|null $thing The tag's content in case of container tags 495 * @return string Parsed tag result 496 * @package TagParser 497 */ 498 499 function processTags($tag, $atts, $thing = null) 500 { 501 global $production_status, $txp_current_tag, $txp_current_form, $trace; 502 static $registry = null; 503 504 if ($production_status !== 'live') { 505 $old_tag = $txp_current_tag; 506 $txp_current_tag = '<txp:'.$tag.$atts.(isset($thing) ? '>' : '/>'); 507 $trace->start($txp_current_tag); 508 } 509 510 if ($registry === null) { 511 $registry = Txp::get('\Textpattern\Tag\Registry'); 512 } 513 514 $out = $registry->process($tag, splat($atts), $thing); 515 516 if ($out === false) { 517 if (maybe_tag($tag)) { // Deprecated in 4.6.0. 518 trigger_error(gTxt('unregistered_tag'), E_USER_NOTICE); 519 $out = $registry->register($tag)->process($tag, splat($atts), $thing); 520 } else { 521 trigger_error(gTxt('unknown_tag'), E_USER_WARNING); 522 $out = ''; 523 } 524 } 525 526 if ($production_status !== 'live') { 527 $trace->stop(isset($thing) ? "</txp:{$tag}>" : null); 528 $txp_current_tag = $old_tag; 529 } 530 531 return $out; 532 } 533 534 /** 535 * Protection from those who'd bomb the site by GET. 536 * 537 * Origin of the infamous 'Nice try' message and an even more useful '503' 538 * HTTP status. 539 */ 540 541 function bombShelter() 542 { 543 global $prefs; 544 $in = serverset('REQUEST_URI'); 545 546 if (!empty($prefs['max_url_len']) and strlen($in) > $prefs['max_url_len']) { 547 txp_status_header('503 Service Unavailable'); 548 exit('Nice try.'); 549 } 550 } 551 552 /** 553 * Checks a named item's existence in a database table. 554 * 555 * The given database table is prefixed with 'txp_'. As such this function can 556 * only be used with core database tables. 557 * 558 * @param string $table The database table name 559 * @param string $val The name to look for 560 * @param bool $debug Dump the query 561 * @return bool|string The item's name, or FALSE when it doesn't exist 562 * @package Filter 563 * @example 564 * if ($r = ckEx('section', 'about')) 565 * { 566 * echo "Section '{$r}' exists."; 567 * } 568 */ 569 570 function ckEx($table, $val, $debug = false) 571 { 572 return safe_field("name", 'txp_'.$table, "name = '".doSlash($val)."' LIMIT 1", $debug); 573 } 574 575 /** 576 * Checks if the given category exists. 577 * 578 * @param string $type The category type, either 'article', 'file', 'link', 'image' 579 * @param string $val The category name to look for 580 * @param bool $debug Dump the query 581 * @return bool|string The category's name, or FALSE when it doesn't exist 582 * @package Filter 583 * @see ckEx() 584 * @example 585 * if ($r = ckCat('article', 'development')) 586 * { 587 * echo "Category '{$r}' exists."; 588 * } 589 */ 590 591 function ckCat($type, $val, $debug = false) 592 { 593 return safe_field("name", 'txp_category', "name = '".doSlash($val)."' AND type = '".doSlash($type)."' LIMIT 1", $debug); 594 } 595 596 /** 597 * Lookup an article by ID. 598 * 599 * This function takes an article's ID, and checks if it's been published. If it 600 * has, returns the section and the ID as an array. FALSE otherwise. 601 * 602 * @param int $val The article ID 603 * @param bool $debug Dump the query 604 * @return array|bool Array of ID and section on success, FALSE otherwise 605 * @package Filter 606 * @example 607 * if ($r = ckExID(36)) 608 * { 609 * echo "Article #{$r['id']} is published, and belongs to the section {$r['section']}."; 610 * } 611 */ 612 613 function ckExID($val, $debug = false) 614 { 615 return safe_row("ID, Section", 'textpattern', "ID = ".intval($val)." AND Status >= 4 LIMIT 1", $debug); 616 } 617 618 /** 619 * Lookup an article by URL title. 620 * 621 * This function takes an article's URL title, and checks if the article has 622 * been published. If it has, returns the section and the ID as an array. 623 * FALSE otherwise. 624 * 625 * @param string $val The URL title 626 * @param bool $debug Dump the query 627 * @return array|bool Array of ID and section on success, FALSE otherwise 628 * @package Filter 629 * @example 630 * if ($r = ckExID('my-article-title')) 631 * { 632 * echo "Article #{$r['id']} is published, and belongs to the section {$r['section']}."; 633 * } 634 */ 635 636 function lookupByTitle($val, $debug = false) 637 { 638 return safe_row("ID, Section", 'textpattern', "url_title = '".doSlash($val)."' AND Status >= 4 LIMIT 1", $debug); 639 } 640 641 /** 642 * Lookup a published article by URL title and section. 643 * 644 * This function takes an article's URL title, and checks if the article has 645 * been published. If it has, returns the section and the ID as an array. 646 * FALSE otherwise. 647 * 648 * @param string $val The URL title 649 * @param string $section The section name 650 * @param bool $debug Dump the query 651 * @return array|bool Array of ID and section on success, FALSE otherwise 652 * @package Filter 653 * @example 654 * if ($r = ckExID('my-article-title', 'my-section')) 655 * { 656 * echo "Article #{$r['id']} is published, and belongs to the section {$r['section']}."; 657 * } 658 */ 659 660 function lookupByTitleSection($val, $section, $debug = false) 661 { 662 return safe_row("ID, Section", 'textpattern', "url_title = '".doSlash($val)."' AND Section = '".doSlash($section)."' AND Status >= 4 LIMIT 1", $debug); 663 } 664 665 /** 666 * Lookup live article by ID and section. 667 * 668 * @param int $id Article ID 669 * @param string $section Section name 670 * @param bool $debug 671 * @return array|bool 672 * @package Filter 673 */ 674 675 function lookupByIDSection($id, $section, $debug = false) 676 { 677 return safe_row("ID, Section", 'textpattern', "ID = ".intval($id)." AND Section = '".doSlash($section)."' AND Status >= 4 LIMIT 1", $debug); 678 } 679 680 /** 681 * Lookup live article by ID. 682 * 683 * @param int $id Article ID 684 * @param bool $debug 685 * @return array|bool 686 * @package Filter 687 */ 688 689 function lookupByID($id, $debug = false) 690 { 691 return safe_row("ID, Section", 'textpattern', "ID = ".intval($id)." AND Status >= 4 LIMIT 1", $debug); 692 } 693 694 /** 695 * Lookup live article by date and URL title. 696 * 697 * @param string $when date wildcard 698 * @param string $title URL title 699 * @param bool $debug 700 * @return array|bool 701 * @package Filter 702 */ 703 704 function lookupByDateTitle($when, $title, $debug = false) 705 { 706 return safe_row("ID, Section", 'textpattern', "posted LIKE '".doSlash($when)."%' AND url_title LIKE '".doSlash($title)."' AND Status >= 4 LIMIT 1"); 707 } 708 709 /** 710 * Chops a request string into URL-decoded path parts. 711 * 712 * @param string $req Request string 713 * @return array 714 * @package URL 715 */ 716 717 function chopUrl($req) 718 { 719 $req = strtolower($req); 720 721 // Strip off query_string, if present. 722 $qs = strpos($req, '?'); 723 724 if ($qs) { 725 $req = substr($req, 0, $qs); 726 } 727 728 $req = preg_replace('/index\.php$/', '', $req); 729 $r = array_map('urldecode', explode('/', $req)); 730 $o['u0'] = (isset($r[0])) ? $r[0] : ''; 731 $o['u1'] = (isset($r[1])) ? $r[1] : ''; 732 $o['u2'] = (isset($r[2])) ? $r[2] : ''; 733 $o['u3'] = (isset($r[3])) ? $r[3] : ''; 734 $o['u4'] = (isset($r[4])) ? $r[4] : ''; 735 736 return $o; 737 } 738 739 /** 740 * Save and retrieve the individual article's attributes plus article list 741 * attributes for next/prev tags. 742 * 743 * @param array $atts 744 * @return array 745 * @since 4.5.0 746 * @package TagParser 747 */ 748 749 function filterAtts($atts = null) 750 { 751 global $prefs, $trace; 752 static $out = array(); 753 754 if (is_array($atts)) { 755 if (empty($out)) { 756 $out = lAtts(array( 757 'sort' => 'Posted desc', 758 'sortby' => '', 759 'sortdir' => '', 760 'keywords' => '', 761 'expired' => $prefs['publish_expired_articles'], 762 'id' => '', 763 'time' => 'past', 764 ), $atts, 0); 765 $trace->log('[filterAtts accepted]'); 766 } else { 767 // TODO: deal w/ nested txp:article[_custom] tags. 768 $trace->log('[filterAtts ignored]'); 769 } 770 } 771 772 if (empty($out)) { 773 $trace->log('[filterAtts not set]'); 774 } 775 776 return $out; 777 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title