/** * Invalidate a special key * * All we do is store the timestamp of teh invalidation * * @see http://b2evolution.net/man/widget-caching */ static function invalidate_key($key, $val) { global $Debuglog, $servertimenow, $instance_name; $lastchanged_key_name = $instance_name . '+last_changed+' . $key . '=' . $val; // Invalidate using the real time (seconds may have elapsed since $sertimenow) // Add 1 second because of the granularity that's down to the second // Worst case scenario: content will be collected/cahced several times for a whole second (as well as the first request after the end of that second) BlockCache::cacheproviderstore($lastchanged_key_name, time() + 1); $Debuglog->add('Invalidated: ' . $lastchanged_key_name . ' @ ' . (time() + 1), 'blockcache'); }
/** * Update the DB based on previously recorded changes */ function dbupdate() { global $DB, $Plugins, $servertimenow; $DB->begin(); parent::dbupdate(); // if this blog settings was modified we need to invalidate this blog's page caches // this way all existing cached page on this blog will be regenerated during next display // TODO: Ideally we want to detect if the changes are minor/irrelevant to caching and not invalidate the page cache if not necessary. // In case of doubt (and for unknown changes), it's better to invalidate. $this->set_setting('last_invalidation_timestamp', $servertimenow); if (isset($this->CollectionSettings)) { $this->CollectionSettings->dbupdate(); } $Plugins->trigger_event('AfterCollectionUpdate', $params = array('Blog' => &$this)); $DB->commit(); // Thick grained invalidation: // This collection has been modified, cached content depending on it should be invalidated: BlockCache::invalidate_key('coll_ID', $this->ID); // Fine grained invalidation: // EXPERIMENTAL: Below are more granular invalidation dates: BlockCache::invalidate_key('set_coll_ID', $this->ID); // Settings have changed BlockCache::invalidate_key('set_coll_ID', 'any'); // Settings of a have changed (for widgets tracking a change on ANY blog) // cont_coll_ID // Content has not changed }
/** * Trigger event AfterItemDelete after calling parent method. * * @todo fp> delete related stuff: comments, cats, file links... * * @return boolean true on success */ function dbdelete() { global $DB, $Plugins; // remember ID, because parent method resets it to 0 $old_ID = $this->ID; // Load the blog $Blog =& $this->get_Blog(); $DB->begin(); if ($r = parent::dbdelete()) { // re-set the ID for the Plugin event & for a deleting of the prerendered content $this->ID = $old_ID; $DB->commit(); $Plugins->trigger_event('AfterItemDelete', $params = array('Item' => &$this)); $this->ID = 0; // BLOCK CACHE INVALIDATION: BlockCache::invalidate_key('cont_coll_ID', $Blog->ID); // Content has changed if ($this->is_intro() || $this->is_featured()) { // Content of intro or featured post has changed BlockCache::invalidate_key('intro_feat_coll_ID', $Blog->ID); } // set_coll_ID // Settings have not changed } else { $DB->rollback(); } return $r; }
/** * Delete user and dependencies from database * * Includes WAY TOO MANY requests because we try to be compatible with MySQL 3.23, bleh! * * @param Log Log object where output gets added (by reference). */ function dbdelete(&$Log) { global $DB, $Plugins; if ($this->ID == 0) { debug_die('Non persistant object cannot be deleted!'); } $deltype = param('deltype', 'string', ''); // spammer $DB->begin(); if ($deltype == 'spammer') { // If we delete user as spammer we should delete private messaged of this user $this->init_relations(true); } else { // If we delete user as not spammer we keep his comments as from anonymous user // Transform registered user comments to unregistered: $ret = $DB->query('UPDATE T_comments SET comment_author_user_ID = NULL, comment_author = ' . $DB->quote($this->get('preferredname')) . ', comment_author_email = ' . $DB->quote($this->get('email')) . ', comment_author_url = ' . $DB->quote($this->get('url')) . ' WHERE comment_author_user_ID = ' . $this->ID); if (is_a($Log, 'log')) { $Log->add('Transforming user\'s comments to unregistered comments... ' . sprintf('(%d rows)', $ret), 'note'); } } // remember ID, because parent method resets it to 0 $old_ID = $this->ID; $old_email = $this->get('email'); // Delete main object: if (!parent::dbdelete()) { $DB->rollback(); $Log->add('User has not been deleted.', 'error'); return false; } if ($deltype == 'spammer') { // User was deleted as spammer, we should mark email of this user as 'Spammer' $EmailAddressCache =& get_EmailAddressCache(); $EmailAddress =& $EmailAddressCache->get_by_name($old_email, false, false); if (!$EmailAddress) { // Create new record in the T_email_address table $EmailAddress = new EmailAddress(); $EmailAddress->set('address', $old_email); } if (!empty($EmailAddress)) { // Save status of an email address $EmailAddress->set('status', 'spammer'); $EmailAddress->dbsave(); } } $DB->commit(); if (is_a($Log, 'log')) { $Log->add('Deleted User.', 'note'); } // Notify plugins: $this->ID = $old_ID; $Plugins->trigger_event('AfterUserDelete', $params = array('User' => &$this)); $this->ID = 0; // BLOCK CACHE INVALIDATION: // This User has been modified, cached content depending on it should be invalidated: BlockCache::invalidate_key('user_ID', $old_ID); return true; }
/** * Update the DB based on previously recorded changes. * * Triggers the plugin event AfterUserUpdate. */ function dbupdate() { global $DB, $Plugins, $current_User, $localtimenow; $DB->begin(); parent::dbupdate(); // Update existing fields: if (!empty($this->updated_fields)) { foreach ($this->updated_fields as $uf_ID => $uf_val) { // Note the updated_fields key values must be integers, so don't need casting or DB->quote() if (empty($uf_val)) { // Delete field: $DB->query('DELETE FROM T_users__fields WHERE uf_ID = ' . $uf_ID); } else { // Update field: $DB->query('UPDATE T_users__fields SET uf_varchar = ' . $DB->quote($uf_val) . ' WHERE uf_ID = ' . $uf_ID); } } // Reset updated fields in object: $this->updated_fields = array(); } // Add new fields: if (!empty($this->new_fields)) { $sql = 'INSERT INTO T_users__fields( uf_user_ID, uf_ufdf_ID, uf_varchar ) VALUES (' . $this->ID . ', ' . implode('), (' . $this->ID . ', ', $this->new_fields) . ' )'; $DB->query($sql, 'Insert new fields'); // Reset new fields in object: $this->new_fields = array(); } // Notify plugins: // Example: An authentication plugin could synchronize/update the password of the user. $Plugins->trigger_event('AfterUserUpdate', $params = array('User' => &$this)); $DB->commit(); // This User has been modified, cached content depending on it should be invalidated: BlockCache::invalidate_key('user_ID', $this->ID); return true; }
/** * Update the DB based on previously recorded changes */ function dbupdate() { global $DB; parent::dbupdate(); // This widget has been modified, cached content depending on it should be invalidated: BlockCache::invalidate_key('wi_ID', $this->ID); }
/** * Update the advanced user/group permissions for edited blog * * @param int Blog ID * @param string 'user' or 'group' */ function blog_update_perms($blog, $context = 'user') { global $DB; /** * @var User */ global $current_User; if ($context == 'user') { $table = 'T_coll_user_perms'; $prefix = 'bloguser_'; $ID_field = 'bloguser_user_ID'; } else { $table = 'T_coll_group_perms'; $prefix = 'bloggroup_'; $ID_field = 'bloggroup_group_ID'; } // Get affected user/group IDs: $IDs = param($context . '_IDs', '/^[0-9]+(,[0-9]+)*$/', ''); $ID_array = explode(',', $IDs); // Can the current user touch advanced admin permissions? if (!$current_User->check_perm('blog_admin', 'edit', false, $blog)) { // We have no permission to touch advanced admins! // Get the users/groups which are advanced admins $admins_ID_array = $DB->get_col("SELECT {$ID_field}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tFROM {$table}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t WHERE {$ID_field} IN (" . implode(',', $ID_array) . ")\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tAND {$prefix}blog_ID = {$blog}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tAND {$prefix}perm_admin <> 0"); // Take the admins out of the list: $ID_array = array_diff($ID_array, $admins_ID_array); } if (empty($ID_array)) { return; } // Delete old perms for this blog: $DB->query("DELETE FROM {$table}\n\t\t\t\t\t\t\t\tWHERE {$ID_field} IN (" . implode(',', $ID_array) . ")\n\t\t\t\t\t\t\t\t\t\t\tAND {$prefix}blog_ID = " . $blog); $inserted_values = array(); foreach ($ID_array as $loop_ID) { // Check new permissions for each user: // Use checkboxes $perm_post = array(); $ismember = param('blog_ismember_' . $loop_ID, 'integer', 0); $can_be_assignee = param('blog_can_be_assignee_' . $loop_ID, 'integer', 0); $perm_published = param('blog_perm_published_' . $loop_ID, 'string', ''); if (!empty($perm_published)) { $perm_post[] = 'published'; } $perm_community = param('blog_perm_community_' . $loop_ID, 'string', ''); if (!empty($perm_community)) { $perm_post[] = 'community'; } $perm_protected = param('blog_perm_protected_' . $loop_ID, 'string', ''); if (!empty($perm_protected)) { $perm_post[] = 'protected'; } $perm_private = param('blog_perm_private_' . $loop_ID, 'string', ''); if (!empty($perm_private)) { $perm_post[] = 'private'; } $perm_review = param('blog_perm_review_' . $loop_ID, 'string', ''); if (!empty($perm_review)) { $perm_post[] = 'review'; } $perm_draft = param('blog_perm_draft_' . $loop_ID, 'string', ''); if (!empty($perm_draft)) { $perm_post[] = 'draft'; } $perm_deprecated = param('blog_perm_deprecated_' . $loop_ID, 'string', ''); if (!empty($perm_deprecated)) { $perm_post[] = 'deprecated'; } $perm_redirected = param('blog_perm_redirected_' . $loop_ID, 'string', ''); if (!empty($perm_redirected)) { $perm_post[] = 'redirected'; } $perm_item_type = param('blog_perm_item_type_' . $loop_ID, 'string', 'standard'); $perm_edit = param('blog_perm_edit_' . $loop_ID, 'string', 'no'); $perm_delpost = param('blog_perm_delpost_' . $loop_ID, 'integer', 0); $perm_edit_ts = param('blog_perm_edit_ts_' . $loop_ID, 'integer', 0); $perm_delcmts = param('blog_perm_delcmts_' . $loop_ID, 'integer', 0); $perm_recycle_owncmts = param('blog_perm_recycle_owncmts_' . $loop_ID, 'integer', 0); $perm_vote_spam_comments = param('blog_perm_vote_spam_cmts_' . $loop_ID, 'integer', 0); $perm_cmtstatuses = 0; $perm_cmtstatuses += param('blog_perm_published_cmt_' . $loop_ID, 'integer', 0) ? get_status_permvalue('published') : 0; $perm_cmtstatuses += param('blog_perm_community_cmt_' . $loop_ID, 'integer', 0) ? get_status_permvalue('community') : 0; $perm_cmtstatuses += param('blog_perm_protected_cmt_' . $loop_ID, 'integer', 0) ? get_status_permvalue('protected') : 0; $perm_cmtstatuses += param('blog_perm_private_cmt_' . $loop_ID, 'integer', 0) ? get_status_permvalue('private') : 0; $perm_cmtstatuses += param('blog_perm_review_cmt_' . $loop_ID, 'integer', 0) ? get_status_permvalue('review') : 0; $perm_cmtstatuses += param('blog_perm_draft_cmt_' . $loop_ID, 'integer', 0) ? get_status_permvalue('draft') : 0; $perm_cmtstatuses += param('blog_perm_deprecated_cmt_' . $loop_ID, 'integer', 0) ? get_status_permvalue('deprecated') : 0; $perm_edit_cmt = param('blog_perm_edit_cmt_' . $loop_ID, 'string', 'no'); $perm_cats = param('blog_perm_cats_' . $loop_ID, 'integer', 0); $perm_properties = param('blog_perm_properties_' . $loop_ID, 'integer', 0); if ($current_User->check_perm('blog_admin', 'edit', false, $blog)) { // We have permission to give advanced admins perm! $perm_admin = param('blog_perm_admin_' . $loop_ID, 'integer', 0); } else { $perm_admin = 0; } $perm_media_upload = param('blog_perm_media_upload_' . $loop_ID, 'integer', 0); $perm_media_browse = param('blog_perm_media_browse_' . $loop_ID, 'integer', 0); $perm_media_change = param('blog_perm_media_change_' . $loop_ID, 'integer', 0); // Update those permissions in DB: if ($ismember || $can_be_assignee || count($perm_post) || $perm_delpost || $perm_edit_ts || $perm_delcmts || $perm_recycle_owncmts || $perm_vote_spam_comments || $perm_cmtstatuses || $perm_cats || $perm_properties || $perm_admin || $perm_media_upload || $perm_media_browse || $perm_media_change) { // There are some permissions for this user: $ismember = 1; // Must have this permission // insert new perms: $inserted_values[] = " ( {$blog}, {$loop_ID}, {$ismember}, {$can_be_assignee}, " . $DB->quote(implode(',', $perm_post)) . ",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" . $DB->quote($perm_item_type) . ", " . $DB->quote($perm_edit) . ",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{$perm_delpost}, {$perm_edit_ts}, {$perm_delcmts}, {$perm_recycle_owncmts}, {$perm_vote_spam_comments}, {$perm_cmtstatuses},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" . $DB->quote($perm_edit_cmt) . ",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{$perm_cats}, {$perm_properties}, {$perm_admin}, {$perm_media_upload},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{$perm_media_browse}, {$perm_media_change} )"; } } // Proceed with insertions: if (count($inserted_values)) { $DB->query("INSERT INTO {$table}( {$prefix}blog_ID, {$ID_field}, {$prefix}ismember, {$prefix}can_be_assignee,\n\t\t\t\t\t\t\t\t\t\t\t{$prefix}perm_poststatuses, {$prefix}perm_item_type, {$prefix}perm_edit, {$prefix}perm_delpost, {$prefix}perm_edit_ts,\n\t\t\t\t\t\t\t\t\t\t\t{$prefix}perm_delcmts, {$prefix}perm_recycle_owncmts, {$prefix}perm_vote_spam_cmts, {$prefix}perm_cmtstatuses, {$prefix}perm_edit_cmt,\n\t\t\t\t\t\t\t\t\t\t\t{$prefix}perm_cats, {$prefix}perm_properties, {$prefix}perm_admin,\n\t\t\t\t\t\t\t\t\t\t\t{$prefix}perm_media_upload, {$prefix}perm_media_browse, {$prefix}perm_media_change )\n\t\t\t\t\t\t\t\t\tVALUES " . implode(',', $inserted_values)); } // Unassign users from the items of the blog $DB->query('UPDATE T_items__item SET post_assigned_user_ID = NULL WHERE post_main_cat_ID IN ( SELECT cat_ID FROM T_categories WHERE cat_blog_ID = ' . $DB->quote($blog) . ' ) AND post_assigned_user_ID NOT IN ( SELECT bloguser_user_ID FROM T_coll_user_perms WHERE bloguser_can_be_assignee = 1 AND bloguser_blog_ID = ' . $DB->quote($blog) . ' ) AND post_assigned_user_ID NOT IN ( SELECT user_ID FROM T_users INNER JOIN T_coll_group_perms ON user_grp_ID = bloggroup_group_ID WHERE bloggroup_can_be_assignee = 1 AND bloggroup_blog_ID = ' . $DB->quote($blog) . ' )'); if ($DB->rows_affected > 0) { global $Messages; $Messages->add(sprintf('%d tasks have lost their assignee due to new permissions (this may include fixes to older inconsistencies in the DB).', $DB->rows_affected), 'warning'); } // BLOCK CACHE INVALIDATION: BlockCache::invalidate_key('set_coll_ID', $blog); // Settings have changed BlockCache::invalidate_key('set_coll_ID', 'any'); // Settings of a have changed (for widgets tracking a change on ANY blog) // cont_coll_ID // Content has not changed }
/** * Read messages from server and create posts * * @param resource $mbox created by pbm_connect() (by reference) * @param integer the number of messages to process * @param boolean TRUE if script is executed by cron * @return boolean true on success */ function pbm_process_messages(&$mbox, $limit, $cron = false) { global $Settings, $debug; global $pbm_item_files, $pbm_messages, $pbm_items, $post_cntr, $del_cntr, $is_cron_mode; // This may take a very long time if there are many messages; No execution time limit: set_max_execution_time(0); // Are we in test mode? $test_mode_on = $Settings->get('eblog_test_mode'); $post_cntr = 0; $del_cntr = 0; for ($index = 1; $index <= $limit; $index++) { // Repeat for as many messages as allowed... pbm_msg('<hr /><h3>' . sprintf(T_('Processing message %s:'), '#' . $index) . '</h3>', $cron); $html_body = ''; $strbody = ''; $hasAttachment = false; $hasRelated = false; $pbm_item_files = array(); // reset the value for each new Item // Save email to a temporary file on hard drive, otherwise BIG attachments may take a lot of RAM: if (!($tmpMIME = tempnam(sys_get_temp_dir(), 'b2evoMail'))) { pbm_msg(T_('Could not create temporary file.'), $cron); continue; } // Save the whole body of a specific message from the mailbox: imap_savebody($mbox, $tmpMIME, $index); // Create random temp directory for message parts: $tmpDirMIME = pbm_tempdir(sys_get_temp_dir(), 'b2evo_'); // Instanciate mime_parser.php library: $mimeParser = new mime_parser_class(); $mimeParser->mbox = 0; // Set to 0 for parsing a *single* RFC 2822 message $mimeParser->decode_headers = 1; // Set to 1 if it is necessary to decode message headers that may have non-ASCII characters and use other character set encodings $mimeParser->ignore_syntax_errors = 1; // ignore syntax errors in malformed messages. $mimeParser->extract_addresses = 0; $MIMEparameters = array('File' => $tmpMIME, 'SaveBody' => $tmpDirMIME, 'SkipBody' => 1); // Associative array to specify parameters for the messagedata parsing and decoding operation. $MIMEparameters = array('File' => $tmpMIME, 'SaveBody' => $tmpDirMIME, 'SkipBody' => 1); // STEP 1: Parse and decode message data and retrieve its structure: if (!$mimeParser->Decode($MIMEparameters, $decodedMIME)) { // error: pbm_msg(sprintf(T_('MIME message decoding error: %s at position %d.'), $mimeParser->error, $mimeParser->error_position), $cron); rmdir_r($tmpDirMIME); unlink($tmpMIME); continue; } else { // the specified message data was parsed successfully: pbm_msg(T_('MIME message decoding successful'), $cron); // STEP 2: Analyze (the first) parsed message to describe its contents: if (!$mimeParser->Analyze($decodedMIME[0], $parsedMIME)) { // error: pbm_msg(sprintf(T_('MIME message analyze error: %s'), $mimeParser->error), $cron); rmdir_r($tmpDirMIME); unlink($tmpMIME); continue; } // Get message $subject and $post_date from headers (by reference) if (!pbm_process_header($parsedMIME, $subject, $post_date, $cron)) { // Couldn't process message headers: rmdir_r($tmpDirMIME); unlink($tmpMIME); continue; } // TODO: handle type == "message" recursively // sam2kb> For some reason imap_qprint() demages HTML text... needs more testing // yura> I replaced imap_qprint() with quoted_printable_decode() to avoid notices about invalid quoted-printable sequence // yura> imap_qprint() and quoted_printable_decode() do empty the message text, thus they were deleted. if ($parsedMIME['Type'] == 'html') { // Mail is HTML: if ($Settings->get('eblog_html_enabled')) { // HTML posting enabled if ($debug) { // Display this info only in debug mode: pbm_msg(sprintf(T_('HTML message part saved as %s'), $parsedMIME['DataFile']), $cron); } $html_body = file_get_contents($parsedMIME['DataFile']); } foreach ($parsedMIME['Alternative'] as $alternative) { // First try to get HTML alternative (when possible) if ($alternative['Type'] == 'html' && $Settings->get('eblog_html_enabled')) { // HTML text: if ($debug) { // Display this info only in debug mode: pbm_msg(sprintf(T_('HTML alternative message part saved as %s'), $alternative['DataFile']), $cron); } $strbody = file_get_contents($alternative['DataFile']); break; // stop after first alternative } elseif ($alternative['Type'] == 'text') { // Plain text: if ($debug) { // Display this info only in debug mode: pbm_msg(sprintf(T_('Text alternative message part saved as %s'), $alternative['DataFile']), $cron); } $strbody = file_get_contents($alternative['DataFile']); break; // stop after first alternative } } } elseif ($parsedMIME['Type'] == 'text') { // Mail is plain text: if ($debug) { // Display this info only in debug mode: pbm_msg(sprintf(T_('Plain-text message part saved as %s'), $parsedMIME['DataFile']), $cron); } $strbody = file_get_contents($parsedMIME['DataFile']); } // Check for attachments: if (!empty($parsedMIME['Attachments'])) { $hasAttachment = true; foreach ($parsedMIME['Attachments'] as $file) { pbm_msg(sprintf(T_('Attachment: %s stored as %s'), $file['FileName'], $file['DataFile']), $cron); } } // Check for inline images: if (!empty($parsedMIME['Related'])) { $hasRelated = true; foreach ($parsedMIME['Related'] as $file) { pbm_msg(sprintf(T_('Related file with content ID: %s stored as %s'), $file['ContentID'], $file['DataFile']), $cron); } } if (count($mimeParser->warnings) > 0) { pbm_msg('<h4>' . sprintf(T_('%d warnings during decode:'), count($mimeParser->warnings)) . '</h4>', $cron); foreach ($mimeParser->warnings as $k => $v) { pbm_msg(sprintf(T_('Warning: %s at position %s'), $v, $k), $cron); } } } unlink($tmpMIME); if (empty($html_body)) { // Plain-text message pbm_msg(sprintf(T_('Message type: %s'), 'TEXT'), $cron); pbm_msg(sprintf(T_('Message body: %s'), '<pre style="font-size:10px">' . htmlspecialchars($strbody) . '</pre>'), $cron); // Process body. First fix different line-endings (dos, mac, unix), remove double newlines $content = str_replace(array("\r", "\n\n"), "\n", trim($strbody)); // First see if there's an <auth> tag with login and password if (($auth = pbm_get_auth_tag($content)) === false) { // No <auth> tag, let's detect legacy "username:password" on the first line $a_body = explode("\n", $content, 2); // tblue> splitting only into 2 parts allows colons in the user PW // Note: login and password cannot include '<' ! $auth = explode(':', strip_tags($a_body[0]), 2); // Drop the first line with username and password $content = $a_body[1]; } } else { // HTML message pbm_msg(sprintf(T_('Message type: %s'), 'HTML'), $cron); if (($parsed_message = pbm_prepare_html_message($html_body, $cron)) === false) { // No 'auth' tag provided, skip to the next message rmdir_r($tmpDirMIME); continue; } list($auth, $content) = $parsed_message; } // TODO: dh> should the password really get trimmed here?! $user_pass = isset($auth[1]) ? trim(remove_magic_quotes($auth[1])) : NULL; $user_login = trim(utf8_strtolower(remove_magic_quotes($auth[0]))); if (empty($user_login) || empty($user_pass)) { pbm_msg(sprintf(T_('Please add username and password in message body in format %s.'), '"<auth>username:password</auth>"'), $cron); rmdir_r($tmpDirMIME); continue; } // Authenticate user pbm_msg(T_('Authenticating user') . ': «' . $user_login . '»', $cron); $pbmUser =& pbm_validate_user_password($user_login, $user_pass); if (!$pbmUser) { pbm_msg(sprintf(T_('Authentication failed for user «%s»'), htmlspecialchars($user_login)), $cron); rmdir_r($tmpDirMIME); continue; } $pbmUser->get_Group(); // Load group if (!empty($is_cron_mode)) { // Assign current User if we are in cron mode. This is needed in order to check user permissions global $current_User; $current_User = duplicate($pbmUser); } // Activate User's locale locale_activate($pbmUser->get('locale')); pbm_msg('<b class="green">' . T_('Success') . '</b>', $cron); if ($post_categories = xmlrpc_getpostcategories($content)) { $main_cat_ID = array_shift($post_categories); $extra_cat_IDs = $post_categories; pbm_msg(T_('Extra categories') . ': ' . implode(', ', $extra_cat_IDs), $cron); } else { $main_cat_ID = $Settings->get('eblog_default_category'); $extra_cat_IDs = array(); } pbm_msg(T_('Main category ID') . ': ' . $main_cat_ID, $cron); $ChapterCache =& get_ChapterCache(); $pbmChapter =& $ChapterCache->get_by_ID($main_cat_ID, false, false); if (empty($pbmChapter)) { pbm_msg(sprintf(T_('Requested category %s does not exist!'), $main_cat_ID), $cron); rmdir_r($tmpDirMIME); continue; } $blog_ID = $pbmChapter->blog_ID; pbm_msg(T_('Blog ID') . ': ' . $blog_ID, $cron); $BlogCache =& get_BlogCache(); $pbmBlog =& $BlogCache->get_by_ID($blog_ID, false, false); if (empty($pbmBlog)) { pbm_msg(sprintf(T_('Requested blog %s does not exist!'), $blog_ID), $cron); rmdir_r($tmpDirMIME); continue; } // Check permission: pbm_msg(sprintf(T_('Checking permissions for user «%s» to post to Blog #%d'), $user_login, $blog_ID), $cron); if (!$pbmUser->check_perm('blog_post!published', 'edit', false, $blog_ID)) { pbm_msg(T_('Permission denied.'), $cron); rmdir_r($tmpDirMIME); continue; } if (($hasAttachment || $hasRelated) && !$pbmUser->check_perm('files', 'add', false, $blog_ID)) { pbm_msg(T_('You have no permission to add/upload files.'), $cron); rmdir_r($tmpDirMIME); continue; } pbm_msg('<b class="green">' . T_('Success') . '</b>', $cron); // Remove content after terminator $eblog_terminator = $Settings->get('eblog_body_terminator'); if (!empty($eblog_terminator) && ($os_terminator = utf8_strpos($content, $eblog_terminator)) !== false) { $content = utf8_substr($content, 0, $os_terminator); } $post_title = pbm_get_post_title($content, $subject); // Remove 'title' and 'category' tags $content = xmlrpc_removepostdata($content); // Remove <br> tags from string start and end // We do it here because there might be extra <br> left after deletion of <auth>, <category> and <title> tags $content = preg_replace(array('~^(\\s*<br[\\s/]*>\\s*){1,}~i', '~(\\s*<br[\\s/]*>\\s*){1,}$~i'), '', $content); if ($hasAttachment || $hasRelated) { // Handle attachments if (isset($GLOBALS['files_Module'])) { if ($mediadir = $pbmBlog->get_media_dir()) { if ($hasAttachment) { pbm_process_attachments($content, $parsedMIME['Attachments'], $mediadir, $pbmBlog->get_media_url(), $Settings->get('eblog_add_imgtag'), 'attach', $cron); } if ($hasRelated) { pbm_process_attachments($content, $parsedMIME['Related'], $mediadir, $pbmBlog->get_media_url(), true, 'related', $cron); } } else { pbm_msg(T_('Unable to access media directory. No attachments processed.'), $cron); } } else { pbm_msg(T_('Files module is disabled or missing!'), $cron); } } // CHECK and FORMAT content global $Plugins; $renderer_params = array('Blog' => &$pbmBlog, 'setting_name' => 'coll_apply_rendering'); $renderers = $Plugins->validate_renderer_list($Settings->get('eblog_renderers'), $renderer_params); pbm_msg(sprintf(T_('Applying the following text renderers: %s'), implode(', ', $renderers)), $cron); // Do some optional filtering on the content // Typically stuff that will help the content to validate // Useful for code display // Will probably be used for validation also $Plugins_admin =& get_Plugins_admin(); $params = array('object_type' => 'Item', 'object_Blog' => &$pbmBlog); $Plugins_admin->filter_contents($post_title, $content, $renderers, $params); pbm_msg(sprintf(T_('Filtered post content: %s'), '<pre style="font-size:10px">' . htmlspecialchars($content) . '</pre>'), $cron); $context = $Settings->get('eblog_html_tag_limit') ? 'commenting' : 'posting'; $post_title = check_html_sanity($post_title, $context, $pbmUser); $content = check_html_sanity($content, $context, $pbmUser); global $Messages; if ($Messages->has_errors()) { // Make it easier for user to find and correct the errors pbm_msg("\n" . sprintf(T_('Processing message: %s'), $post_title), $cron); pbm_msg($Messages->get_string(T_('Cannot post, please correct these errors:'), 'error'), $cron); $Messages->clear(); rmdir_r($tmpDirMIME); continue; } if ($test_mode_on) { // Test mode pbm_msg('<b class="green">' . T_('It looks like the post can be successfully saved in the database. However we will not do it in test mode.') . '</b>', $cron); } else { load_class('items/model/_item.class.php', 'Item'); global $pbm_items, $DB, $localtimenow; $post_status = 'published'; pbm_msg('<h4>' . sprintf(T_('Saving item "%s" in the database'), $post_title) . '</h4>', $cron); // INSERT NEW POST INTO DB: $edited_Item = new Item(); $edited_Item->set_creator_User($pbmUser); $edited_Item->set($edited_Item->lasteditor_field, $pbmUser->ID); $edited_Item->set('title', $post_title); $edited_Item->set('content', $content); $edited_Item->set('datestart', $post_date); $edited_Item->set('datemodified', date('Y-m-d H:i:s', $localtimenow)); $edited_Item->set('main_cat_ID', $main_cat_ID); $edited_Item->set('extra_cat_IDs', $extra_cat_IDs); $edited_Item->set('status', $post_status); $edited_Item->set('locale', $pbmUser->locale); $edited_Item->set('renderers', $renderers); // INSERT INTO DB: $edited_Item->dbinsert(); pbm_msg(sprintf(T_('Item created?: %s'), isset($edited_Item->ID) ? 'yes' : 'no'), $cron); // Execute or schedule notifications & pings: $edited_Item->handle_post_processing(true); if (!empty($pbm_item_files)) { // Attach files $FileCache =& get_FileCache(); $order = 1; foreach ($pbm_item_files as $filename) { pbm_msg(sprintf(T_('Saving file "%s" in the database'), $filename), $cron); $pbmFile =& $FileCache->get_by_root_and_path('collection', $pbmBlog->ID, $filename); $pbmFile->meta = 'notfound'; // Save time and don't try to load meta from DB, it's not there anyway $pbmFile->dbsave(); pbm_msg(sprintf(T_('File saved?: %s'), isset($pbmFile->ID) ? 'yes' : 'no'), $cron); pbm_msg(sprintf(T_('Attaching file "%s" to the post'), $filename), $cron); // Let's make the link! $pbmLink = new Link(); $pbmLink->set('itm_ID', $edited_Item->ID); $pbmLink->set('file_ID', $pbmFile->ID); $pbmLink->set('position', 'aftermore'); $pbmLink->set('order', $order++); $pbmLink->dbinsert(); pbm_msg(sprintf(T_('File attached?: %s'), isset($pbmLink->ID) ? 'yes' : 'no'), $cron); } // Invalidate blog's media BlockCache BlockCache::invalidate_key('media_coll_ID', $edited_Item->get_blog_ID()); } // Save posted items sorted by author user for reports $pbm_items['user_' . $pbmUser->ID][] = $edited_Item; ++$post_cntr; } pbm_msg(T_('Message posting successful'), $cron); // Delete temporary directory rmdir_r($tmpDirMIME); if (!$test_mode_on && $Settings->get('eblog_delete_emails')) { pbm_msg(sprintf(T_('Marking message for deletion from inbox: %s'), $index), $cron); imap_delete($mbox, $index); ++$del_cntr; } } // Expunge messages marked for deletion imap_expunge($mbox); return true; }
if ($edited_Item->dbinsert()) { // echo '<br>file meta: '.$l_File->meta; if ($l_File->meta == 'notfound') { // That file has no meta data yet, create it now! $l_File->dbsave(); } // Let's make the link! $edited_Link = new Link(); $edited_Link->set('itm_ID', $edited_Item->ID); $edited_Link->set('file_ID', $l_File->ID); $edited_Link->set('position', 'teaser'); $edited_Link->set('order', 1); $edited_Link->dbinsert(); $DB->commit(); // Invalidate blog's media BlockCache BlockCache::invalidate_key('media_coll_ID', $edited_Item->get_blog_ID()); $Messages->add(sprintf(T_('«%s» has been posted.'), $l_File->dget('name')), 'success'); $fileNum++; } else { $DB->rollback(); $Messages->add(sprintf(T_('«%s» couldn\'t be posted.'), $l_File->dget('name')), 'error'); } } // Note: we redirect without restoring filter. This should allow to see the new files. // &filter=restore header_redirect($dispatcher . '?ctrl=items&blog=' . $blog); // Will save $Messages // Note: we should have EXITED here. In case we don't (error, or sth...) // Reset stuff so it doesn't interfere with upcomming display unset($edited_Item); unset($edited_Link);
/** * Update the DB based on previously recorded changes * * @param boolean do we want to auto track the mod date? * @param boolean Update slug? - We want to PREVENT updating slug when item dbupdate is called, * because of the item canonical url title was changed on the slugs edit form, so slug update is already done. * If slug update wasn't done already, then this param has to be true. * @param boolean Update excerpt? - We want to PREVENT updating exerpts when the item content wasn't changed ( e.g. only item canonical slug was changed ) * @return boolean true on success */ function dbupdate($auto_track_modification = true, $update_slug = true, $update_excerpt = true) { global $DB, $Plugins; $DB->begin('SERIALIZABLE'); if ($this->status != 'draft') { // The post is getting published in some form, set the publish date so it doesn't get auto updated in the future: $this->set('dateset', 1); } // save Item settings if (isset($this->ItemSettings)) { $this->ItemSettings->dbupdate(); } // validate url title / slug if ($update_slug) { // item canonical slug wasn't updated outside from this call, if it was changed or it wasn't set yet, we must update the slugs if (empty($this->urltitle) || isset($this->dbchanges['post_urltitle'])) { // Url title has changed or is empty, we do need to update the slug: $edited_slugs = $this->update_slugs(); } } $this->update_renderers_from_Plugins(); if ($update_excerpt) { // We want to update the excerpt: $this->update_excerpt(); } // TODO: dh> allow a plugin to cancel update here (by returning false)? $Plugins->trigger_event('PrependItemUpdateTransact', $params = array('Item' => &$this)); $dbchanges = $this->dbchanges; // we'll save this for passing it to the plugin hook // pre_dump($this->dbchanges); // fp> note that dbchanges isn't actually 100% accurate. At this time it does include variables that actually haven't changed. if (isset($this->dbchanges['post_status']) || isset($this->dbchanges['post_title']) || isset($this->dbchanges['post_content'])) { // One of the fields we track in the revision history has changed: // Save the "current" (soon to be "old") data as a version before overwriting it in parent::dbupdate: // fp> TODO: actually, only the fields that have been changed should be copied to the version, the other should be left as NULL // Get next version ID $iver_SQL = new SQL(); $iver_SQL->SELECT('MAX( iver_ID )'); $iver_SQL->FROM('T_items__version'); $iver_SQL->WHERE('iver_itm_ID = ' . $this->ID); $iver_ID = (int) $DB->get_var($iver_SQL->get()) + 1; $sql = 'INSERT INTO T_items__version( iver_ID, iver_itm_ID, iver_edit_user_ID, iver_edit_datetime, iver_status, iver_title, iver_content ) SELECT "' . $iver_ID . '" AS iver_ID, post_ID, post_lastedit_user_ID, post_datemodified, post_status, post_title, post_content FROM T_items__item WHERE post_ID = ' . $this->ID; $DB->query($sql, 'Save a version of the Item'); } if ($auto_track_modification && count($dbchanges) > 0 && !isset($dbchanges['last_touched_ts'])) { // Update last_touched_ts field only if it wasn't updated yet and the datemodified will be updated for sure. global $localtimenow; $this->set_param('last_touched_ts', 'date', date('Y-m-d H:i:s', $localtimenow)); } if ($result = parent::dbupdate($auto_track_modification) !== false) { // We could update the item object: // Let's handle the extracats: $result = $this->insert_update_extracats('update'); if ($result) { // Let's handle the tags: $this->insert_update_tags('update'); } // Let's handle the slugs: // TODO: dh> $result handling here feels wrong: when it's true already, it should not become false (add "|| $result"?) // asimo>dh The result handling is in a transaction. If somehow the new slug creation fails, then the item insertion should rollback either if ($result && !empty($edited_slugs)) { // if we have new created $edited_slugs, we have to insert it into the database: foreach ($edited_slugs as $edited_Slug) { if ($edited_Slug->ID == 0) { // Insert only new created slugs $edited_Slug->dbinsert(); } } if (isset($edited_slugs[0]) && $edited_slugs[0]->ID > 0) { // Make first slug from list as main slug for this item $this->set('canonical_slug_ID', $edited_slugs[0]->ID); $this->set('urltitle', $edited_slugs[0]->get('title')); $result = parent::dbupdate(); } } } if ($result === false) { // Update failed $DB->rollback(); } else { // Update was successful $this->delete_prerendered_content(); $DB->commit(); $Plugins->trigger_event('AfterItemUpdate', $params = array('Item' => &$this, 'dbchanges' => $dbchanges)); } // Load the blog we're in: $Blog =& $this->get_Blog(); // Thick grained invalidation: // This collection has been modified, cached content depending on it should be invalidated: BlockCache::invalidate_key('coll_ID', $Blog->ID); // Fine grained invalidation: // EXPERIMENTAL: Below are more granular invalidation dates: // set_coll_ID // Settings have not changed BlockCache::invalidate_key('cont_coll_ID', $Blog->ID); // Content has changed return $result; }
/** * Add new link to owner Item * * @param integer file ID * @param integer link position ( 'teaser', 'teaserperm', 'teaserlink', 'aftermore', 'inline', 'fallback' ) * @param int order of the link * @param boolean true to update owner last touched timestamp after link was created, false otherwise * @return integer|boolean Link ID on success, false otherwise */ function add_link($file_ID, $position = NULL, $order = 1, $update_owner = true) { if (is_null($position)) { // Use default link position $position = $this->get_default_position($file_ID); } $edited_Link = new Link(); $edited_Link->set('itm_ID', $this->Item->ID); $edited_Link->set('file_ID', $file_ID); $edited_Link->set('position', $position); $edited_Link->set('order', $order); if ($edited_Link->dbinsert()) { // New link was added to the item, invalidate blog's media BlockCache BlockCache::invalidate_key('media_coll_ID', $this->Item->get_blog_ID()); $FileCache =& get_FileCache(); $File = $FileCache->get_by_ID($file_ID, false, false); $file_name = empty($File) ? '' : $File->get_name(); syslog_insert(sprintf('File %s was linked to %s with ID=%s', '<b>' . $file_name . '</b>', $this->type, $this->link_Object->ID), 'info', 'file', $file_ID); if ($update_owner) { // Update last touched date of the Item $this->update_last_touched_date(); } // Reset the Links $this->Links = NULL; $this->load_Links(); return $edited_Link->ID; } return false; }