Slashblog
[ class tree: Slashblog ] [ index: Slashblog ] [ all elements ]

Source for file blog.class.php

Documentation is available at blog.class.php

  1. <?php
  2. /**
  3.  * Contains the class Blog, which represents the main interface, connected to smaller components such as settings and blog.
  4.  * 
  5.  * Depends on datacontainer.class.php, dataitem.class.php and post.class.php
  6.  * 
  7.  * @author Daniel Nordstrom <dnordstrom@mankindorganization.com>
  8.  * @link www.mrnordstrom.com
  9.  */
  10.  
  11.  
  12. /**
  13.  * Holds and handles settings and posts. Also takes care of saving data to file.
  14.  */
  15. class Blog {
  16.     
  17.     private $settings_blog// DataContainer with blog settings.
  18.     private $posts// Array of DataContainers, each containing a post.
  19.     private $current_post// Used in post iteration. ID of post we're currently displaying.
  20.     private $current_page// Current page being displayed.
  21.     private $logged_in// Are we logged in as owner?
  22.     private $single// Are we viewing a single post?
  23.     private $pages// Our admin pages and their files.
  24.     private $authpages// Pages that needs authentification before access.
  25.     private $current_page_include// Filename of page we're viewing.
  26.     private $current_page_name// Name of the page we're viewing.
  27.     
  28.     /**
  29.      * Loads dynamic settings, only meant to be used internally and by subclasses.
  30.      * 
  31.      * @param string $file File to load settings from.
  32.      * @param DataContainer &$container Container to dump data in.
  33.      */
  34.     private function LoadSettings($file&$container)
  35.     {
  36.         $container new DataContainer();
  37.         
  38.         $settings_file fopen(PATH_BASE PATH_DATA $file'r');
  39.         
  40.         while(!feof($settings_file))
  41.         {
  42.             // Read key and value from file, and trim away EOL garbage.
  43.             $key trim(fgets($settings_file))
  44.             $value trim(fgets($settings_file));
  45.             
  46.             $container->AddItem($key$value)// Adds item to specified data container
  47.         }
  48.     }
  49.     
  50.     /**
  51.      * Loads posts into data container.
  52.      * 
  53.      * @param integer $number Optional, how many posts to load.
  54.      * @param integer $id Optional, if we choose to load only a single file, we here specify if we want to load a certain ID.
  55.      */
  56.     private function LoadPostsFromFlatfile($number 0$id = -1)
  57.     {
  58.         unset($this->posts)// Remove any loaded posts and...
  59.         $this->posts array()// ...initialize a new array.
  60.         
  61.         if(!($posts_file fopen(PATH_BASE PATH_DATA FILE_BLOG_POSTS'r'))) // Throw exception if file we can't load file.
  62.             throw new Exception("Invalid file. Cannot load posts."3);
  63.         
  64.         // Load all, or a number of, the posts stored in FILE_POSTS.
  65.         $post_counter 0;
  66.         while(!feof($posts_file))
  67.         {
  68.             $post new Post();
  69.             $done 0// Marks when we're done and ready to add post. Without this, we're gonna have trouble with empty lines at the end of the file.
  70.  
  71.             while(!feof($posts_file))
  72.             {
  73.                 // Read key and value from file, and trim away EOL garbage.
  74.                 $key strtoupper(trim(fgets($posts_file)))
  75.                 
  76.                 if(strcmp($key""== 0// If line was empty, we don't want todo anything.
  77.                     continue;
  78.                 elseif(strcmp($key"STARTPOST"== 0// If line is a start post marker, we skip to the next iteration to add the fields.
  79.                 {
  80.                     $done 1// Done, now add post.
  81.                     continue;
  82.                 }
  83.                 elseif(strcmp($key"ENDPOST"== 0// If line is an end of post marker, we stop the iteration and jump to the next post.
  84.                     break;
  85.                 
  86.                 $value trim(fgets($posts_file));
  87.                     
  88.                 $post->AddItem($key$value)// Adds item to post data container
  89.             }
  90.             
  91.             if($id >= && $done && strcmp($post->GetValue("ID")$id== 0// If we want to load just a single specific post, we check if this is the one.
  92.             {
  93.                 array_push($this->posts$post);
  94.                 unset($post);
  95.                 break// We break, since we don't want to add any more posts.    
  96.             }
  97.             elseif($id == -&& $done)
  98.             {
  99.                 array_push($this->posts$post);
  100.                 unset($post);
  101.                 $done 0;    
  102.                 $post_counter++;
  103.             }
  104.  
  105.             if($number && $post_counter >= $number// Break if user don't want to load any more posts.
  106.                 break;    
  107.         }
  108.     }
  109.     
  110.     /**
  111.      * Loads posts from XML file into data container.
  112.      *
  113.      * @param integer $number Optional, how many posts to load.
  114.      * @param integer $id Optional, if we choose to load only a single file, we here specify if we want to load a certain ID.
  115.      */
  116.     private function LoadPostsFromXML($number 0$id = -1)
  117.     {
  118.         unset($this->posts)// Reset the posts variable, to kick any old posts out of there.
  119.         $this->posts array();
  120.         
  121.         if(!($posts_file new SimpleXMLElement(PATH_BASE PATH_DATA FILE_BLOG_POSTS_XMLNULLtrue))) // Throw exception if file we can't load file.
  122.             throw new Exception("Invalid file. Cannot load posts."3);
  123.         
  124.         // Load all, or a number of, the posts stored in FILE_POSTS.
  125.         $post_counter 0;
  126.         
  127.         foreach($posts_file->children(as $child// For every post...
  128.         {
  129.             unset($post);
  130.             $post new Post();
  131.             
  132.             foreach($child->children(as $subchild// ...add all it's items,
  133.             {
  134.                 $key strtoupper($subchild->GetName());
  135.                 $value $subchild;
  136.                 
  137.                 if($key == "CONTENT"// If this is the post content, we want to decode HTML entities and convert back any HTML content.
  138.                 {
  139.                     $value html_entity_decode($value);
  140.                     $value str_replace('[''<'$value);
  141.                     $value str_replace(']''>'$value);
  142.                 }
  143.                 $post->AddItem($key$value)
  144.             }
  145.                     
  146.             if($id >= && strcmp($post->GetValue("ID")$id== 0// If we want to load just a single specific post, we check if this is the one.
  147.             {
  148.                 array_push($this->posts$post);
  149.                 break// We break, since we don't want to add any more posts.    
  150.             }
  151.             elseif($id == -1// Otherwise we just...
  152.             {
  153.                 array_push($this->posts$post)// ...push em' onto the array.
  154.                 $post_counter++// And boom, we've added a post.
  155.             }
  156.  
  157.             if($number && ($post_counter >= $number)) // Break if user don't want to load any more posts.
  158.                 break;            
  159.         }
  160.     }
  161.  
  162.     /**
  163.      * Loads a single post from source defined in config.php
  164.      * 
  165.      * @param integer $id ID of post to load.
  166.      */
  167.     public function LoadSingle($id)
  168.     {
  169.         switch(DATA_STORAGE)
  170.         {
  171.             case "XML":
  172.                 $this->LoadPostsFromXML(1$id);
  173.                 break;
  174.             case "FLATFILE":
  175.                 $this->LoadPostsFromFlatfile(1$id);
  176.                 break;
  177.         }
  178.     }
  179.  
  180.     /**
  181.      * Loads post depending on what source we specify in config.php.
  182.      */
  183.     public function LoadPosts($number 0)
  184.     {
  185.         switch(DATA_STORAGE)
  186.         {
  187.             case "XML":
  188.                 $this->LoadPostsFromXML($number);
  189.                 break;
  190.             case "FLATFILE":
  191.                 $this->LoadPostsFromFlatfile($number);
  192.                 break;
  193.         }
  194.     }
  195.  
  196.     /**
  197.      * Publishes a new post to the storage file specified in config.php. Differs from the AddPost function in the way that it actually loads and saves posts to and from file, thus also rebuilding the ID structure.
  198.      * 
  199.      * @param Post &$post Reference to object containing the post we're adding.
  200.      */
  201.     public function PublishPost(&$post)
  202.     {
  203.         $this->LoadPosts();
  204.         $this->AddPost($post);
  205.         $this->SavePosts();
  206.     }
  207.     
  208.     /**
  209.      * Inserts a new post at the beginning of the array.
  210.      * 
  211.      * @param Post &$post Reference to object containing the posts we're adding.
  212.      */
  213.     private function AddPost(&$post)
  214.     {
  215.         // We need to reverse the array in order to add the new post to the right side.
  216.         $this->posts array_reverse($this->posts);
  217.         if(!array_push($this->posts$post)) // Throw exception if we fail to push and...
  218.             throw new Exception("Could not add new post. array_push failed."6);
  219.         $this->posts array_reverse($this->posts);
  220.         
  221.         return 1// ...return 1 for success otherwise.
  222.     }
  223.     
  224.     /**
  225.      * Sets a new value of specified field of specified post. If no such field yet exists, it creates a new field. Does not, however, save the posts to file, so we need to do that ourselves.
  226.      * 
  227.      * @param integer $id ID of post to edit.
  228.      * @param string $field Key of field/DataItem to edit.
  229.      * @param string $value New value of field/DataItem.
  230.      */
  231.     private function SetPostField($id$field$value)
  232.     {
  233.         $key $this->GetKeyByID($id);
  234.             
  235.         if(!($this->posts[$key]->EditItem($field$value))) // If we can't edit it, it doesn't exist, so we...
  236.             $this->posts[$key]->AddItem($field$value)// ...add it as a new item.
  237.     }
  238.     
  239.     /**
  240.      * Writes currently loaded posts to file using the storage method specified in config.php
  241.      */
  242.     public function SavePosts()
  243.     {
  244.         switch(strtoupper(DATA_STORAGE))
  245.         {
  246.             case "XML":
  247.                 if(!($posts_file fopen(PATH_BASE PATH_DATA FILE_BLOG_POSTS_XML'w'))) // Throw exception if file we can't load file.
  248.                     throw new Exception("Cannot open XML file for writing."3);
  249.                     
  250.                 $id count($this->posts1// We set the first ID to the number of posts there are (minus one since the first post has ID 0), since we want the newest post to have the highest ID.
  251.                 fwrite($posts_file'<?xml version="1.0"?>' PHP_EOL '<POSTS>' PHP_EOL);
  252.                 foreach($this->posts as $post)
  253.                 {
  254.                     $post->DeleteItem('ID')// Remove old ID to allow for a new, clean and linear structure.
  255.                     $post->AddItem('ID'$id)// Give the post a new ID.
  256.                     fwrite($posts_file$post->AsXML())// Write each post to file using an XML format.
  257.                     $id--;
  258.                 }
  259.                 fwrite($posts_file'</POSTS>');
  260.                 break;
  261.                 
  262.             case "FLATFILE":
  263.                 if(!($posts_file fopen(PATH_BASE PATH_DATA FILE_BLOG_POSTS'w'))) // Throw exception if file we can't load file.
  264.                     throw new Exception("Cannot open flatfile for writing."3);
  265.                 
  266.                 $id count($this->posts1;
  267.                 foreach($this->posts as $post
  268.                 {
  269.                     $post->DeleteItem('ID')// Remove old ID to allow for a new, clean and linear structure.
  270.                     $post->AddItem('ID'$id)// Give the post an ID.
  271.                     fwrite($posts_file$post->AsText())// Write each post to file with our own flat format.
  272.                     $id--;
  273.                 }
  274.                 break;
  275.         }
  276.         fclose($posts_file);
  277.     }
  278.         
  279.     /**
  280.      * Returns key to the post with the specified ID.
  281.      */
  282.     private function GetKeyByID($id)
  283.     {
  284.         foreach($this->posts as $current_key => $post)
  285.             if(strcmp($post->GetValue("ID")$id== 0)
  286.                 return $current_key;
  287.         return -1;    
  288.     }
  289.  
  290.     /**
  291.      * Prints the RSS feed.
  292.      */
  293.     public function PrintRSS()
  294.     {
  295.         if(!$this->PostsLoaded()) // Check if posts are already loaded.
  296.             $this->LoadPosts()// If they aren't, we load them now.
  297.             
  298.         // Compose all sections of the RSS data.
  299.         $header '<?xml version="1.0"?>' PHP_EOL '<rss version="2.0">' PHP_EOL '<channel>' PHP_EOL;
  300.         $header .= '<title>' $this->GetSetting("TITLE"'</title>' PHP_EOL;
  301.         $header .= '<description>' $this->GetSetting("DESCRIPTION"'</description>' PHP_EOL;
  302.         $header .= '<link>' $this->GetSetting("URL"'</link>' PHP_EOL;
  303.         $items "";
  304.         foreach($this->posts as $post)
  305.         {
  306.             $items .= '<item>' PHP_EOL;
  307.             $items .= '<title>' $post->GetTitle('</title>' PHP_EOL;
  308.             $items .= '<description>' $post->GetContent('</description>' PHP_EOL;
  309.             $items .= '<pubDate>' $post->GetDate('</pubDate>' PHP_EOL;
  310.             $items .= '<link>' $this->GetSetting("URL"'index.php?post=' $post->GetID('</link>' PHP_EOL;
  311.             $items .= '</item>' PHP_EOL;
  312.         }
  313.         $footer '</channel>' PHP_EOL '</rss>';
  314.         
  315.         echo $header $items $footer// Print it all out.
  316.     }
  317.     
  318.     /**
  319.      * Returns reference to the post with the specified ID.
  320.      */
  321.     public function &GetPostByID($id)
  322.     {
  323.         foreach($this->posts as $post)
  324.             if(strcmp($post->GetValue("ID")$id== 0)
  325.                 return $post;
  326.         return NULL;    
  327.     }
  328.     
  329.     /**
  330.      * Deletes the post with the specified ID.
  331.      * 
  332.      * This assumes there are only one post with that ID, which we make sure of by giving the posts new unique IDs every time we save them to file.
  333.      */
  334.     public function DeletePost($id)
  335.     {
  336.         for($x 0$x $this->PostsLoaded()$x++// Find out which one we should delete, try to match against specified ID.
  337.         {
  338.             if (strcmp $id$this->items[$x]->GetKey() ) == 0)
  339.             {
  340.                 unset($this->posts[$x])// Here it is, let's delete it and...
  341.                 $this->SavePosts()// ...make sure we save this to file as well, and finally...
  342.                 return 1// ...return 1 on success.
  343.             }
  344.         }
  345.         
  346.         // If post was not found, -1 is returned.
  347.         return -1;
  348.     }
  349.     
  350.     /**
  351.      * Returns number of posts currently loaded.
  352.      * 
  353.      * This is a private function, only meant to be used within this class.
  354.      * 
  355.      * @return integer Number of posts currently loaded. Thus returning 0 if no posts have been loaded.
  356.      */
  357.     private function PostsLoaded()
  358.     {
  359.         return count($this->posts);
  360.     }
  361.     
  362.     /**
  363.      * Prints author of post currently being displayed.
  364.      */
  365.     public function CurrentAuthor()
  366.     {
  367.         echo $this->posts[$this->current_post]->GetAuthor();
  368.     }
  369.     
  370.     /**
  371.      * Prints title of post currently being displayed.
  372.      */
  373.     public function CurrentTitle()
  374.     {
  375.         echo $this->posts[$this->current_post]->GetTitle();
  376.     }
  377.     
  378.     /**
  379.      * Prints the blog title.
  380.      */
  381.     public function BlogTitle()
  382.     {
  383.         echo ucwords(strtolower($this->GetSetting('TITLE')));
  384.     }
  385.     
  386.     /**
  387.      * Prints content of post currently being displayed.
  388.      */
  389.     public function CurrentContent()
  390.     {
  391.         echo $this->posts[$this->current_post]->GetContent();
  392.     }
  393.     
  394.     /**
  395.      * Prints date of post currently being displayed.
  396.      */
  397.     public function CurrentDate()
  398.     {
  399.         echo $this->posts[$this->current_post]->GetDate();
  400.     }
  401.     
  402.     /**
  403.      * Prints tags of post currently being displayed.
  404.      */
  405.     public function CurrentTags()
  406.     {
  407.         echo $this->posts[$this->current_post]->GetTags();
  408.     }
  409.     
  410.     /**
  411.      * Set post being displayed to next one in order.
  412.      */
  413.     public function NextPost()
  414.     {
  415.         if(is_object($this->GetPostByID($this->current_post 1))) // Check if there are no more posts at all.
  416.         {
  417.             if(isset($this->current_page&& ($this->current_post 1>= $this->GetSetting('POSTS PER PAGE'$this->current_page)
  418.                 $this->current_post = -1// If this is the end of the page, set to negative.
  419.             else 
  420.                 $this->current_post++// Set current post to next post.
  421.         }
  422.         else
  423.             $this->current_post = -1// Set to negative, to show HavePosts() that there are no more posts.
  424.     }
  425.     
  426.     /**
  427.      * Checks if there are more posts to display after the current one.
  428.      * 
  429.      * @return bool True if there are more posts, false otherwise.
  430.      */
  431.     public function MorePosts()
  432.     {
  433.         if($this->current_post == -1// If current post is minus one, then there are no more posts and we return false.
  434.             return false;
  435.         else
  436.             return true;
  437.     }
  438.     
  439.     /**
  440.      * Checks if there are more posts to display after the current one.
  441.      * 
  442.      * @return bool True if there are more pages, false otherwise.
  443.      */
  444.     public function OlderPages()
  445.     {
  446.         if(!is_object($this->GetPostByID($this->current_post 1))) // Test for different scenarios where there are no more posts after this page.
  447.             return true;
  448.         elseif(!is_object($this->GetPostByID($this->current_page $this->GetSetting('POSTS PER PAGE'))))
  449.             return false;
  450.         else
  451.             return true;
  452.     }
  453.     
  454.     /**
  455.      * Checks if there are newer pages to display before the current one.
  456.      * 
  457.      * @return bool True if there are more newer pages, false otherwise.
  458.      */
  459.     public function NewerPages()
  460.     {
  461.         if(!is_object($this->GetPostByID(($this->current_page $this->GetSetting('POSTS PER PAGE')) $this->GetSetting('POSTS PER PAGE'1))) // Test for if there are no pages before this one.
  462.             return false;
  463.         else
  464.             return true;
  465.     }
  466.     
  467.     /**
  468.      * Authenticates user if password is supplied. If not, checks if user is logged in or redirects him to login page. Sets login variable to true on success.
  469.      * 
  470.      * @param string $password Password for login attempt.
  471.      * @return bool True if user is authenticated or authentication was successful, false otherwise.
  472.      */
  473.     public function Authenticate($password "")
  474.     {
  475.         if($password == ""// Password not set, we want to check if user is authenticated.
  476.         {
  477.             if(!$this->LoggedIn(&& in_array($this->current_page_name$this->authpages)) // If trying to view protected page without being logged in.
  478.             {
  479.                 header("location: " $this->pages['admin-login'"&error=password")//&nb