Added new option $_CONF['article_comment_close_enabled'] to enable/disable automatically closing stories for comments after a certain amount of days (bug #0000959). Changed handling of comment_expire field in gl_stories such that 0 means the story is always open for comments.
3 /* Reminder: always indent with 4 spaces (no tabs). */
4 // +---------------------------------------------------------------------------+
6 // +---------------------------------------------------------------------------+
9 // | Geeklog Story Abstraction. |
10 // +---------------------------------------------------------------------------+
11 // | Copyright (C) 2006-2009 by the following authors: |
13 // | Authors: Michael Jervis, mike AT fuckingbrit DOT com |
14 // +---------------------------------------------------------------------------+
16 // | This program is free software; you can redistribute it and/or |
17 // | modify it under the terms of the GNU General Public License |
18 // | as published by the Free Software Foundation; either version 2 |
19 // | of the License, or (at your option) any later version. |
21 // | This program is distributed in the hope that it will be useful, |
22 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
23 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
24 // | GNU General Public License for more details. |
26 // | You should have received a copy of the GNU General Public License |
27 // | along with this program; if not, write to the Free Software Foundation, |
28 // | Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
30 // +---------------------------------------------------------------------------+
33 * This file provides a class to represent a story, or article. It provides a
34 * finite state machine for articles. Switching them between the various states:
44 * @copyright Copyright © 2006-2009
45 * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
46 * @author Michael Jervis, mike AT fuckingbrit DOT com
51 * Constants for stories:
52 * Loading from database:
54 define('STORY_LOADED_OK', 1);
56 define('STORY_INVALID_SID', -1);
57 define('STORY_PERMISSION_DENIED', -2);
58 define('STORY_EDIT_DENIED', -3);
61 * Constants for Stories:
64 define('STORY_SAVED', 2);
65 define('STORY_SAVED_SUBMISSION', 3);
68 * Constants for Stories:
69 * Loading from request.
71 define('STORY_DUPLICATE_SID', -4);
72 define('STORY_EXISTING_NO_EDIT_PERMISSION', -5);
73 define('STORY_NO_ACCESS_PARAMS', -6);
74 define('STORY_EMPTY_REQUIRED_FIELDS', -7);
75 define('STORY_NO_ACCESS_TOPIC', -8);
78 * Constants for our magic loader
80 define('STORY_AL_ALPHANUM', 0);
81 define('STORY_AL_NUMERIC', 1);
82 define('STORY_AL_CHECKBOX', 2);
83 define('STORY_AL_ANYTHING', 3);
87 //*************************************************************************/
92 * Mode, either 'admin' for in the admin screens, or submission, for
93 * when the user is using submit.php. Controls tons of stuff.
98 * Type of story. User submission or normal editor entered stuff.
99 * Will be 'submission' if it's from the submission queue.
101 var $type = 'article';
106 * PRIVATE MEMBER VARIABLES: Things that make up a story.
110 var $_meta_description;
121 var $_comment_expire;
126 var $_show_topic_icon;
131 var $_advanced_editor_mode;
140 /* Misc display fields we also load from the database for a story: */
149 * The original SID of the article, cached incase it's changed:
159 * Array of images uploaded for the story.
164 * Magic array used for cheating when loading/saving stories from/to db.
166 * List of database field names (which are translated into member variables
167 * by prepending _ to the value) as pointers to whether or not they are used
168 * to save data. Everything with a save value of 1 will be saved, those with
169 * a save value of 0 will just be loaded.
171 * This allows us to automate the loading of story, user and topic from a
172 * database result array, and generate saving of a story, from the same
175 var $_dbFields = array
183 'meta_description' => 1,
184 'meta_keywords' => 1,
193 'show_topic_icon' => 1,
195 'comment_expire' => 1,
196 'trackbackcode' => 1,
200 'advanced_editor_mode' => 1,
215 * Magic array used for loading basic data from posted form. Of form:
216 * postfield -> numeric, target, used with COM_applyFilter. Some fields
217 * have exceptions applied
219 var $_postFields = array
231 'meta_description' => array
236 'meta_keywords' => array
241 'show_topic_icon' => array
246 'draft_flag' => array
251 'statuscode' => array
266 'commentcode' => array
271 'trackbackcode' => array
281 'story_hits' => array
286 'story_comments' => array
291 'story_emails' => array
296 'story_trackbacks' => array
326 'trackbacks' => array
336 /**************************************************************************/
338 /**************************************************************************/
341 * Constructor, creates a story, taking a (geeklog) database object.
342 * @param $mode string Story class mode, either 'admin' or 'submission'
344 function Story($mode = 'admin')
350 * Check to see if there is any content in the story, for
351 * bothering to preview testing really.
353 * @return boolean trim(title+intro+body) != ''
355 function hasContent()
357 if (trim($this->_title) != '') {
360 if (trim($this->_introtext) != '') {
363 if (trim($this->_bodytext) != '') {
371 * Loads a story object from an array (that's come back from the db..)
373 * Used from loadFromDatabase, and used on it's own from story list
375 * @param $story array Story array from db
378 function loadFromArray($story)
380 /* Use the magic cheat array to quickly reload the whole story
381 * from the database result array, doing the quick stripslashes.
383 reset($this->_dbFields);
385 while (list($fieldname,$save) = each($this->_dbFields)) {
386 $varname = '_' . $fieldname;
388 if (array_key_exists($fieldname, $story)) {
389 $this->{$varname}= stripslashes($story[$fieldname]);
393 if (array_key_exists('username', $story)) {
394 $this->_username = $story['username'];
396 if (array_key_exists('fullname', $story)) {
397 $this->_fullname = $story['fullname'];
400 // Overwrite the date with the timestamp.
401 $this->_date = $story['unixdate'];
403 if (!empty($story['expireunix'])) {
404 $this->_expire = $story['expireunix'];
409 if (!empty($story['cmt_expire_unix'])) {
410 $this->_comment_expire = $story['cmt_expire_unix'];
412 $this->_comment_expire = 0;
415 // Store the original SID
416 $this->_originalSid = $this->_sid;
420 * Load a Story object from the sid specified, returning a status result.
421 * The result will either be a permission denied message, invalid SID
422 * message, or a loaded ok message. If it's loaded ok, then we've got all
423 * the exciting gubbins here.
425 * Only used from story admin and submit.php!
427 * @param $sid string Story Identifier, valid geeklog story id from the db.
428 * @return Integer from a constant.
430 function loadFromDatabase($sid, $mode = 'edit')
432 global $_TABLES, $_CONF, $_USER;
434 $sid = addslashes(COM_applyFilter($sid));
436 if (!empty($sid) && (($mode == 'edit') || ($mode == 'view'))) {
440 = "SELECT STRAIGHT_JOIN s.*, UNIX_TIMESTAMP(s.date) AS unixdate, UNIX_TIMESTAMP(s.expire) AS expireunix, UNIX_TIMESTAMP(s.comment_expire) AS cmt_expire_unix, "
441 . "u.username, u.fullname, u.photo, u.email, t.topic, t.imageurl " . "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u, {$_TABLES['topics']} AS t " . "WHERE (s.uid = u.uid) AND (s.tid = t.tid) AND (sid = '$sid')";
444 "SELECT STRAIGHT_JOIN s.sid, s.uid, s.draft_flag, s.tid, s.date, s.title, CAST(s.introtext AS text) AS introtext, CAST(s.bodytext AS text) AS bodytext, s.hits, s.numemails, s.comments, s.trackbacks, s.related, s.featured, s.show_topic_icon, s.commentcode, s.trackbackcode, s.statuscode, s.expire, s.postmode, s.frontpage, s.owner_id, s.group_id, s.perm_owner, s.perm_group, s.perm_members, s.perm_anon, s.advanced_editor_mode, " . " UNIX_TIMESTAMP(s.date) AS unixdate, UNIX_TIMESTAMP(s.expire) AS expireunix, UNIX_TIMESTAMP(s.comment_expire) AS cmt_expire_unix, " . "u.username, u.fullname, u.photo, u.email, t.topic, t.imageurl " . "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u, {$_TABLES['topics']} AS t " . "WHERE (s.uid = u.uid) AND (s.tid = t.tid) AND (sid = '$sid')";
445 } elseif (!empty($sid) && ($mode == 'editsubmission')) {
446 $sql = 'SELECT STRAIGHT_JOIN s.*, UNIX_TIMESTAMP(s.date) AS unixdate, '
447 . 'u.username, u.fullname, u.photo, u.email, t.topic, t.imageurl, t.group_id, ' . 't.perm_owner, t.perm_group, t.perm_members, t.perm_anon ' . 'FROM ' . $_TABLES['storysubmission'] . ' AS s, ' . $_TABLES['users'] . ' AS u, ' . $_TABLES['topics'] . ' AS t WHERE (s.uid = u.uid) AND' . ' (s.tid = t.tid) AND (sid = \'' . $sid . '\')';
448 } elseif ($mode == 'edit') {
449 $this->_sid = COM_makesid();
450 $this->_old_sid = $this->_sid;
452 if (isset($_CONF['draft_flag'])) {
453 $this->_draft_flag = $_CONF['draft_flag'];
455 $this->_draft_flag = 0;
458 if (isset($_CONF['show_topic_icon'])) {
459 $this->_show_topic_icon = $_CONF['show_topic_icon'];
461 $this->_show_topic_icon = 1;
464 if (COM_isAnonUser()) {
467 $this->_uid = $_USER['uid'];
469 $this->_date = time();
470 $this->_expire = time();
471 if ($_CONF['article_comment_close_enabled']) {
472 $this->_comment_expire = time() +
473 ($_CONF['article_comment_close_days'] * 86400);
475 $this->_comment_expire = 0;
477 $this->_commentcode = $_CONF['comment_code'];
478 $this->_trackbackcode = $_CONF['trackback_code'];
480 $this->_meta_description = '';
481 $this->_meta_keywords = '';
482 $this->_introtext = '';
483 $this->_bodytext = '';
485 if (isset($_CONF['frontpage'])) {
486 $this->_frontpage = $_CONF['frontpage'];
488 $this->_frontpage = 1;
492 $this->_comments = 0;
493 $this->_trackbacks = 0;
494 $this->_numemails = 0;
496 if (isset($_CONF['advanced_editor']) && $_CONF['advanced_editor'] && ($_CONF['postmode'] != 'plaintext')) {
497 $this->_advanced_editor_mode = 1;
498 $this->_postmode = 'adveditor';
500 $this->_postmode = $_CONF['postmode'];
501 $this->_advanced_editor_mode = 0;
504 $this->_statuscode = 0;
505 $this->_featured = 0;
506 if (COM_isAnonUser()) {
507 $this->_owner_id = 1;
509 $this->_owner_id = $_USER['uid'];
512 if (isset($_GROUPS['Story Admin'])) {
513 $this->_group_id = $_GROUPS['Story Admin'];
515 $this->_group_id = SEC_getFeatureGroup('story.edit');
519 SEC_setDefaultPermissions($array, $_CONF['default_permissions_story']);
520 $this->_perm_owner = $array['perm_owner'];
521 $this->_perm_group = $array['perm_group'];
522 $this->_perm_anon = $array['perm_anon'];
523 $this->_perm_members = $array['perm_members'];
525 $this->loadFromArgsArray($_POST);
528 /* if we have SQL, load from it */
530 $result = DB_query($sql);
533 $story = DB_fetchArray($result, false);
534 if ($story == null) {
535 return STORY_INVALID_SID;
537 $this->loadFromArray($story);
538 if (!isset($story['owner_id'])) {
539 $story['owner_id'] = 1;
541 $access = SEC_hasAccess($story['owner_id'], $story['group_id'],
542 $story['perm_owner'], $story['perm_group'],
543 $story['perm_members'], $story['perm_anon']);
545 $this->_access = min($access, SEC_hasTopicAccess($this->_tid));
547 if ($this->_access == 0) {
548 return STORY_PERMISSION_DENIED;
549 } elseif ($this->_access == 2 && $mode != 'view') {
550 return STORY_EDIT_DENIED;
551 } elseif ((($this->_access == 2) && ($mode == 'view')) && (($this->_draft_flag == 1) || ($this->_date > time()))) {
552 return STORY_INVALID_SID;
555 return STORY_INVALID_SID;
559 if ($mode == 'editsubmission') {
560 if (isset($_CONF['draft_flag'])) {
561 $this->_draft_flag = $_CONF['draft_flag'];
563 $this->_draft_flag = 1;
566 if (isset($_CONF['show_topic_icon'])) {
567 $this->_show_topic_icon = $_CONF['show_topic_icon'];
569 $this->_show_topic_icon = 1;
572 $this->_commentcode = $_CONF['comment_code'];
573 $this->_trackbackcode = $_CONF['trackback_code'];
574 $this->_featured = 0;
575 $this->_expire = time();
576 if ($_CONF['article_comment_close_enabled']) {
577 $this->_comment_expire = time() +
578 ($_CONF['article_comment_close_days'] * 86400);
580 $this->_comment_expire = 0;
583 if (DB_getItem($_TABLES['topics'], 'archive_flag', "tid = '{$this->_tid}'") == 1) {
584 $this->_frontpage = 0;
585 } elseif (isset($_CONF['frontpage'])) {
586 $this->_frontpage = $_CONF['frontpage'];
588 $this->_frontpage = 1;
591 $this->_comments = 0;
592 $this->_trackbacks = 0;
593 $this->_numemails = 0;
594 $this->_statuscode = 0;
595 $this->_owner_id = $this->_uid;
598 $this->_sanitizeData();
599 return STORY_LOADED_OK;
603 * Saves the story in it's final state to the database.
605 * Handles all the SID magic etc.
606 * @return Integer status result from a constant list.
608 function saveToDatabase()
612 if (DB_getItem($_TABLES['topics'], 'tid', 'archive_flag=1') == $this->_tid) {
613 $this->_featured = 0;
614 $this->_frontpage = 0;
615 $this->_statuscode = STORY_ARCHIVE_ON_EXPIRE;
618 /* if a featured, non-draft, that goes live straight away, unfeature
619 * other stories in same topic:
621 if ($this->_featured == '1') {
622 // there can only be one non-draft featured story
623 if ($this->_draft_flag == 0 AND $this->_date <= time()) {
624 if ($this->_frontpage == 1) {
625 // un-feature any featured frontpage story
626 DB_query("UPDATE {$_TABLES['stories']} SET featured = 0 WHERE featured = 1 AND draft_flag = 0 AND frontpage = 1 AND date <= NOW()");
629 // un-feature any featured story in the same topic
630 DB_query("UPDATE {$_TABLES['stories']} SET featured = 0 WHERE featured = 1 AND draft_flag = 0 AND tid = '{$this->_tid}' AND date <= NOW()");
634 $oldArticleExists = false;
635 $currentSidExists = false;
637 /* Fix up old sid => new sid stuff */
638 $checksid = addslashes($this->_originalSid); // needed below
640 if ($this->_sid != $this->_originalSid) {
641 /* The sid has changed. Load from request will have
642 * ensured that if the new sid exists an error has
643 * been thrown, but we need to know if the old sid
644 * actually existed (as opposed to being a generated
645 * sid that was then thrown away) to reduce the sheer
646 * number of SQL queries we do.
648 $newsid = addslashes($this->_sid);
650 $sql = "SELECT 1 FROM {$_TABLES['stories']} WHERE sid='{$checksid}'";
651 $result = DB_query($sql);
653 if ($result && (DB_numRows($result) > 0)) {
654 $oldArticleExists = true;
657 if ($oldArticleExists) {
659 $sql = "UPDATE {$_TABLES['comments']} SET sid='$newsid' WHERE type='article' AND sid='$checksid'";
663 $sql = "UPDATE {$_TABLES['article_images']} SET ai_sid = '{$newsid}' WHERE ai_sid = '{$checksid}'";
666 /* Move trackbacks */
667 $sql = "UPDATE {$_TABLES['trackback']} SET sid='{$newsid}' WHERE sid='{$checksid}' AND type='article'";
672 /* Acquire Comment Count */
673 $sql = "SELECT COUNT(1) FROM {$_TABLES['comments']} WHERE type='article' AND sid='{$this->_sid}'";
674 $result = DB_query($sql);
676 if ($result && (DB_numRows($result) == 1)) {
677 $array = DB_fetchArray($result);
678 $this->_comments = $array[0];
680 $this->_comments = 0;
683 /* Format dates for storage: */
685 * Doing this here would use the webserver's timezone, but we need
686 * to use the DB server's timezone so that ye olde timezone hack
687 * still works. See use of FROM_UNIXTIME in the SQL below.
689 * $this->_date = date('Y-m-d H:i:s', $this->_date);
690 * $this->_expire = date('Y-m-d H:i:s', $this->_expire);
694 // Get the related URLs
695 $this->_related = implode("\n", STORY_extractLinks("{$this->_introtext} {$this->_bodytext}"));
696 $sql = 'REPLACE INTO ' . $_TABLES['stories'] . ' (';
697 $values = ' VALUES (';
698 reset($this->_dbFields);
700 /* This uses the database field array to generate a SQL Statement. This
701 * means that when adding new fields to save and load, all we need to do
702 * is add the field name to the array, and the code will magically cope.
704 while (list($fieldname, $save) = each($this->_dbFields)) {
706 $varname = '_' . $fieldname;
707 $sql .= $fieldname . ', ';
708 if (($fieldname == 'date') || ($fieldname == 'expire') ||
709 ($fieldname == 'comment_expire')) {
710 // let the DB server do this conversion (cf. timezone hack)
711 $values .= 'FROM_UNIXTIME(' . $this->{$varname} . '), ';
713 $values .= '\'' . addslashes($this->{$varname}) . '\', ';
718 $sql = substr($sql, 0, strlen($sql) - 2);
719 $values = substr($values, 0, strlen($values) - 2);
720 $sql .= ') ' . $values . ')';
724 /* Clean up the old story */
725 if ($oldArticleExists) {
726 $sql = "DELETE FROM {$_TABLES['stories']} WHERE sid='$checksid'";
730 if ($this->type == 'submission') {
731 /* there might be a submission, clean it up */
732 DB_delete($_TABLES['storysubmission'], 'sid', $checksid);
739 * Loads a story from the post data. This is the most exciting function in
740 * the whole entire world. First it'll clean up that horrible Magic Quotes
741 * crap. Then it'll do all Geeklog's funky security stuff, anti XSS, anti
742 * SQL Injection. Yay.
744 function loadFromArgsArray(&$array)
748 /* magic_quotes_gpc cleanup routine now in submitstory() in
749 * /public_html/admin/story.php
752 $retval = STORY_LOADED_OK; // default to success
754 /* Load the trivial stuff: */
755 $this->_loadBasics($array);
757 /* Check to see if we have permission to edit this sid, and that this
758 * sid is not a duplicate or anything horrible like that. ewww.
761 = 'SELECT owner_id, group_id, perm_owner, perm_group, perm_members, perm_anon ' . ' FROM ' . $_TABLES['stories']
762 . ' WHERE sid=\'' . $this->_sid . '\'';
763 $result = DB_query($sql);
765 if ($result && (DB_numRows($result) > 0)) {
766 /* Sid exists! Is it our article? */
767 if ($this->_sid != $this->_originalSid) {
768 // for story preview: don't abort
769 $retval = STORY_DUPLICATE_SID;
772 $article = DB_fetchArray($result);
774 if (SEC_hasAccess($article['owner_id'], $article['group_id'],
775 $article['perm_owner'], $article['perm_group'],
776 $article['perm_members'], $article['perm_anon']) < 3) {
777 return STORY_EXISTING_NO_EDIT_PERMISSION;
781 $access = SEC_hasAccess($this->_owner_id, $this->_group_id, $this->_perm_owner, $this->_perm_group,
782 $this->_perm_members, $this->_perm_anon);
784 if (($access < 3) || !SEC_hasTopicAccess($this->_tid) || !SEC_inGroup($this->_group_id)) {
785 return STORY_NO_ACCESS_PARAMS;
788 /* Load up the topic name and icon */
789 $topic = DB_query("SELECT topic, imageurl FROM {$_TABLES['topics']} WHERE tid='{$this->_tid}'");
790 $topic = DB_fetchArray($topic);
791 $this->_topic = $topic['topic'];
792 $this->_imageurl = $topic['imageurl'];
794 //$title = COM_stripSlashes( $array['title'] );
795 //$intro = COM_stripSlashes( $array['introtext'] );
796 //$body = COM_stripSlashes( $array['bodytext'] );
798 /* Then load the title, intro and body */
799 if (($array['postmode'] == 'html') || ($array['postmode'] == 'adveditor') || ($array['postmode'] == 'wikitext')) {
800 $this->_htmlLoadStory($array['title'], $array['introtext'], $array['bodytext']);
802 if ($this->_postmode == 'adveditor') {
803 $this->_advanced_editor_mode = 1;
804 $this->_postmode = 'html';
806 $this->_advanced_editor_mode = 0;
809 $this->_advanced_editor_mode = 0;
810 $this->_plainTextLoadStory($array['title'], $array['introtext'], $array['bodytext']);
813 if (empty($this->_title) || empty($this->_introtext)) {
814 return STORY_EMPTY_REQUIRED_FIELDS;
817 $this->_sanitizeData();
823 * Sets up basic data for a new user submission story
825 * @param string Topic the user picked before heading to submission
827 function initSubmission($topic)
829 global $_USER, $_CONF, $_TABLES;
831 if (COM_isAnonUser()) {
834 $this->_uid = $_USER['uid'];
837 $this->_postmode = $_CONF['postmode'];
839 // If a topic has been specified, use it, if permitted
840 // otherwise, fall back to the default permitted topic.
841 // if we still don't have one...
843 // Have we specified a permitted topic?
844 if (!empty($topic)) {
846 = DB_getItem($_TABLES['topics'], 'tid', "tid = '" . addslashes($topic) . "'" . COM_getTopicSql('AND'));
848 if ($allowed != $topic) {
853 // Do we now not have a topic?
855 // Get default permitted:
856 $topic = DB_getItem($_TABLES['topics'], 'tid', 'is_default = 1' . COM_getPermSQL('AND'));
860 $this->_tid = $topic;
861 $this->_date = time();
865 * Loads a submitted story from postdata
867 function loadSubmission()
871 $this->_expire = time();
872 $this->_date = time();
873 if ($_CONF['article_comment_close_enabled']) {
874 $this->_comment_expire = time() +
875 ($_CONF['article_comment_close_days'] * 86400);
877 $this->_comment_expire = 0;
880 // Handle Magic GPC Garbage:
881 while (list($key, $value) = each($array))
883 $array[$key] = COM_stripslashes($value);
886 $this->_postmode = COM_applyFilter($array['postmode']);
887 $this->_sid = COM_applyFilter($array['sid']);
888 $this->_uid = COM_applyFilter($array['uid'], true);
889 if ($this->_uid < 1) {
892 $this->_unixdate = COM_applyFilter($array['date'], true);
894 if (!isset($array['bodytext'])) {
895 $array['bodytext'] = '';
898 /* Then load the title, intro and body */
899 if (($array['postmode'] == 'html') || ($array['postmode'] == 'adveditor')) {
900 $this->_htmlLoadStory($array['title'], $array['introtext'], $array['bodytext']);
902 if ($this->_postmode == 'adveditor') {
903 $this->_advanced_editor_mode = 1;
904 $this->_postmode = 'html';
906 $this->_advanced_editor_mode = 0;
909 $this->_advanced_editor_mode = 0;
910 $this->_plainTextLoadStory($array['title'], $array['introtext'], $array['bodytext']);
913 $this->_tid = COM_applyFilter($array['tid']);
915 if (empty($this->_title) || empty($this->_introtext)) {
916 return STORY_EMPTY_REQUIRED_FIELDS;
919 return STORY_LOADED_OK;
923 * Returns a story formatted for spam check:
925 * @return string Story formatted for spam check.
927 function GetSpamCheckFormat()
929 return "<h1>{$this->_title}</h1><p>{$this->_introtext}</p><p>{$this->_bodytext}</p>";
933 * Saves a story submission.
935 * @return integer result code explaining behaviour.
937 function saveSubmission()
939 global $_USER, $_CONF, $_TABLES;
940 $this->_sid = COM_makeSid();
942 if (COM_isAnonUser()) {
945 $this->_uid = $_USER['uid'];
948 $tmptid = addslashes(COM_sanitizeID($this->_tid));
950 $result = DB_query('SELECT group_id,perm_owner,perm_group,perm_members,perm_anon FROM ' .
951 "{$_TABLES['topics']} WHERE tid = '{$tmptid}'" .
952 COM_getTopicSQL('AND'));
954 if (DB_numRows($result) == 0) {
955 // user doesn't have access to this topic - bail
956 return STORY_NO_ACCESS_TOPIC;
959 $T = DB_fetchArray($result);
961 if (($_CONF['storysubmission'] == 1) && !SEC_hasRights('story.submit')) {
962 $this->_sid = addslashes($this->_sid);
963 $this->_tid = $tmptid;
964 $this->_title = addslashes($this->_title);
965 $this->_introtext = addslashes($this->_introtext);
966 $this->_bodytext = addslashes($this->_bodytext);
967 $this->_postmode = addslashes($this->_postmode);
968 DB_save($_TABLES['storysubmission'], 'sid,tid,uid,title,introtext,bodytext,date,postmode',
969 "{$this->_sid},'{$this->_tid}',{$this->_uid},'{$this->_title}'," .
970 "'{$this->_introtext}','{$this->_bodytext}',NOW(),'{$this->_postmode}'");
972 return STORY_SAVED_SUBMISSION;
974 // post this story directly. First establish the necessary missing data.
975 $this->_sanitizeData();
977 if (!isset($_CONF['show_topic_icon'])) {
978 $_CONF['show_topic_icon'] = 1;
981 if (DB_getItem($_TABLES['topics'], 'archive_flag', "tid = '{$tmptid}'") == 1) {
982 $this->_frontpage = 0;
983 } elseif (isset($_CONF['frontpage'])) {
984 $this->_frontpage = $_CONF['frontpage'];
986 $this->_frontpage = 1;
989 $this->_oldsid = $this->_sid;
990 $this->_date = mktime();
991 $this->_featured = 0;
992 $this->_commentcode = $_CONF['comment_code'];
993 $this->_trackbackcode = $_CONF['trackback_code'];
994 $this->_statuscode = 0;
995 $this->_show_topic_icon = $_CONF['show_topic_icon'];
996 if (COM_isAnonUser()) {
997 $this->_owner_id = 1;
999 $this->_owner_id = $_USER['uid'];
1001 $this->_group_id = $T['group_id'];
1002 $this->_perm_owner = $T['perm_owner'];
1003 $this->_perm_group = $T['perm_group'];
1004 $this->_perm_members = $T['perm_members'];
1005 $this->_perm_anon = $T['perm_anon'];
1007 $this->saveToDatabase();
1009 PLG_itemSaved($this->_sid, 'article');
1010 COM_rdfUpToDateCheck();
1018 * Inserts image HTML into the place of Image Placeholders for stories
1021 * @return array containing errors, or empty.
1023 function insertImages()
1025 global $_CONF, $_TABLES, $LANG24;
1027 // Grab member vars into locals:
1028 $intro = $this->_introtext;
1029 $body = $this->_bodytext;
1030 $fulltext = "$intro $body";
1032 $result = DB_query("SELECT ai_filename FROM {$_TABLES['article_images']} WHERE "
1033 ."ai_sid = '{$this->_sid}' ORDER BY ai_img_num"
1035 $nrows = DB_numRows($result);
1037 $stdImageLoc = true;
1039 if (!strstr($_CONF['path_images'], $_CONF['path_html'])) {
1040 $stdImageLoc = false;
1043 for ($i = 1; $i <= $nrows; $i++) {
1044 $A = DB_fetchArray($result);
1046 $sizeattributes = COM_getImgSizeAttributes($_CONF['path_images'] . 'articles/' . $A['ai_filename']);
1048 $norm = '[image' . $i . ']';
1049 $left = '[image' . $i . '_left]';
1050 $right = '[image' . $i . '_right]';
1052 $unscalednorm = '[unscaled' . $i . ']';
1053 $unscaledleft = '[unscaled' . $i . '_left]';
1054 $unscaledright = '[unscaled' . $i . '_right]';
1056 // See how many times image $i is used in the fulltext of the article:
1057 $icount = substr_count($fulltext, $norm) + substr_count($fulltext, $left) +
1058 substr_count($fulltext, $right);
1059 // including unscaled.
1060 $icount = $icount + substr_count($fulltext, $unscalednorm) +
1061 substr_count($fulltext, $unscaledleft)
1062 + substr_count($fulltext, $unscaledright);
1064 // If the image we are currently looking at wasn't used, we need
1067 // There is an image that wasn't used, create an error
1068 $errors[] = $LANG24[48] . " #$i, {$A['ai_filename']}, " . $LANG24[53];
1070 // We had no errors, so this image and all previous images
1071 // are used, so we will then go and replace them
1072 if (count($errors) == 0) {
1076 // If we are storing images on a "standard path" i.e. is
1077 // available to the host web server, then the url to this
1078 // image is based on the path to images, site url, articles
1079 // folder and it's filename.
1081 // Otherwise, we have to use the image handler to load the
1082 // image from whereever else on the file system we're
1085 $imgpath = substr($_CONF['path_images'], strlen($_CONF['path_html']));
1086 $imgSrc = $_CONF['site_url'] . '/' . $imgpath . 'articles/' . $A['ai_filename'];
1088 $imgSrc = $_CONF['site_url'] . '/getimage.php?mode=articles&image=' . $A['ai_filename'];
1091 // Build image tags for each flavour of the image:
1092 $img_noalign = '<img ' . $sizeattributes . 'src="' . $imgSrc . '" alt=""' . XHTML . '>';
1093 $img_leftalgn = '<img ' . $sizeattributes . 'class="floatleft" src="' . $imgSrc . '" alt=""' . XHTML . '>';
1094 $img_rightalgn = '<img ' . $sizeattributes . 'class="floatright" src="' . $imgSrc . '" alt=""' . XHTML . '>';
1097 // Are we keeping unscaled images?
1098 if ($_CONF['keep_unscaled_image'] == 1) {
1099 // Yes we are, so, we need to find out what the filename
1100 // of the original, unscaled image is:
1101 $lFilename_large = substr_replace($A['ai_filename'], '_original.',
1102 strrpos($A['ai_filename'], '.'), 1);
1103 $lFilename_large_complete = $_CONF['path_images'] . 'articles/' .
1106 // We need to map that filename to the right location
1107 // or the fetch script:
1109 $lFilename_large_URL = $_CONF['site_url'] . '/' . $imgpath .
1110 'articles/' . $lFilename_large;
1112 $lFilename_large_URL = $_CONF['site_url'] .
1113 '/getimage.php?mode=show&image=' .
1117 // And finally, replace the [imageX_mode] tags with the
1118 // image and its hyperlink (only when the large image
1122 if (file_exists($lFilename_large_complete)) {
1123 $lLink_url = $lFilename_large_URL;
1124 $lLink_attr = array('title' => $LANG24[57]);
1128 if (!empty($lLink_url)) {
1129 $intro = str_replace($norm, COM_createLink($img_noalign, $lLink_url, $lLink_attr), $intro);
1130 $body = str_replace($norm, COM_createLink($img_noalign, $lLink_url, $lLink_attr), $body);
1131 $intro = str_replace($left, COM_createLink($img_leftalgn, $lLink_url, $lLink_attr), $intro);
1132 $body = str_replace($left, COM_createLink($img_leftalgn, $lLink_url, $lLink_attr), $body);
1133 $intro = str_replace($right, COM_createLink($img_rightalgn, $lLink_url, $lLink_attr), $intro);
1134 $body = str_replace($right, COM_createLink($img_rightalgn, $lLink_url, $lLink_attr), $body);
1136 // We aren't wrapping our image tags in hyperlinks, so
1137 // just replace the [imagex_mode] tags with the image:
1138 $intro = str_replace($norm, $img_noalign, $intro);
1139 $body = str_replace($norm, $img_noalign, $body);
1140 $intro = str_replace($left, $img_leftalgn, $intro);
1141 $body = str_replace($left, $img_leftalgn, $body);
1142 $intro = str_replace($right, $img_rightalgn, $intro);
1143 $body = str_replace($right, $img_rightalgn, $body);
1146 // And insert the unscaled mode images:
1147 if (($_CONF['allow_user_scaling'] == 1) and ($_CONF['keep_unscaled_image'] == 1)) {
1148 if (file_exists($lFilename_large_complete)) {
1149 $imgSrc = $lFilename_large_URL;
1150 $sizeattributes = COM_getImgSizeAttributes($lFilename_large_complete);
1153 $intro = str_replace($unscalednorm, '<img ' . $sizeattributes . 'src="' .
1154 $imgSrc . '" alt=""' . XHTML . '>', $intro);
1155 $body = str_replace($unscalednorm, '<img ' . $sizeattributes . 'src="' .
1156 $imgSrc . '" alt=""' . XHTML . '>', $body);
1157 $intro = str_replace($unscaledleft, '<img ' . $sizeattributes .
1158 'align="left" src="' . $imgSrc . '" alt=""' . XHTML . '>', $intro);
1159 $body = str_replace($unscaledleft, '<img ' . $sizeattributes .
1160 'align="left" src="' . $imgSrc . '" alt=""' . XHTML . '>', $body);
1161 $intro = str_replace($unscaledright, '<img ' . $sizeattributes .
1162 'align="right" src="' . $imgSrc. '" alt=""' . XHTML . '>', $intro);
1163 $body = str_replace($unscaledright, '<img ' . $sizeattributes .
1164 'align="right" src="' . $imgSrc . '" alt=""' . XHTML . '>', $body);
1170 $this->_introtext = $intro;
1171 $this->_bodytext = $body;
1177 * This replaces all article image HTML in intro and body with
1180 * @param string $sid ID for story to parse
1181 * @param string $intro Intro text
1182 * @param string $body Body text
1183 * @return string processed text
1186 function replaceImages($text)
1188 global $_CONF, $_TABLES, $LANG24;
1190 $stdImageLoc = true;
1192 if (!strstr($_CONF['path_images'], $_CONF['path_html'])) {
1193 $stdImageLoc = false;
1197 /* If we haven't already cached the images for this story, do so */
1198 if (!is_array($this->_storyImages)) {
1199 $result= DB_query("SELECT ai_filename FROM {$_TABLES['article_images']} WHERE " .
1200 "ai_sid = '{$this->_sid}' ORDER BY ai_img_num");
1201 $nrows = DB_numRows($result);
1202 $this->_storyImages = array();
1204 for ($i = 1; $i <= $nrows; $i++)
1206 $this->_storyImages[] = DB_fetchArray($result);
1211 $count = count($this->_storyImages);
1214 // If the article has any images, remove them back to [image] tags.
1215 for ($i = 0; $i < $count; $i++) {
1216 $A = $this->_storyImages[$i];
1218 $imageX = '[image' . ($i + 1) . ']';
1219 $imageX_left = '[image' . ($i + 1) . '_left]';
1220 $imageX_right = '[image' . ($i + 1) . '_right]';
1222 $sizeattributes = COM_getImgSizeAttributes($_CONF['path_images'] . 'articles/' . $A['ai_filename']);
1227 if ($_CONF['keep_unscaled_image'] == 1) {
1228 $lFilename_large = substr_replace($A['ai_filename'],
1229 '_original.', strrpos($A['ai_filename'], '.'), 1);
1230 $lFilename_large_complete = $_CONF['path_images'] . 'articles/' . $lFilename_large;
1233 $imgpath = substr($_CONF['path_images'], strlen($_CONF['path_html']));
1234 $lFilename_large_URL = $_CONF['site_url'] . '/' . $imgpath . 'articles/' . $lFilename_large;
1236 $lFilename_large_URL = $_CONF['site_url'] . '/getimage.php?mode=show&image='
1240 if (file_exists($lFilename_large_complete)) {
1241 $lLinkPrefix = '<a href="' . $lFilename_large_URL . '" title="' . $LANG24[57] . '">';
1242 $lLinkSuffix = '</a>';
1247 $imgpath = substr($_CONF['path_images'], strlen($_CONF['path_html']));
1248 $imgSrc = $_CONF['site_url'] . '/' . $imgpath . 'articles/' . $A['ai_filename'];
1250 $imgSrc = $_CONF['site_url'] . '/getimage.php?mode=articles&image=' . $A['ai_filename'];
1253 $norm = $lLinkPrefix . '<img ' . $sizeattributes . 'src="' . $imgSrc . '" alt=""' . XHTML . '>' . $lLinkSuffix;
1254 $left = $lLinkPrefix . '<img ' . $sizeattributes . 'class="floatleft" src="' . $imgSrc . '" alt=""' . XHTML . '>'
1256 $right = $lLinkPrefix . '<img ' . $sizeattributes . 'class="floatright" src="' . $imgSrc . '" alt=""' . XHTML . '>'
1259 $text = str_replace($norm, $imageX, $text);
1260 $text = str_replace($left, $imageX_left, $text);
1261 $text = str_replace($right, $imageX_right, $text);
1263 if (($_CONF['allow_user_scaling'] == 1) and ($_CONF['keep_unscaled_image'] == 1)) {
1264 $unscaledX = '[unscaled' . ($i + 1) . ']';
1265 $unscaledX_left = '[unscaled' . ($i + 1) . '_left]';
1266 $unscaledX_right = '[unscaled' . ($i + 1) . '_right]';
1268 if (file_exists($lFilename_large_complete)) {
1269 $sizeattributes = COM_getImgSizeAttributes($lFilename_large_complete);
1270 $norm = '<img ' . $sizeattributes . 'src="' . $lFilename_large_URL . '" alt=""' . XHTML . '>';
1271 $left = '<img ' . $sizeattributes . 'align="left" src="' . $lFilename_large_URL . '" alt=""' . XHTML . '>';
1272 $right = '<img ' . $sizeattributes . 'align="right" src="' . $lFilename_large_URL . '" alt=""' . XHTML . '>';
1275 $text = str_replace($norm, $unscaledX, $text);
1276 $text = str_replace($left, $unscaledX_left, $text);
1277 $text = str_replace($right, $unscaledX_right, $text);
1285 * Return the SID in a clean way
1287 * @param $fordb boolean True if we want an 'addslashes' version for the db
1289 function getSid($fordb = false)
1292 return addslashes($this->_sid);
1299 * Get the access level
1301 function getAccess()
1303 return $this->_access;
1307 * Provide access to story elements. For the editor.
1309 * This is a pseudo-property, implementing a getter for story
1310 * details as if as an associative array. Personally, I'd
1311 * rather be able to assign getters and setters to actual
1312 * properties to mask controlled access to private member
1313 * variables. But, you get what you get with PHP. So here it
1314 * is in all its nastiness.
1316 * @param string $item Item to fetch.
1317 * @return mixed The clean and ready to use (in edit mode) value requested.
1319 function EditElements($item = 'title')
1322 switch (strtolower($item))
1325 $return = strtotime($this->_date);
1330 $return = strtotime($this->_expire);
1334 case 'publish_hour':
1335 $return = date('H', $this->_date);
1339 case 'publish_month':
1340 $return = date('m', $this->_date);
1345 $return = date('d', $this->_date);
1349 case 'publish_year':
1350 $return = date('Y', $this->_date);
1355 $return = date('H', $this->_date);
1359 case 'publish_minute':
1360 $return = date('i', $this->_date);
1364 case 'publish_second':
1365 $return = date('s', $this->_date);
1369 case 'expire_second':
1370 $return = date('s', $this->_expire);
1374 case 'expire_minute':
1375 $return = date('i', $this->_expire);
1380 $return = date('H', $this->_expire);
1385 $return = date('d', $this->_expire);
1389 case 'expire_month':
1390 $return = date('m', $this->_expire);
1395 $return = date('Y', $this->_expire);
1400 $return = ($this->_comment_expire == 0) ? false : true;
1404 case 'cmt_close_second':
1405 if ($this->_comment_expire == 0) {
1406 $return = date('s', time() +
1407 ($_CONF['article_comment_close_days'] * 86400));
1409 $return = date('s', $this->_comment_expire);
1414 case 'cmt_close_minute':
1415 if ($this->_comment_expire == 0) {
1416 $return = date('i', time() +
1417 ($_CONF['article_comment_close_days'] * 86400));
1419 $return = date('i', $this->_comment_expire);
1424 case 'cmt_close_hour':
1425 if ($this->_comment_expire == 0) {
1426 $return = date('H', time() +
1427 ($_CONF['article_comment_close_days'] * 86400));
1429 $return = date('H', $this->_comment_expire);
1434 case 'cmt_close_day':
1435 if ($this->_comment_expire == 0) {
1436 $return = date('d', time() +
1437 ($_CONF['article_comment_close_days'] * 86400));
1439 $return = date('d', $this->_comment_expire);
1444 case 'cmt_close_month':
1445 if ($this->_comment_expire == 0) {
1446 $return = date('m', time() +
1447 ($_CONF['article_comment_close_days'] * 86400));
1449 $return = date('m', $this->_comment_expire);
1454 case 'cmt_close_year':
1455 if ($this->_comment_expire == 0) {
1456 $return = date('Y', time() +
1457 ($_CONF['article_comment_close_days'] * 86400));
1459 $return = date('Y', $this->_comment_expire);
1465 $return = $this->_title; //htmlspecialchars($this->_title);
1469 case 'meta_description':
1470 $return = $this->_meta_description;
1474 case 'meta_keywords':
1475 $return = $this->_meta_keywords;
1480 if (isset($this->_draft_flag) && ($this->_draft_flag == 1)) {
1489 $return = $this->_editText($this->_introtext);
1494 $return = $this->_editText($this->_bodytext);
1499 $varname = '_' . $item;
1501 if (isset($this->{$varname})) {
1502 $return = $this->{$varname};
1515 * Provide access to story elements. For display.
1517 * This is a peudo-property, implementing a getter for story
1518 * details as if as an associative array. Personally, I'd
1519 * rather be able to assign getters and setters to actual
1520 * properties to mask controlled access to private member
1521 * variables. But, you get what you get with PHP. So here it
1522 * is in all it's nastyness.
1524 * @param string $item Item to fetch.
1525 * @return mixed The clean and ready to use value requested.
1527 function DisplayElements($item = 'title')
1529 global $_CONF, $_TABLES;
1533 switch (strtolower($item))
1536 if ($this->_postmode == 'plaintext') {
1537 $return = nl2br($this->_introtext);
1538 } elseif ($this->_postmode == 'wikitext') {
1539 $return = COM_renderWikiText($this->_editUnescape($this->_introtext));
1541 $return = $this->_introtext;
1544 $return = PLG_replaceTags($this->_displayEscape($return));
1548 if (($this->_postmode == 'plaintext') && !(empty($this->_bodytext))) {
1549 $return = nl2br($this->_bodytext);
1550 } elseif (($this->_postmode == 'wikitext') && !(empty($this->_bodytext))) {
1551 $return = COM_renderWikiText($this->_editUnescape($this->_bodytext));
1552 } elseif (!empty($this->_bodytext)) {
1553 $return = $this->_displayEscape($this->_bodytext);
1556 $return = PLG_replaceTags($return);
1560 $return = $this->_displayEscape($this->_title);
1564 case 'meta_description':
1565 $return = $this->_meta_description;
1569 case 'meta_keywords':
1570 $return = $this->_meta_keywords;
1575 $return = strftime($_CONF['shortdate'], $this->_date);
1580 $return = strftime($_CONF['dateonly'], $this->_date);
1585 $return = COM_getUserDateTimeFormat($this->_date);
1587 $return = $return[0];
1591 $return = $this->_date;
1595 $return = COM_NumberFormat($this->_hits);
1600 $return = htmlspecialchars($this->_topic);
1605 if (empty($this->_expire)) {
1608 $return = $this->_expire;
1614 // check to see if comment_time has passed
1615 if ($this->_comment_expire != 0 && (time() > $this->_comment_expire) && $this->_commentcode == 0 ) {
1616 // if comment code is not 1, change it to 1
1617 DB_query("UPDATE {$_TABLES['stories']} SET commentcode = '1' WHERE sid = '$this->_sid'");
1620 $return = $this->_commentcode;
1625 $varname = '_' . $item;
1627 if (isset($this->{$varname})) {
1628 $return = $this->{$varname};
1638 * Set the TID to a new value.
1640 * @param $tid int ID of the topic to set
1642 function setTid($tid)
1648 * Perform a security check and return permission level.
1650 * saves the bother of accessing dozen's of vars.
1652 * @return int access level for this story
1654 function checkAccess()
1656 return SEC_hasAccess($this->_owner_id, $this->_group_id,
1657 $this->_perm_owner, $this->_perm_group,
1658 $this->_perm_members, $this->_perm_anon);
1662 // End Public Methods.
1667 * Escapes certain HTML for nicely encoded HTML.
1670 * @param string $in Text to escpae
1671 * @return string escaped string
1673 function _displayEscape($in)
1675 $return = str_replace('$', '$', $in);
1676 $return = str_replace('{', '{', $return);
1677 $return = str_replace('}', '}', $return);
1682 * Unescapes certain HTML for editing again.
1685 * @param string $in Text escaped to unescape for editing
1686 * @return string Unescaped string
1688 function _editUnescape($in)
1690 if (($this->_postmode == 'html') || ($this->_postmode == 'wikitext')) {
1691 /* Raw and code blocks need entity decoding. Other areas do not.
1692 * otherwise, annoyingly, < will end up as < on preview 1, on
1693 * preview 2 it'll be stripped by KSES. Can't beleive I missed that
1694 * in rewrite phase 1.
1698 $inlower = MBYTE_strtolower($in);
1700 $start_pos = MBYTE_strpos($inlower, '[raw]');
1701 if( $start_pos !== false ) {
1703 while( $start_pos !== false ) {
1704 /* Copy in to start to out */
1705 $out .= MBYTE_substr($buffer, 0, $start_pos);
1707 $end_pos = MBYTE_strpos($inlower, '[/raw]');
1708 if( $end_pos !== false ) {
1709 /* Encode body and append to out */
1710 $encoded = html_entity_decode(MBYTE_substr($buffer, $start_pos, $end_pos - $start_pos));
1711 $out .= $encoded . '[/raw]';
1713 $inlower = MBYTE_substr($inlower, $end_pos + 6);
1714 $buffer = MBYTE_substr($buffer, $end_pos + 6);
1715 } else { // missing [/raw]
1716 // Treat the remainder as code, but this should have been
1717 // checked prior to calling:
1718 $out .= html_entity_decode(MBYTE_substr($buffer, $start_pos + 5));
1721 $start_pos = MBYTE_strpos($inlower, '[raw]');
1723 // Append remainder:
1724 if( $buffer != '' ) {
1732 $inlower = MBYTE_strtolower($in);
1734 $start_pos = MBYTE_strpos($inlower, '[code]');
1735 if( $start_pos !== false ) {
1737 while( $start_pos !== false ) {
1738 /* Copy in to start to out */
1739 $out .= MBYTE_substr($buffer, 0, $start_pos);
1741 $end_pos = MBYTE_strpos($inlower, '[/code]');
1742 if( $end_pos !== false ) {
1743 /* Encode body and append to out */
1744 $encoded = html_entity_decode(MBYTE_substr($buffer, $start_pos, $end_pos - $start_pos));
1745 $out .= $encoded . '[/code]';
1747 $inlower = MBYTE_substr($inlower, $end_pos + 7);
1748 $buffer = MBYTE_substr($buffer, $end_pos + 7);
1749 } else { // missing [/code]
1750 // Treat the remainder as code, but this should have been
1751 // checked prior to calling:
1752 $out .= html_entity_decode(MBYTE_substr($buffer, $start_pos + 6));
1755 $start_pos = MBYTE_strpos($inlower, '[code]');
1757 // Append remainder:
1758 if( $buffer != '' ) {
1765 // advanced editor or plaintext can handle themselves...
1771 * Returns text ready for the edit fields.
1774 * @param string $in Text to prepare for editing
1775 * @return string Escaped String
1777 function _editText($in)
1781 $out = $this->replaceImages($in);
1783 if ($this->_postmode == 'plaintext') {
1784 $out = COM_undoClickableLinks($out);
1785 $out = $this->_displayEscape($out);
1786 } elseif ($this->_postmode == 'wikitext') {
1787 $out = $this->_editUnescape($in);
1790 $out = str_replace('<pre><code>', '[code]', $out);
1791 $out = str_replace('</code></pre>', '[/code]', $out);
1792 $out = str_replace('<!--raw--><span class="raw">', '[raw]', $out);
1793 $out = str_replace('</span><!--/raw-->', '[/raw]', $out);
1794 $out = $this->_editUnescape($out);
1795 $out = $this->_displayEscape(htmlspecialchars($out));
1802 * Loads the basic details of an article into the internal
1803 * variables, cleaning them up nicely.
1805 * @param $array Array of POST/GET data (by ref).
1808 function _loadBasics(&$array)
1810 /* For the really, really basic stuff, we can very easily load them
1811 * based on an array that defines how to COM_applyFilter them.
1813 foreach ($this->_postFields as $key => $value) {
1814 $vartype = $value[0];
1815 $varname = $value[1];
1817 // If we have a value
1818 if (array_key_exists($key, $array)) {
1819 // And it's alphanumeric or numeric, filter it and use it.
1820 if (($vartype == STORY_AL_ALPHANUM) || ($vartype == STORY_AL_NUMERIC)) {
1821 $this->{$varname} = COM_applyFilter($array[$key], $vartype);
1822 } elseif ($vartype == STORY_AL_ANYTHING) {
1823 $this->{$varname} = $array[$key];
1824 } elseif (($array[$key] === 'on') || ($array[$key] === 1)) {
1825 // If it's a checkbox that is on
1826 $this->{$varname} = 1;
1828 // Otherwise, it must be a checkbox that is off:
1829 $this->{$varname} = 0;
1831 } elseif (($vartype == STORY_AL_NUMERIC) || ($vartype == STORY_AL_CHECKBOX)) {
1832 // If we don't have a value, and have a numeric or text box, default to 0
1833 $this->{$varname} = 0;
1837 // SID's are a special case:
1838 $sid = COM_sanitizeID($array['sid']);
1839 if (isset($array['old_sid'])) {
1840 $oldsid = COM_sanitizeID($array['old_sid'], false);
1850 $sid = COM_makeSid();
1854 $this->_originalSid = $oldsid;
1856 /* Need to deal with the postdate and expiry date stuff */
1858 if (isset($array['publish_ampm'])) {
1859 $publish_ampm = COM_applyFilter($array['publish_ampm']);
1862 if (isset($array['publish_hour'])) {
1863 $publish_hour = COM_applyFilter($array['publish_hour'], true);
1865 $publish_minute = 0;
1866 if (isset($array['publish_minute'])) {
1867 $publish_minute = COM_applyFilter($array['publish_minute'], true);
1869 $publish_second = 0;
1870 if (isset($array['publish_second'])) {
1871 $publish_second = COM_applyFilter($array['publish_second'], true);
1874 if ($publish_ampm == 'pm') {
1875 if ($publish_hour < 12) {
1876 $publish_hour = $publish_hour + 12;
1880 if ($publish_ampm == 'am' AND $publish_hour == 12) {
1881 $publish_hour = '00';
1885 if (isset($array['publish_year'])) {
1886 $publish_year = COM_applyFilter($array['publish_year'], true);
1889 if (isset($array['publish_month'])) {
1890 $publish_month = COM_applyFilter($array['publish_month'], true);
1893 if (isset($array['publish_day'])) {
1894 $publish_day = COM_applyFilter($array['publish_day'], true);
1896 $this->_date = strtotime(
1897 "$publish_month/$publish_day/$publish_year $publish_hour:$publish_minute:$publish_second");
1901 if (isset($array['archiveflag'])) {
1902 $archiveflag = COM_applyFilter($array['archiveflag'], true);
1904 /* Override status code if no archive flag is set: */
1905 if ($archiveflag != 1) {
1906 $this->_statuscode = 0;
1909 if (array_key_exists('expire_ampm', $array)) {
1910 $expire_ampm = COM_applyFilter($array['expire_ampm']);
1911 $expire_hour = COM_applyFilter($array['expire_hour'], true);
1912 $expire_minute = COM_applyFilter($array['expire_minute'], true);
1913 $expire_second = COM_applyFilter($array['expire_second'], true);
1914 $expire_year = COM_applyFilter($array['expire_year'], true);
1915 $expire_month = COM_applyFilter($array['expire_month'], true);
1916 $expire_day = COM_applyFilter($array['expire_day'], true);
1918 if ($expire_ampm == 'pm') {
1919 if ($expire_hour < 12) {
1920 $expire_hour = $expire_hour + 12;
1924 if ($expire_ampm == 'am' AND $expire_hour == 12) {
1925 $expire_hour = '00';
1929 = strtotime("$expire_month/$expire_day/$expire_year $expire_hour:$expire_minute:$expire_second");
1931 $expiredate = time();
1934 $this->_expire = $expiredate;
1936 // comment expire time
1937 if (isset($array['cmt_close_flag'])) {
1938 $cmt_close_ampm = COM_applyFilter($array['cmt_close_ampm']);
1939 $cmt_close_hour = COM_applyFilter($array['cmt_close_hour'], true);
1940 $cmt_close_minute = COM_applyFilter($array['cmt_close_minute'], true);
1941 $cmt_close_second = COM_applyFilter($array['cmt_close_second'], true);
1942 $cmt_close_year = COM_applyFilter($array['cmt_close_year'], true);
1943 $cmt_close_month = COM_applyFilter($array['cmt_close_month'], true);
1944 $cmt_close_day = COM_applyFilter($array['cmt_close_day'], true);
1946 if ($cmt_close_ampm == 'pm') {
1947 if ($cmt_close_hour < 12) {
1948 $cmt_close_hour = $cmt_close_hour + 12;
1952 if ($cmt_close_ampm == 'am' AND $cmt_close_hour == 12) {
1953 $cmt_close_hour = '00';
1957 = strtotime("$cmt_close_month/$cmt_close_day/$cmt_close_year $cmt_close_hour:$cmt_close_minute:$cmt_close_second");
1959 $this->_comment_expire = $cmt_close_date;
1961 $this->_comment_expire = 0;
1965 /* Then grab the permissions */
1967 // Convert array values to numeric permission values
1968 if (is_array($array['perm_owner']) || is_array($array['perm_group']) ||
1969 is_array($array['perm_members']) ||
1970 is_array($array['perm_anon'])) {
1972 list($this->_perm_owner, $this->_perm_group, $this->_perm_members, $this->_perm_anon) = SEC_getPermissionValues($array['perm_owner'], $array['perm_group'], $array['perm_members'], $array['perm_anon']);
1975 $this->_perm_owner = $array['perm_owner'];
1976 $this->_perm_group = $array['perm_group'];
1977 $this->_perm_members = $array['perm_members'];
1978 $this->_perm_anon = $array['perm_anon'];
1983 * This is the importantest bit. This function must load the title, intro
1984 * and body of the article from the post array, providing all appropriate
1985 * conversions of HTML mode content into the nice safe form that geeklog
1986 * can then (simply) spit back out into the page on render. After doing a
1987 * magic tags replacement.
1989 * This DOES NOT ADDSLASHES! We do that on DB store, because we want to
1990 * keep our internal variables in "display mode", not in db mode or anything.
1992 * @param $title string posttitle, only had stripslashes if necessary
1993 * @param $intro string introtext, only had stripslashes if necessary
1994 * @param $body string bodytext, only had stripslashes if necessary
1998 function _htmlLoadStory($title, $intro, $body)
2002 // fix for bug in advanced editor
2003 if ($_CONF['advanced_editor'] && ($body == '<br' . XHTML . '>')) {
2007 $this->_title = htmlspecialchars(strip_tags(COM_checkWords($title)));
2008 $this->_introtext = COM_checkHTML(COM_checkWords($intro), 'story.edit');
2009 $this->_bodytext = COM_checkHTML(COM_checkWords($body), 'story.edit');
2014 * This is the second most importantest bit. This function must load the
2015 * title, intro and body of the article from the post array, removing all
2016 * HTML mode content into the nice safe form that geeklog can then (simply)
2017 * spit back out into the page on render. After doing a magic tags
2018 * replacement. And nl2br.
2020 * This DOES NOT ADDSLASHES! We do that on DB store, because we want to
2021 * keep our internal variables in "display mode", not in db mode or anything.
2023 * @param $title string posttitle, only had stripslashes if necessary
2024 * @param $intro string introtext, only had stripslashes if necessary
2025 * @param $body string bodytext, only had stripslashes if necessary
2029 function _plainTextLoadStory($title, $intro, $body)
2031 $this->_title = htmlspecialchars(strip_tags(COM_checkWords($title)));
2032 $this->_introtext = COM_makeClickableLinks(htmlspecialchars(COM_checkWords($intro)));
2033 $this->_bodytext = COM_makeClickableLinks(htmlspecialchars(COM_checkWords($body)));
2037 * Perform some basic cleanups of data, dealing with empty required,
2038 * defaultable fields.
2040 function _sanitizeData()
2044 if (empty($this->_hits)) {
2048 if (empty($this->_commentcount)) {
2049 $this->_comments = 0;
2052 if (empty($this->_numemails)) {
2053 $this->_numemails = 0;
2056 if (empty($this->_trackbacks)) {
2057 $this->_trackbacks = 0;
2060 if ($this->_draft_flag === 'on') {
2061 $this->_draft_flag = 1;
2062 } elseif ($this->_draft_flag != 1) {
2063 $this->_draft_flag = 0;
2066 if ($this->_show_topic_icon === 'on') {
2067 $this->_show_topic_icon = 1;
2068 } elseif ($this->_show_topic_icon != 1) {
2069 $this->_show_topic_icon = 0;
2073 // End Private Methods.
2075 /**************************************************************************/