No news today.'; var $isFrontPage = false; var $got_content = array(); function introspect(&$propbag) { global $serendipity; $propbag->add('name', PLUGIN_EVENT_NEWSBOX_TITLE); $propbag->add('description', PLUGIN_EVENT_NEWSBOX_DESC); $propbag->add('copyright', 'GPL'); $propbag->add('groups', array('FRONTEND_VIEWS', 'FRONTEND_FEATURES')); $propbag->add('stackable', true); $propbag->add('author', 'Jude Anthony'); $propbag->add('version', '0.6.1'); $propbag->add('requirements', array( 'serendipity' => '1.0-alpha1', 'smarty' => '2.6.7', 'php' => '4.1.0' )); // Rejected event hooks // 'frontend_fetchentry' => true, // 'backend_publish' => true, // 'backend_save' => true, // 'backend_display' => true, // 'backend_import_entry' => true, // 'entry_display' => true, // 'frontend_entryproperties' => true, // 'backend_sidebar_entries_event_display_buildcache' => true, // 'backend_sidebar_entries' => true, // 'backend_cache_entries' => true, // 'backend_cache_purge' => true, // 'backend_plugins_new_instance' => true, // 'frontend_entryproperties_query' => true, // 'frontend_entries_rss' => true $propbag->add('event_hooks', array( 'genpage' => true, 'frontend_fetchentries' => true, 'css' => true, 'frontend_header' => true, 'entries_header' => true, 'entries_footer' => true, 'frontend_footer' => true, 'newsbox:get_containers' => true, 'newsbox:get_content' => true, )); $propbag->add('configuration', array('title', 'content_type', 'news_cats', 'max_entries', 'placement')); } function introspect_config_item($name, &$propbag) { switch($name) { case 'title': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_NEWSBOX_TITLEFIELD); $propbag->add('description', PLUGIN_EVENT_NEWSBOX_TITLEFIELD_DESC); $propbag->add('default', 'News'); break; case 'news_cats': $propbag->add('type', 'content'); $propbag->add('default', $this->makeCategorySelector()); // Name and description aren't used for type 'content'. break; case 'max_entries': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_NEWSBOX_NUMENTRIES); $propbag->add('description', PLUGIN_EVENT_NEWSBOX_NUMENTRIES_DESC); $propbag->add('default', '5'); break; case 'placement': $propbag->add('type', 'select'); $propbag->add('name', PLUGIN_EVENT_NEWSBOX_PLACEMENT); $propbag->add('description', PLUGIN_EVENT_NEWSBOX_PLACEMENT_DESC); $select = array( 'page header' => PLUGIN_EVENT_NEWSBOX_PLACEMENT_PAGE_TOP, 'entry top' => PLUGIN_EVENT_NEWSBOX_PLACEMENT_ENTRY_TOP, 'entry bottom' => PLUGIN_EVENT_NEWSBOX_PLACEMENT_ENTRY_BOTTOM, 'page footer' => PLUGIN_EVENT_NEWSBOX_PLACEMENT_PAGE_BOTTOM ); // Get all the newsbox containers (except me) $containers = array(); serendipity_plugin_api::hook_event('newsbox:get_containers', $containers, array('id' => $this->instance)); foreach( $containers as $container ) { $cid = $container['id']; $cname = $container['name']; $select[$cid] = $cname; } $select['hidden'] = PLUGIN_EVENT_NEWSBOX_PLACEMENT_HIDDEN; $propbag->add('select_values', $select); $propbag->add('default', 'entry top'); break; case 'content_type': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_NEWSBOX_CONTENTTYPE); $propbag->add('description', PLUGIN_EVENT_NEWSBOX_CONTENTTYPE_DESC); $radio = array(); $radio['desc'] = array('Newsboxes', 'Categories'); $radio['value'] = array('newsboxes', 'categories'); $propbag->add('radio', $radio); $propbag->add('radio_per_row', 1); $propbag->add('default', 'categories'); break; } return true; } // Ah, a neat trick. See, when someone hits the "save" button for // configuration, it returns us to the configuration screen. In that case, // s9y is going to call introspection again. This gives us a chance to // check variables and change settings, even though the "configuration // saved" message has already been printed. A bit deceptive, but // effective. // // Strangely, while serendipity_event_includeentries can use the same // string for the POST variable and the configuration variable name, I // get an error about htmlspecialchars() at admin/plugins.inc.php line // 356: an array is passed to parameter 1. It's easily fixed by making // the two strings slightly different. function &makeCategorySelector() { global $serendipity; $selector = ''; // We start in our own, column-spanning row $selector .= ''; $selector .= ''; $selector .= ''. PLUGIN_EVENT_NEWSBOX_NEWSCATS .'
'; $selector .= ''; $selector .= PLUGIN_EVENT_NEWSBOX_NEWSCATS_DESC . '
'; $selector .= ''; $selector .= '' . CATEGORIES . '
'; // $selected_cats is used to set up the selections; // 'news_cats' holds the value when it's set. if (is_array($serendipity['POST']['plugin']['newscats'])) { // Someone has already selected categories $selected_cats = array(); foreach ($serendipity['POST']['plugin']['newscats'] AS $idx => $id) { $selected_cats[$id] = true; } $catlist = implode(',', array_keys($selected_cats)); $this->set_config('news_cats', $catlist); } else { // Form is just being displayed; get previously selected categories // Must use default value! 'false' is very fast. $catlist = $this->get_config('news_cats', false); if (!$catlist) { $selected_cats = array(); } else { $cat_ids = explode(',', $catlist); foreach ($cat_ids AS $id) { $selected_cats[$id] = true; } } } $selector .= ''; $selector .= ''; return $selector; } function generate_content(&$title) { $title = 'Newsbox: ' . $this->get_config('title'); } function event_hook($event, &$bag, &$eventData, $addlData = null) { global $serendipity; $hooks = &$bag->get('event_hooks'); // I'm not really certain why this is here (since we only get called // for the events we hooked), but Garvin uses it, so it must be // important. if (isset($hooks[$event])) { $content_type = $this->get_config('content_type', 'categories'); $placement = $this->get_config('placement', 'entry top'); $news_cats = $this->get_config('news_cats'); switch($event) { case 'genpage': // Is this the frontpage? (Garvin's algorythm; works // for all cases except index.html, on my server) if ($addlData['startpage']) { $this->isFrontPage = true; } else { $this->isFrontPage = false; return true; } // Get this newsbox's entries // // Hidden newsboxes don't need to waste time doing this. if ($placement == 'hidden') { return true; } // If I don't contain categories, I don't generate HTML from categories. if ($content_type != 'categories') { return true; } // If this newsbox is empty, we'd get an SQL error. if (empty($news_cats)) { $this->html = '
No ' . $this->get_config('title', PLUGIN_EVENT_NEWSBOX_DEFAULT_TITLE) . ' today.
'; return true; } // Smarty isn't initialized yet. serendipity_smarty_init(); // Create the SQL to fetch my entries $sql = "\n" . ' e.id IN ' . "\n" . '(SELECT entryid FROM ' . $serendipity['dbPrefix'] . 'entrycat' . "\n" . ' WHERE categoryid IN (' . $news_cats . ')' . "\n" . ')'; // We don't want our exclusion logic to execute on *this* // fetchEntries call! $serendipity['newsbox'] = 'no_exclude'; //--JAM: yeah, it looks like a bug to me. I wonder what else gets accidentally overwritten? $oldLimit = $serendipity['fetchLimit']; // We want the number of entries configurable $max_entries = $this->get_config('max_entries', 5); if (!is_numeric($max_entries)) { $max_entries = 5; } $entries = serendipity_fetchEntries(null, true, $max_entries, false, false, 'timestamp DESC', $sql); $serendipity['fetchLimit'] = $oldLimit; unset($serendipity['newsbox']); // Process our input data with new printEntries: // $entries, no extended, no preview, block NEWSBOX, no smarty fetch, no hooks, footer serendipity_printEntries($entries, 0, false, 'NEWSBOX', false, false, false); $newsbox_data = array(); $newsbox_data['title'] = $this->get_config('title', PLUGIN_EVENT_NEWSBOX_DEFAULT_TITLE); $newsbox_data['cats'] = explode(',', $news_cats); $newsbox_data['content_type'] = $content_type; $newsbox_data['isContainer'] = ($content_type != 'categories'); $newsbox_data['multicat_action'] = $serendipity['baseURL'] . $serendipity['indexFile']; $serendipity['smarty']->assign('newsbox_data', $newsbox_data); $nb = serendipity_getTemplateFile('newsbox.tpl'); if ($nb && $nb != 'newsbox.tpl') { // Template is obviously newsbox-aware $this->html = serendipity_smarty_fetch('NEWSBOX', 'newsbox.tpl', false); } else { // Set the newsbox variable for the template, in case it's newsbox-aware $serendipity['smarty']->assign('isNewsbox', true); // Modify the footer link $more = '
'; foreach (explode(',', $news_cats) as $cat) { $more .= ''; } $more .= '
'; $serendipity['smarty']->assign('footer_info', $more); // Get the HTML $serendipity['skip_smarty_hooks'] = true; // Don't call entries_header from the template! $this->html = serendipity_smarty_fetch('NEWSBOX', 'entries.tpl', false); unset($serendipity['skip_smarty_hooks']); // Don't leave the newsbox variable set for the regular fetch $serendipity['smarty']->clear_assign('isNewsbox'); // Check if the template supports newsboxes // Matches class = "whatever_newsbox_whatever", taking care to allow // whitespace where legal and match quote types (I don't think you // can use a quote in a class name, but hey...) if (preg_match('/class\\s*=\\s*(["\'])[^\\1]*newsbox/', $this->html) == 0) { // Add the div; give it the default class "newsbox" and a title $title = $this->get_config('title'); $this->html = "\n

$title

\n" . $this->html . "\n
\n"; } } // Done processing the newsbox break; case 'frontend_fetchentries': // Only on the frontpage if (!$this->isFrontPage) { return true; } // Don't even call this hook if we're already in this hook if (isset($serendipity['newsbox']) && $serendipity['newsbox'] == 'no_exclude') { return true; } // If we don't contain categories, we don't want to // exclude categories accidentally if ($content_type != 'categories') { return true; } // Don't restrict the calendar, etc; only the main listing $source = $addlData['source']; if ($source != 'entries') { return true; } // No joins required! // $joins = array(); $conds = array(); if (isset($news_cats) && !empty($news_cats)) { // Exclude entries in the newbox $conds[] = ' (e.id NOT IN (SELECT entryid from ' . $serendipity['dbPrefix'] . 'entrycat' . ' WHERE categoryid IN (' . $news_cats . ')))'; } if (count($conds) > 0) { $cond = implode(' AND ', $conds); if (empty($eventData['and'])) { $eventData['and'] = " WHERE $cond "; } else { $eventData['and'] .= " AND $cond "; } } return true; break; case 'css': // Can't tell if this is the fronpage or not. Better // generate the CSS, just in case. if (strpos($eventData, 'newsbox') !== false) { // This CSS is already newsbox-aware. return true; } $eventData = $eventData . ' .newsbox { border: 1px solid black; padding: 2px; margin-bottom: 4px; } .newsbox_title { font-style: italic; } .newsbox_container { border: 1px solid black; padding: 2px; margin-bottom: 4px; text-align: center; margin: 2px auto; } .newsbox_container .newsbox { border: none; width: 48%; float: left; text-align: left; margin: 2px; display: inline; } '; return true; break; // Placement cases: if configured placement equals the hook, // print my HTML. Hidden takes care of itself: there is no // matching hook, so it never gets printed. Contained // newsboxes will also never match a hook; their HTML is // requested by the containing newsbox. case 'frontend_header': if ($this->isFrontPage && $placement == 'page header') { echo $this->getHTML(); } return true; break; case 'entries_header': if ($this->isFrontPage && $placement == 'entry top') { echo $this->getHTML(); } return true; break; case 'entries_footer': if ($this->isFrontPage && $placement == 'entry bottom') { // Entry footer markup is ugly. Close the div. echo ''; echo $this->getHTML(); // Reopen the div we closed to avoid bad markup. echo '
'; } return true; break; case 'frontend_footer': if ($this->isFrontPage && $placement == 'page footer') { echo $this->getHTML(); } return true; break; case 'newsbox:get_content': // Custom hook to retrieve data for contained newsboxes. // If the container asking for content is my container, // add my content to the data array. if ($addlData['id'] == $placement) { // 1. Avoid recursion. // 2. Go to step 1. if (!$this->got_content[$addlData['id']]) { $this->got_content[$addlData['id']] = true; $eventData[] = $this->getHTML(); } return true; } break; case 'newsbox:get_containers': // Custom hook to find newsbox containers. If I'm a newsbox // container, return my instance ID. if (($addlData['id'] != $this->instance) && ($this->get_config('content_type', 'categories') == 'newsboxes')) { $eventData[] = array( 'id' => $this->instance, 'name' => 'Newsbox: ' . $this->get_config('title', PLUGIN_EVENT_NEWSBOX_DEFAULT_TITLE)); return true; } break; default: return false; break; } } else { return false; } return true; } // Convenience function to avoid duplicating code in every possible // placement of output. function getHTML() { global $serendipity; $content_type = $this->get_config('content_type', 'categories'); if ($content_type == 'newsboxes') { // Wrap content from my contained newsboxes. $contents = array(); serendipity_plugin_api::hook_event('newsbox:get_content', $contents, array('id' => $this->instance)); $nb = serendipity_getTemplateFile('newsbox.tpl'); if ($nb && $nb != 'newsbox.tpl') { // The template should be able to handle this $serendipity['smarty']->assign('NEWSBOX', $contents); $newsbox_data = array(); $newsbox_data['title'] = $this->get_config('title', PLUGIN_EVENT_NEWSBOX_DEFAULT_TITLE); $newsbox_data['cats'] = ''; $newsbox_data['content_type'] = $content_type; $newsbox_data['isContainer'] = true; $newsbox_data['multicat_action'] = ''; $serendipity['smarty']->assign('newsbox_data', $newsbox_data); $this->html = serendipity_smarty_fetch('NEWSBOX', 'newsbox.tpl', false); } else { $this->html = "\n

" . $this->get_config('title') . "

\n"; foreach ($contents as $box) { $this->html .= $box . "\n"; } $this->html .= '

' . "\n"; $this->html .= "\n
\n"; } } return $this->html; } } /* vim: set sts=4 ts=4 expandtab : */ ?>