The page you are looking at now is at this URL: http://pastoid.com/bzz
This paste was last updated on February 13, 2010 at 12:54 am.
<?php /** * @package Habari * */ /** * * Includes an instance of the PostInfo class; for holding inforecords about a Post * If the Post object describes an existing post; use the internal info object to * get, set, unset and test for existence (isset) of info records. * <code> * $this->info= new PostInfo( 1 ); // Info records of post with id = 1 * $this->info->option1= "blah"; // set info record with name "option1" to value "blah" * $info_value= $this->info->option1; // get value of info record with name "option1" into variable $info_value * if ( isset ( $this->info->option1 ) ) // test for existence of "option1" * unset ( $this->info->option1 ); // delete "option1" info record * </code> * */ class Post extends QueryRecord implements IsContent { // static variables to hold post status and post type values static $post_status_list = array(); static $post_type_list_active = array(); static $post_type_list_all = array(); private $tags = null; private $comments_object = null; private $author_object = null; private $tokens = null; private $inforecords = null; protected $url_args; /** * returns an associative array of active post types * @param bool whether to force a refresh of the cached values * @return array An array of post type names => integer values */ public static function list_active_post_types( $refresh = false ) { if ( ( ! $refresh ) && ( ! empty( self::$post_type_list_active ) ) ) { return self::$post_type_list_active; } self::$post_type_list_active['any'] = 0; $sql = 'SELECT * FROM {posttype} WHERE active = 1 ORDER BY id ASC'; $results = DB::get_results( $sql ); foreach ( $results as $result ) { self::$post_type_list_active[$result->name] = $result->id; } return self::$post_type_list_active; } /** * returns an associative array of all post types * @param bool whether to force a refresh of the cached values * @return array An array of post type names => (integer values, active values) */ public static function list_all_post_types( $refresh = false ) { if ( ( ! $refresh ) && ( ! empty( self::$post_type_list_all ) ) ) { return self::$post_type_list_all; } self::$post_type_list_all['any'] = 0; $sql = 'SELECT * FROM {posttype} ORDER BY id ASC'; $results = DB::get_results( $sql ); foreach ( $results as $result ) { self::$post_type_list_all[$result->name] = array( 'id' => $result->id, 'active' => $result->active ); } return self::$post_type_list_all; } /** * Activate an existing post type * * @param string The post type to activate */ public static function activate_post_type( $type ) { $all_post_types = Post::list_all_post_types( true ); // We force a refresh // Check if it exists if ( array_key_exists( $type, $all_post_types ) ) { if ( ! $all_post_types[$type]['active'] == 1 ) { // Activate it $sql = 'UPDATE {posttype} SET active = 1 WHERE id = ' . $all_post_types[$type]['id']; DB::query( $sql ); } return true; } else { return false; // Doesn't exist } } /** * Deactivate a post type * * @param string The post type to deactivate */ public static function deactivate_post_type( $type ) { $active_post_types = Post::list_active_post_types( false ); // We force a refresh if ( array_key_exists( $type, $active_post_types ) ) { // $type is active so we'll deactivate it $sql = 'UPDATE {posttype} SET active = 0 WHERE id = ' . $active_post_types[$type]; DB::query( $sql ); return true; } return false; } /** * returns an associative array of post statuses * @param mixed $all true to list all statuses, not just external ones, Post to list external and any that match the Post status * @param boolean $refresh true to force a refresh of the cached values * @return array An array of post statuses names => interger values */ public static function list_post_statuses( $all = true, $refresh = false ) { $statuses = array(); $statuses['any'] = 0; if ( $refresh || empty( self::$post_status_list ) ) { $sql = 'SELECT * FROM {poststatus} ORDER BY id ASC'; $results = DB::get_results( $sql ); self::$post_status_list = $results; } foreach ( self::$post_status_list as $status ) { if ( $all instanceof Post ) { if( ! $status->internal || $status->id == $all->status ) { $statuses[$status->name] = $status->id; } } elseif ( $all ) { $statuses[$status->name] = $status->id; } elseif ( ! $status->internal ) { $statuses[$status->name] = $status->id; } } return $statuses; } /** * returns the integer value of the specified post status, or false * @param mixed a post status name or value * @return mixed an integer or boolean false */ public static function status( $name ) { $statuses = Post::list_post_statuses(); if ( is_numeric( $name ) && ( FALSE !== in_array( $name, $statuses ) ) ) { return $name; } if ( isset( $statuses[strtolower( $name )] ) ) { return $statuses[strtolower( $name )]; } return false; } /** * returns the friendly name of a post status, or null * @param mixed a post status value, or name * @return mixed a string of the status name, or null */ public static function status_name( $status ) { $statuses = array_flip( Post::list_post_statuses() ); if ( is_numeric( $status ) && isset( $statuses[$status] ) ) { return $statuses[$status]; } if ( FALSE !== in_array( $status, $statuses ) ) { return $status; } return ''; } /** * returns the integer value of the specified post type, or false * @param mixed a post type name or number * @return mixed an integer or boolean false */ public static function type( $name ) { $types = Post::list_active_post_types(); if ( is_numeric( $name ) && ( FALSE !== in_array( $name, $types ) ) ) { return $name; } if ( isset( $types[strtolower( $name )] ) ) { return $types[strtolower( $name )]; } return false; } /** * returns the friendly name of a post type, or null * @param mixed a post type number, or name * @return mixed a string of the post type, or null */ public static function type_name( $type ) { $types = array_flip( Post::list_active_post_types() ); if ( is_numeric( $type ) && isset( $types[$type] ) ) { return $types[$type]; } if ( FALSE !== in_array( $type, $types ) ) { return $type; } return ''; } /** * inserts a new post type into the database, if it doesn't exist * @param string The name of the new post type * @param bool Whether the new post type is active or not * @return none */ public static function add_new_type( $type, $active = true ) { // refresh the cache from the DB, just to be sure $types = self::list_all_post_types( true ); if ( ! array_key_exists( $type, $types ) ) { // Doesn't exist in DB.. add it and activate it. DB::query( 'INSERT INTO {posttype} (name, active) VALUES (?, ?)', array( $type, $active ) ); } elseif ( $types[$type]['active'] == 0 ) { // Isn't active so we activate it self::activate_post_type( $type ); } ACL::create_token( 'post_' . Utils::slugify($type), _t('Permissions to posts of type "%s"', array($type) ), _t('Content'), TRUE ); // now force a refresh of the caches, so the new/activated type // is available for immediate use $types = self::list_active_post_types( true ); $types = self::list_all_post_types( true ); } /** * inserts a new post status into the database, if it doesn't exist * @param string The name of the new post status * @param bool Whether this status is for internal use only. If true, this status will NOT be presented to the user * @return none */ public static function add_new_status( $status, $internal = false ) { // refresh the cache from the DB, just to be sure $statuses = self::list_post_statuses( true ); if ( ! array_key_exists( $status, $statuses ) ) { // let's make sure we only insert an integer $internal = intval( $internal ); DB::query( 'INSERT INTO {poststatus} (name, internal) VALUES (?, ?)', array( $status, $internal ) ); // force a refresh of the cache, so the new status // is available for immediate use $statuses = self::list_post_statuses( true, true ); } } /** * Return the defined database columns for a Post. * @return array Array of columns in the Post table */ public static function default_fields() { return array( 'id' => 0, 'slug' => '', 'title' => '', 'guid' => '', 'content' => '', 'cached_content' => '', 'user_id' => 0, 'status' => Post::status( 'draft' ), 'pubdate' => HabariDateTime::date_create(), 'updated' => HabariDateTime::date_create(), 'modified' => HabariDateTime::date_create(), 'content_type' => Post::type( 'entry' ) ); } /** * Constructor for the Post class. * @param array $paramarray an associative array of initial Post field values. */ public function __construct( $paramarray = array() ) { // Defaults $this->fields = array_merge( self::default_fields(), $this->fields ); parent::__construct( $paramarray ); if ( isset( $this->fields['tags'] ) ) { $this->tags = $this->parsetags( $this->fields['tags'] ); unset( $this->fields['tags'] ); } $this->exclude_fields( 'id' ); /* $this->fields['id'] could be null in case of a new post. If so, the info object is _not_ safe to use till after set_key has been called. Info records can be set immediately in any other case. */ } /** * Return a single requested post. * * <code> * $post= Post::get( array( 'slug' => 'wooga' ) ); * </code> * * @param array $paramarray An associative array of parameters, or a querystring * @return Post The first post that matched the given criteria */ static function get( $paramarray = array() ) { // Defaults $defaults = array ( 'where' => array( array( 'status' => Post::status( 'published' ), ), ), 'fetch_fn' => 'get_row', ); foreach ( $defaults['where'] as $index => $where ) { $defaults['where'][$index] = array_merge( $where, Utils::get_params( $paramarray ) ); } // make sure we get at most one result $defaults['limit'] = 1; return Posts::get( $defaults ); } /** * Create a post and save it. * * @param array $paramarray An associative array of post fields * @return Post The new Post object */ static function create( $paramarray ) { $post = new Post( $paramarray ); $post->insert(); return $post; } /** * Generate a new slug for the post. * * @return string The slug */ private function setslug() { // determine the base value from: // - the new slug if ( isset( $this->newfields['slug']) && $this->newfields['slug'] != '' ) { $value = $this->newfields['slug']; } // - the existing slug elseif ( $this->fields['slug'] != '' ) { $value = $this->fields['slug']; } // - the new post title elseif ( isset( $this->newfields['title'] ) && $this->newfields['title'] != '' ) { $value = $this->newfields['title']; } // - the existing post title elseif ( $this->fields['title'] != '' ) { $value = $this->fields['title']; } // - default else { $value = 'Post'; } // make sure our slug is unique $slug = Plugins::filter( 'post_setslug', $value ); $slug = Utils::slugify( $slug ); $postfix = ''; $postfixcount = 0; do { if ( ! $slugcount = DB::get_row( 'SELECT COUNT(slug) AS ct FROM {posts} WHERE slug = ?;', array( $slug . $postfix ) ) ) { Utils::debug( DB::get_errors() ); exit; } if ( $slugcount->ct != 0 ) { $postfix = "-" . ( ++$postfixcount ); } } while ( $slugcount->ct != 0 ); return $this->newfields['slug'] = $slug . $postfix; } /** * Generate the GUID for the new post. */ private function setguid() { if ( ! isset( $this->newfields['guid'] ) || ( $this->newfields['guid'] == '' ) // GUID is empty || ( $this->newfields['guid'] == '//?p=' ) // GUID created by WP was erroneous (as is too common) ) { $result = 'tag:' . Site::get_url( 'hostname' ) . ',' . date( 'Y' ) . ':' . rawurlencode($this->setslug()) . '/' . time(); $this->newfields['guid'] = $result; } return $this->newfields['guid']; } /** * function setstatus * @param mixed the status to set it to. String or integer. * @return integer the status of the post, or false if the new status isn't valid * Sets the status for a post, given a string or integer. */ private function setstatus( $value ) { $statuses = Post::list_post_statuses(); if ( is_numeric( $value ) && in_array( $value, $statuses ) ) { return $this->newfields['status'] = $value; } elseif ( array_key_exists( $value, $statuses ) ) { return $this->newfields['status'] = Post::status( $value ); } return false; } /** * Ensure this is an array of tags. * * @param Mixed A string to parse for tags or an array of tags. * @return Array An array of tags */ private static function parsetags( $tags ) { if ( is_string( $tags ) ) { if ( '' === $tags ) { return array(); } // dirrty ;) $rez = array( '\\"'=>':__unlikely_quote__:', '\\\''=>':__unlikely_apos__:' ); $zer = array( ':__unlikely_quote__:'=>'"', ':__unlikely_apos__:'=>"'" ); // escape $tagstr = str_replace( array_keys( $rez ), $rez, $tags ); // match-o-matic preg_match_all( '/((("|((?<= )|^)\')\\S([^\\3]*?)\\3((?=[\\W])|$))|[^,])+/', $tagstr, $matches ); // cleanup $tags = array_map( 'trim', $matches[0] ); $tags = preg_replace( array_fill( 0, count( $tags ), '/^(["\'])(((?!").)+)(\\1)$/'), '$2', $tags ); // unescape $tags = str_replace( array_keys( $zer ), $zer, $tags ); // hooray return $tags; } elseif ( is_array( $tags ) ) { return $tags; } } /** * Save the tags associated to this post into the terms and object_terms tables */ private function save_tags() { return Tags::save_associations( $this->tags, $this->id ); } /** * function insert * Saves a new post to the posts table */ public function insert() { $this->newfields['updated'] = HabariDateTime::date_create(); $this->newfields['modified'] = $this->newfields['updated']; $this->setguid(); $allow = true; $allow = Plugins::filter( 'post_insert_allow', $allow, $this ); if ( ! $allow ) { return; } Plugins::act( 'post_insert_before', $this ); // Invoke plugins for all fields, since they're all "changed" when inserted foreach ( $this->fields as $fieldname => $value ) { Plugins::act( 'post_update_' . $fieldname, $this, ( $this->id == 0 ) ? null : $value, $this->$fieldname ); } // invoke plugins for status changes Plugins::act( 'post_status_' . self::status_name( $this->status ), $this, null ); $result = parent::insertRecord( DB::table( 'posts' ) ); $this->newfields['id'] = DB::last_insert_id(); // Make sure the id is set in the post object to match the row id $this->fields = array_merge( $this->fields, $this->newfields ); $this->newfields = array(); $this->info->commit( DB::last_insert_id() ); $this->save_tags(); $this->create_default_permissions(); EventLog::log( sprintf(_t('New post %1$s (%2$s); Type: %3$s; Status: %4$s'), $this->id, $this->slug, Post::type_name( $this->content_type ), $this->statusname), 'info', 'content', 'habari' ); Plugins::act( 'post_insert_after', $this ); //scheduled post if( $this->status == Post::status( 'scheduled' ) ) { Posts::update_scheduled_posts_cronjob(); } return $result; } /** * function update * Updates an existing post in the posts table * @param bool $minor Indicates if this is a major or minor update */ public function update( $minor = true ) { $this->modified = HabariDateTime::date_create(); if ( ! $minor ) { $this->updated = $this->modified; } if ( isset( $this->fields['guid'] ) ) { unset( $this->newfields['guid'] ); } $allow = true; $allow = Plugins::filter( 'post_update_allow', $allow, $this ); if ( ! $allow ) { return; } Plugins::act( 'post_update_before', $this ); // Call setslug() only when post slug is changed if ( isset( $this->newfields['slug'] ) && $this->newfields['slug'] != '' ) { if ( $this->fields['slug'] != $this->newfields['slug'] ) { $this->setslug(); } } // invoke plugins for all fields which have been changed // For example, a plugin action "post_update_status" would be // triggered if the post has a new status value foreach ( $this->newfields as $fieldname => $value ) { Plugins::act( 'post_update_' . $fieldname, $this, $this->fields[$fieldname], $value ); } // invoke plugins for status changes if ( isset( $this->newfields['status'] ) && $this->fields['status'] != $this->newfields['status'] ) { Plugins::act( 'post_status_' . self::status_name( $this->newfields['status'] ), $this, $this->fields['status'] ); } $result = parent::updateRecord( DB::table( 'posts' ), array( 'id' => $this->id ) ); //scheduled post if ( $this->fields['status'] == Post::status( 'scheduled' ) || $this->status == Post::status( 'scheduled' ) ) { Posts::update_scheduled_posts_cronjob(); } $this->fields = array_merge( $this->fields, $this->newfields ); $this->newfields = array(); $this->save_tags(); $this->info->commit(); Plugins::act( 'post_update_after', $this ); return $result; } /** * function delete * Deletes an existing post */ public function delete() { $allow = true; $allow = Plugins::filter( 'post_delete_allow', $allow, $this ); if ( ! $allow ) { return; } // invoke plugins Plugins::act( 'post_delete_before', $this ); // delete all the tags associated with this post foreach ( $this->get_tags() as $tag_slug => $tag_text ) { $tag = Tags::get_by_slug( $tag_slug ); Tag::detach_from_post( $tag->id, $this->id ); } // Delete all comments associated with this post if ( $this->comments->count() > 0 ) { $this->comments->delete(); } // Delete all info records associated with this post if ( isset( $this->info ) ) { $this->info->delete_all(); } // Delete all post_tokens associated with this post $this->delete_tokens(); $result = parent::deleteRecord( DB::table( 'posts' ), array( 'slug'=>$this->slug ) ); EventLog::log( sprintf(_t('Post %1$s (%2$s) deleted.'), $this->id, $this->slug), 'info', 'content', 'habari' ); //scheduled post if( $this->status == Post::status( 'scheduled' ) ) { Posts::update_scheduled_posts_cronjob(); } // invoke plugins on the after_post_delete action Plugins::act( 'post_delete_after', $this ); return $result; } /** * function publish * Updates an existing post to published status * @return boolean True on success, false if not */ public function publish() { if ( $this->status == Post::status( 'published' ) ) { return true; } $allow = true; $allow = Plugins::filter( 'post_publish_allow', $allow, $this ); if ( ! $allow ) { return; } Plugins::act( 'post_publish_before', $this ); if ( $this->status != Post::status( 'scheduled' ) ) { $this->pubdate = HabariDateTime::date_create(); } if ( $this->status == Post::status( 'scheduled' ) ) { $this->get_tags(); $msg = sprintf(_t('Scheduled Post %1$s (%2$s) published at %3$s.'), $this->id, $this->slug, $this->pubdate->format()); } else { $msg = sprintf(_t('Post %1$s (%2$s) published.'), $this->id, $this->slug); } $this->status = Post::status( 'published' ); $result = $this->update( false ); EventLog::log( $msg, 'info', 'content', 'habari' ); // and call any final plugins Plugins::act( 'post_publish_after', $this ); return $result; } /** * function __get * Overrides QueryRecord __get to implement custom object properties * @param string Name of property to return * @return mixed The requested field value */ public function __get( $name ) { $fieldnames = array_merge( array_keys( $this->fields ), array( 'permalink', 'tags', 'comments', 'comment_count', 'comment_feed_link', 'author', 'editlink' ) ); if ( !in_array( $name, $fieldnames ) && strpos( $name, '_' ) !== false ) { preg_match( '/^(.*)_([^_]+)$/', $name, $matches ); list( $junk, $name, $filter )= $matches; } else { $filter = false; } switch ( $name ) { case 'statusname': $out = self::status_name( $this->status ); break; case 'typename': $out = self::type_name( $this->content_type ); break; case 'permalink': $out = $this->get_permalink(); break; case 'editlink': $out = $this->get_editlink(); break; case 'tags': $out = $this->get_tags(); break; case 'comments': $out = $this->get_comments(); break; case 'comment_count': $out = $this->get_comments()->count(); break; case 'comment_feed_link': $out = $this->get_comment_feed_link(); break; case 'author': $out = $this->get_author(); break; case 'info': $out = $this->get_info(); break; default: $out = parent::__get( $name ); break; } $out = Plugins::filter( "post_get", $out, $name, $this ); $out = Plugins::filter( "post_{$name}", $out, $this ); if ( $filter ) { $out = Plugins::filter( "post_{$name}_{$filter}", $out, $this ); } return $out; } /** * function __set * Overrides QueryRecord __set to implement custom object properties * @param string Name of property to return * @return mixed The requested field value */ public function __set( $name, $value ) { switch( $name ) { case 'pubdate': case 'updated': case 'modified': if ( !($value instanceOf HabariDateTime) ) { $value = HabariDateTime::date_create($value); } break; case 'tags': if ( is_array( $value) ) { return $this->tags = $value; } else { return $this->tags = $this->parsetags( $value ); } case 'status': return $this->setstatus( $value ); } return parent::__set( $name, $value ); } /** * Handle calls to this Post object that are implemented by plugins * @param string $name The name of the function called * @param array $args Arguments passed to the function call * @return mixed The value returned from any plugin filters, null if no value is returned */ public function __call( $name, $args ) { array_unshift($args, 'post_call_' . $name, null, $this); return call_user_func_array(array('Plugins', 'filter'), $args); } /** * Returns a form for editing this post * @param string $context The context the form is being created in, most often 'admin' * @return FormUI A form appropriate for creating and updating this post. */ public function get_form($context) { $form = new FormUI('create-content'); $form->class[] = 'create'; $newpost = ( 0 === $this->id ); // If the post has already been saved, add a link to its permalink if ( !$newpost ) { $post_links = $form->append('wrapper', 'post_links'); $permalink = ( $this->status != Post::status( 'published' ) ) ? $this->permalink . '?preview=1' : $this->permalink; $post_links->append('static', 'post_permalink', '<a href="'. $permalink .'" class="viewpost" >'.( $this->status != Post::status('published') ? _t('Preview Post') : _t('View Post') ).'</a>'); $post_links->class ='container'; } // Create the Title field $form->append('text', 'title', 'null:null', _t('Title'), 'admincontrol_text'); $form->title->class[] = 'important'; $form->title->class[] = 'check-change'; $form->title->tabindex = 1; $form->title->value = $this->title; $form->title->add_validator( 'validate_required' ); // Create the silos if ( count( Plugins::get_by_interface( 'MediaSilo' ) ) ) { $form->append('silos', 'silos'); $form->silos->silos = Media::dir(); } // Create the Content field $form->append('textarea', 'content', 'null:null', _t('Content'), 'admincontrol_textarea'); $form->content->class[] = 'resizable'; $form->content->class[] = 'check-change'; $form->content->tabindex = 2; $form->content->value = $this->content; $form->content->raw = true; // Create the tags field $form->append('text', 'tags', 'null:null', _t('Tags, separated by, commas'), 'admincontrol_text'); $form->tags->class = 'check-change'; $form->tags->tabindex = 3; $form->tags->value = implode(', ', $this->get_tags()); // Create the splitter $publish_controls = $form->append('tabs', 'publish_controls'); // Create the publishing controls // pass "false" to list_post_statuses() so that we don't include internal post statuses $statuses = Post::list_post_statuses( $this ); unset( $statuses[array_search( 'any', $statuses )] ); $statuses = Plugins::filter( 'admin_publish_list_post_statuses', $statuses ); $settings = $publish_controls->append('fieldset', 'settings', _t('Settings')); $settings->append('select', 'status', 'null:null', _t('Content State'), array_flip($statuses), 'tabcontrol_select'); $settings->status->value = $this->status; // hide the minor edit checkbox if the post is new if ( $newpost ) { $settings->append('hidden', 'minor_edit', 'null:null'); $settings->minor_edit->value = false; } else { $settings->append('checkbox', 'minor_edit', 'null:null', _t('Minor Edit'), 'tabcontrol_checkbox'); $settings->minor_edit->value = true; $form->append('hidden', 'modified', 'null:null')->value = $this->modified; } $settings->append('checkbox', 'comments_enabled', 'null:null', _t('Comments Allowed'), 'tabcontrol_checkbox'); $settings->comments_enabled->value = $this->info->comments_disabled ? false : true; $settings->append('text', 'pubdate', 'null:null', _t('Publication Time'), 'tabcontrol_text'); $settings->pubdate->value = $this->pubdate->format('Y-m-d H:i:s'); $settings->append('text', 'newslug', 'null:null', _t('Content Address'), 'tabcontrol_text'); $settings->newslug->value = $this->slug; // Create the button area $buttons = $form->append('fieldset', 'buttons'); $buttons->template = 'admincontrol_buttons'; $buttons->class[] = 'container'; $buttons->class[] = 'buttons'; $buttons->class[] = 'publish'; // Create the Save button $require_any = array( 'own_posts' => 'create', 'post_any' => 'create', 'post_' . Post::type_name( $this->content_type ) => 'create' ); if ( ( $newpost && User::identify()->can_any( $require_any ) ) || ( !$newpost && ACL::access_check( $this->get_access(), 'edit' ) ) ) { $buttons->append('submit', 'save', _t('Save'), 'admincontrol_submit'); $buttons->save->tabindex = 4; } // Add required hidden controls $form->append('hidden', 'content_type', 'null:null'); $form->content_type->id = 'content_type'; $form->content_type->value = $this->content_type; $form->append('hidden', 'post_id', 'null:null'); $form->post_id->id = 'id'; $form->post_id->value = $this->id; $form->append('hidden', 'slug', 'null:null'); $form->slug->value = $this->slug; // $form->on_success( array( 'AdminHandler', 'form_publish_success' ), $this ); $form->on_success( array( $this, 'form_publish_success' ) ); // Let plugins alter this form Plugins::act('form_publish', $form, $this, $context); // Return the form object return $form; } public function form_publish_success( FormUI $form ) { Utils::debug( $this->id ); // die(); $post_id = 0; // if ( isset($this->handler_vars['id']) ) { if ( isset($this->id) ) { // $post_id = intval($this->handler_vars['id']); $post_id = intval($this->id); } // If an id has been passed in, we're updating an existing post, otherwise we're creating one if ( 0 !== $post_id ) { $post = Post::get( array( 'id' => $post_id, 'status' => Post::status( 'any' ) ) ); $this->theme->admin_page = sprintf(_t('Publish %s'), Plugins::filter('post_type_display', Post::type_name($post->content_type), 'singular')); // $form = $post->get_form( 'admin' ); // Verify that the post hasn't already been updated since the form was loaded if ( $post->modified != $form->modified->value ) { Session::notice( _t( 'The post %1$s was updated since you made changes. Please review those changes before overwriting them.', array( sprintf('<a href="%1$s">\'%2$s\'</a>', $post->permalink, htmlspecialchars( $post->title ) ) ) ) ); Utils::redirect( URL::get( 'admin', 'page=publish&id=' . $post->id ) ); exit; } // Don't try to update form values that have been removed by plugins $expected = array('title', 'tags', 'content'); foreach ( $expected as $field ) { if ( isset($form->$field) ) { $post->$field = $form->$field->value; } } if ( $form->newslug->value == '' ) { Session::notice( _t( 'A post slug cannot be empty. Keeping old slug.' ) ); } elseif ( $form->newslug->value != $form->slug->value ) { $post->slug = $form->newslug->value; } // sorry, we just don't allow changing posts you don't have rights to if ( ! ACL::access_check( $post->get_access(), 'edit' ) ) { Session::error( _t( 'You don\'t have permission to edit that post' ) ); $this->get_blank(); } // sorry, we just don't allow changing content types to types you don't have rights to $user = User::identify(); $type = 'post_' . Post::type_name( $form->content_type->value ); if ( $form->content_type->value != $post->content_type && ( $user->cannot( $type ) || ! $user->can_any( array( 'own_posts' => 'edit', 'post_any' => 'edit', $type => 'edit' ) ) ) ) { Session::error(_t('Changing content types is not allowed')); $this->get_blank(); } $post->content_type = $form->content_type->value; // if not previously published and the user wants to publish now, change the pubdate to the current date/time // if the post pubdate is <= the current date/time. if ( ( $post->status != Post::status( 'published' ) ) && ( $form->status->value == Post::status( 'published' ) ) && ( HabariDateTime::date_create( $form->pubdate->value )->int <= HabariDateTime::date_create()->int ) ) { $post->pubdate = HabariDateTime::date_create(); } // else let the user change the publication date. // If previously published and the new date is in the future, the post will be unpublished and scheduled. Any other status, and the post will just get the new pubdate. // This will result in the post being scheduled for future publication if the date/time is in the future and the new status is published. else { $post->pubdate = HabariDateTime::date_create( $form->pubdate->value ); } $minor = $form->minor_edit->value && ($post->status != Post::status('draft')); $post->status = $form->status->value; } else { $post = new Post(); // $form = $post->get_form( 'admin' ); // check the user can create new posts of the set type. $user = User::identify(); $type = 'post_' . Post::type_name($form->content_type->value); if ( ACL::user_cannot( $user, $type) || ( ! ACL::user_can( $user, 'post_any', 'create' ) && ! ACL::user_can( $user, $type, 'create') ) ) { Session::error(_t('Creating that post type is denied')); $this->get_blank(); } // $form->set_option( 'form_action', URL::get('admin', 'page=publish' ) ); $form->on_success( array( $this, 'form_publish_success' ) ); if ( HabariDateTime::date_create( $form->pubdate->value )->int > $post->pubdate->int ) { $post->pubdate = HabariDateTime::date_create( $form->pubdate->value ); } $postdata = array( 'slug' => $form->newslug->value, 'user_id' => User::identify()->id, 'pubdate' => $post->pubdate, 'status' => $form->status->value, 'content_type' => $form->content_type->value, ); // Don't try to add form values that have been removed by plugins $expected = array('title', 'tags', 'content'); foreach ( $expected as $field ) { if ( isset($form->$field) ) { $postdata[$field] = $form->$field->value; } } $minor = false; $post = Post::create( $postdata ); } if ( $post->pubdate->int > HabariDateTime::date_create()->int && $post->status == Post::status( 'published' ) ) { $post->status = Post::status( 'scheduled' ); } $post->info->comments_disabled = !$form->comments_enabled->value; Plugins::act('publish_post', $post, $form); $post->update( $minor ); $permalink = ( $post->status != Post::status( 'published' ) ) ? $post->permalink . '?preview=1' : $post->permalink; Session::notice( sprintf( _t( 'The post %1$s has been saved as %2$s.' ), sprintf('<a href="%1$s">\'%2$s\'</a>', $permalink, htmlspecialchars( $post->title ) ), Post::status_name( $post->status ) ) ); if ( $post->slug != Utils::slugify( $post->title ) ) { Session::notice( sprintf( _t( 'The content address is \'%1$s\'.'), $post->slug )); } Utils::redirect( URL::get( 'admin', 'page=publish&id=' . $post->id ) ); } /** * Manage this post's comment form * * @param String context // What is $context for ? * @return FormUI The comment form for this post */ public function comment_form($context = 'public') { // Handle comment submissions and default commenter id values $cookie = 'comment_' . Options::get( 'GUID' ); $commenter_name = ''; $commenter_email = ''; $commenter_url = ''; $commenter_content = ''; $user = User::identify(); if ( isset( $_SESSION['comment'] ) ) { $details = Session::get_set( 'comment' ); $commenter_name = $details['name']; $commenter_email = $details['email']; $commenter_url = $details['url']; $commenter_content = $details['content']; } elseif ( $user->loggedin ) { $commenter_name = $user->displayname; $commenter_email = $user->email; $commenter_url = Site::get_url( 'habari' ); } elseif ( isset( $_COOKIE[$cookie] ) ) { list( $commenter_name, $commenter_email, $commenter_url )= explode( '#', $_COOKIE[$cookie] ); } // Now start the form. $form = new FormUI('comment-' . $context, 'comment'); $form->class[] = $context; $form->class[] = 'commentform'; $form->set_option( 'form_action', URL::get( 'submit_feedback', array( 'id' => $this->id ) ) ); // Create the Name field $form->append( 'text', 'cf_commenter', 'null:null', _t('Name <span class="required">*Required</span>'), 'formcontrol_text' )->add_validator('validate_required', _t('The Name field value is required')) ->id = 'comment_name'; $form->cf_commenter->tabindex = 1; $form->cf_commenter->value = $commenter_name; // Create the Email field $form->append( 'text', 'cf_email', 'null:null', _t('Email'), 'formcontrol_text' )->add_validator('validate_email', _t('The Email field value must be a valid email address')) ->id = 'comment_email'; $form->cf_email->tabindex = 2; if ( Options::get('comments_require_id') == 1 ) { $form->cf_email->caption = _t('Email <span class="required">*Required</span>'); } $form->cf_email->value = $commenter_email; // Create the URL field $form->append( 'text', 'cf_url', 'null:null', _t('Website'), 'formcontrol_text' )->add_validator('validate_url', _t('The Web Site field value must be a valid URL')) ->id = 'comment_url'; $form->cf_url->tabindex = 3; $form->cf_url->value = $commenter_url; // Create the Comment field $form->append( 'text', 'cf_content', 'null:null', _t('Comment'), 'formcontrol_textarea' )->add_validator('validate_required', _t('The Content field value is required')) ->id = 'comment_content'; $form->cf_content->tabindex = 4; $form->cf_content->value = $commenter_content; // Create the Submit button $form->append('submit', 'cf_submit', _t('Submit'), 'formcontrol_submit'); $form->cf_submit->tabindex = 5; // Add required hidden controls /* $form->append('hidden', 'content_type', 'null:null'); $form->content_type->value = $this->content_type; $form->append('hidden', 'post_id', 'null:null'); $form->post_id->id = 'id'; $form->post_id->value = $this->id; $form->append('hidden', 'slug', 'null:null'); $form->slug->value = $this->slug; */ // Let plugins alter this form Plugins::act('form_comment', $form, $this, $context); // Return the form object return $form; } /** * Returns a URL for the ->permalink property of this class. * @return string A URL to this post. * @todo separate permalink rule? (Not sure what this means - OW) */ private function get_permalink() { $content_type = Post::type_name( $this->content_type ); return URL::get( array( "display_{$content_type}", "display_post" ), $this, false ); } /** * Returns a URL for the ->editlink property of this class. * @return string A url to edit this post in the admin. */ private function get_editlink() { return URL::get('admin', 'page=publish&id=' . $this->id); } /** * function get_tags * Gets the tags for the post * @return array The tags array for this post */ private function get_tags() { if ( empty( $this->tags ) ) { $result = Tags::get_associations( $this->id ); if ( $result ) { foreach ( $result as $t ) { $this->tags[$t->term] = $t->term_display; } } } if ( count( $this->tags ) == 0 ) { return array(); } return $this->tags; } /** * function get_comments * Gets the comments for the post * @return &array A reference to the comments array for this post */ private function &get_comments() { if ( ! $this->comments_object ) { $this->comments_object = Comments::by_post_id( $this->id ); } return $this->comments_object; } /** * private function get_comment_feed_link * Returns the permalink for this post's comments Atom feed * @return string The permalink of this post's comments Atom feed */ private function get_comment_feed_link() { $content_type = Post::type_name( $this->content_type ); return URL::get( array( "atom_feed_{$content_type}_comments" ), $this, false ); } /** * function get_info * Gets the info object for this post, which contains data from the postinfo table * related to this post. * @return PostInfo object */ private function get_info() { if ( ! isset( $this->inforecords ) ) { // If this post isn't in the database yet... if( 0 == $this->id ) { $this->inforecords = new PostInfo(); } else { $this->inforecords = new PostInfo( $this->id ); } } else { $this->inforecords->set_key( $this->id ); } return $this->inforecords; } /** * private function get_author() * returns a User object for the author of this post * @return User a User object for the author of the current post */ private function get_author() { if ( ! isset( $this->author_object ) ) { // XXX for some reason, user_id is a string sometimes? $this->author_object = User::get_by_id( $this->user_id ); } return $this->author_object; } /** * Returns a set of properties used by URL::get to create URLs * @return array Properties of this post used to build a URL */ public function get_url_args() { if ( !$this->url_args ) { $arr = array( 'content_type_name' => Post::type_name( $this->content_type ) ); $author = URL::extract_args( $this->author, 'author_' ); $info = URL::extract_args( $this->info, 'info_' ); $this->url_args = array_merge( $author, $info, $arr, $this->to_array(), $this->pubdate->getdate() ); } return $this->url_args; } /** * Returns the ascending post, relative to this post, according to params * @params The params by which to work out what is the ascending post * @return Post The ascending post */ public function ascend($params = null) { return Posts::ascend($this, $params); } /** * Returns the descending post, relative to this post, according to params * @params The params by which to work out what is the descending post * @return Post The descending post */ public function descend($params = null) { return Posts::descend($this, $params); } /** * Return the content type of this object * * @return array An array of content types that this object represents, starting with the most specific * @see IsContent */ public function content_type() { return array(Post::type_name($this->content_type), 'Post'); } /** * Adds the default tokens to this post when it's saved */ public function create_default_tokens() { $tokens = array(); $tokens = Plugins::filter('post_tokens', $tokens, $this); $this->add_tokens( $this->content_type() ); } /** * Checks if this post has one or more tokens * * @param mixed $tokens A single token string or an array of tokens * @return mixed false if no tokens match, an array of matching token ids if any match */ public function has_tokens( $tokens ) { $this->get_tokens(); $tokens = Utils::single_array( $tokens ); $tokens = array_map(array('ACL', 'token_id'), $tokens); $tokens = array_intersect($tokens, $this->tokens); if ( count($tokens) == 0 ) { return false; } return $tokens; } /** * Add a token to a post * @param mixed $token The name of the permission to add, or an array of permissions to add */ public function add_tokens( $tokens ) { $this->get_tokens(); $tokens = Utils::single_array( $tokens ); $tokens = array_map(array('ACL', 'token_id'), $tokens); $add_tokens = array_diff($tokens, $this->tokens); $add_tokens = array_unique($add_tokens); foreach ( $add_tokens as $token_id ) { DB::insert( '{post_tokens}', array( 'post_id' => $this->id, 'token_id' => $token_id ) ); } $this->tokens = array_merge($this->tokens, $add_tokens); $this->tokens = array_unique($this->tokens); } /** * Deletes all tokens from a post */ public function delete_tokens() { DB::delete( '{post_tokens}', array( 'post_id' => $this->id ) ); $this->tokens = array(); } /** * Deletes tokens from a post * @param mixed $token The name of the permission to remove, or an array of permissions to remove */ public function remove_tokens( $tokens ) { $this->get_tokens(); $tokens = Utils::single_array( $tokens ); $tokens = array_map(array('ACL', 'token_id'), $tokens); $remove_tokens = array_intersect($tokens, $this->tokens); foreach ( $remove_tokens as $token_id ) { DB::delete( '{post_tokens}', array( 'post_id' => $this->id, 'token_id' => $token_id ) ); } $this->tokens = array_diff($this->tokens, $remove_tokens); } /** * Applies a new set of specific tokens to a post * @param mixed $tokens A string token, or an array of tokens to apply to this post */ public function set_tokens( $tokens ) { $tokens = Utils::single_array( $tokens ); $new_tokens = array_map(array('ACL', 'token_id'), $tokens); $new_tokens = array_unique($new_tokens); DB::delete( '{post_tokens}', array( 'post_id' => $this->id ) ); foreach ( $new_tokens as $token_id ) { DB::insert( '{post_tokens}', array( 'post_id' => $this->id, 'token_id' => $token_id ) ); } $this->tokens = $new_tokens; } /** * Returns an array of token ids that are associated with this post * Also initializes the internal token array for use by other token operations * * @return array An array of token ids */ public function get_tokens() { if ( empty( $this->tokens ) ) { $this->tokens = DB::get_column( 'SELECT token_id FROM {post_tokens} WHERE post_id = ?', array($this->id) ); } return $this->tokens; } /** * Returns an access Bitmask for the given user on this post * @param User $user The user mask to fetch * @return Bitmask */ public function get_access( $user = null ) { if ( ! $user instanceof User ) { $user = User::identify(); } if ( $user->can( 'super_user' ) ) { return ACL::get_bitmask( 'full' ); } // Collect a list of applicable tokens $tokens = array( 'post_any', 'post_' . Post::type_name( $this->content_type ), ); if ( $user->id == $this->user_id) { $tokens[] = 'own_posts'; } $tokens = array_merge($tokens, $this->get_tokens()); // collect all possible token accesses on this post $token_accesses = array(); foreach ( $tokens as $token ) { $access = ACL::get_user_token_access( $user, $token ); if ( $access instanceof Bitmask ) { $token_accesses[] = ACL::get_user_token_access( $user, $token )->value; } } // now that we have all the accesses, loop through them to build the access to the particular post if ( in_array( 0, $token_accesses ) ) { return ACL::get_bitmask( 0 ); } return ACL::get_bitmask( Utils::array_or( $token_accesses ) ); } } ?>
<?php /** * @package Habari * */ /** * Habari AdminHandler Class * Backbone of the admin area, handles requests and functionality. * * @todo Split into page-specific controllers. * Discussion: See http://groups.google.com/group/habari-dev/browse_thread/thread/9c469a4fcb61c814 * Branch: https://trac.habariproject.org/habari/browser/branches/adminhandler * Related branch: http://trac.habariproject.org/habari/browser/branches/handlers */ class AdminHandler extends ActionHandler { /** An instance of the active public theme, which allows plugin hooks to execute */ protected $active_theme = NULL; /** * Verifies user credentials before creating the theme and displaying the request. */ public function __construct() { $user = User::identify(); if ( !$user->loggedin ) { Session::add_to_set( 'login', $_SERVER['REQUEST_URI'], 'original' ); if ( URL::get_matched_rule()->name == 'admin_ajax' && isset($_SERVER['HTTP_REFERER']) ) { header( 'Content-Type: text/javascript;charset=utf-8' ); echo '{callback: function(){location.href="'.$_SERVER['HTTP_REFERER'].'"} }'; } else { $post_raw = $_POST->get_array_copy_raw(); if ( !empty( $post_raw ) ) { Session::add_to_set( 'last_form_data', $post_raw, 'post' ); Session::error( _t('We saved the last form you posted. Log back in to continue its submission.'), 'expired_form_submission' ); } $get_raw = $_GET->get_array_copy_raw(); if ( !empty( $get_raw ) ) { Session::add_to_set( 'last_form_data', $get_raw, 'get' ); Session::error( _t('We saved the last form you posted. Log back in to continue its submission.'), 'expired_form_submission' ); } Utils::redirect( URL::get( 'user', array( 'page' => 'login' ) ) ); } exit; } $last_form_data = Session::get_set( 'last_form_data' ); // This was saved in the "if ( !$user )" above, UserHandler transferred it properly. /* At this point, Controller has not created handler_vars, so we have to modify $_POST/$_GET. */ if ( isset( $last_form_data['post'] ) ) { $_POST = $_POST->merge( $last_form_data['post'] ); $_SERVER['REQUEST_METHOD'] = 'POST'; // This will trigger the proper act_admin switches. Session::remove_error( 'expired_form_submission' ); } if ( isset( $last_form_data['get'] ) ) { $_GET = $_GET->merge( $last_form_data['get'] ); Session::remove_error( 'expired_form_submission' ); // No need to change REQUEST_METHOD since GET is the default. } $user->remember(); // Create an instance of the active public theme so that its plugin functions are implemented $this->active_theme = Themes::create(); // setup the stacks for javascript in the admin - it's a method so a plugin can call it externally self::setup_stacks(); } /** * Dispatches the request to the defined method. (ie: post_{page}) */ public function act_admin() { $page = ( isset( $this->handler_vars['page'] ) && !empty( $this->handler_vars['page'] ) ) ? $this->handler_vars['page'] : 'dashboard'; if ( isset($this->handler_vars['content_type']) ) { $type = Plugins::filter('post_type_display', Post::type_name($this->handler_vars['content_type']), 'singular'); } elseif ( $page == 'publish' && isset($this->handler_vars['id'] ) ) { $type = Plugins::filter('post_type_display', Post::type_name(Post::get(intval($this->handler_vars['id']))->content_type), 'singular'); } else { $type = ''; } //$type = ( isset( $this->handler_vars['content_type'] ) && !empty( $this->handler_vars['content_type'] ) ) ? $this->handler_vars['content_type'] : ''; $theme_dir = Plugins::filter( 'admin_theme_dir', Site::get_dir( 'admin_theme', TRUE ) ); $this->theme = Themes::create( 'admin', 'RawPHPEngine', $theme_dir ); // Add some default stylesheets Stack::add('admin_stylesheet', array(Site::get_url('admin_theme') . '/css/admin.css', 'screen'), 'admin'); // Add some default template variables $this->set_admin_template_vars( $this->theme ); $this->theme->admin_type = $type; $this->theme->admin_page = $page; $this->theme->admin_page_url = ( $page == 'dashboard' ) ? URL::get( 'admin', 'page=' ) : URL::get( 'admin', 'page=' . $page ); $this->theme->page = $page; $this->theme->admin_title = ucwords($page) . ( $type != '' ? ' ' . ucwords($type) : '' ); $this->theme->admin_title = isset( $this->theme->mainmenu[$this->theme->admin_page]['text'] ) ? $this->theme->mainmenu[$this->theme->admin_page]['text'] : ucwords($page) . ( $type != '' ? ' ' . ucwords($type) : '' ); // Access check to see if the user is allowed the requested page Utils::check_request_method( array( 'GET', 'HEAD', 'POST' ) ); if ( !$this->access_allowed( $page, $type ) ) { Session::error(_t('Access to that page has been denied by the administrator.')); $this->get_blank(); } switch( $_SERVER['REQUEST_METHOD'] ) { case 'POST': // Let plugins try to handle the page Plugins::act('admin_theme_post_' . $page, $this, $this->theme); // Handle POSTs to the admin pages $fn = 'post_' . $page; if ( method_exists( $this, $fn ) ) { $this->$fn(); } else { $classname = get_class( $this ); echo sprintf( _t( '%1$s->%2$s() does not exist.' ), $classname, $fn ); exit; } break; case 'GET': case 'HEAD': // Let plugins try to handle the page Plugins::act('admin_theme_get_' . $page, $this, $this->theme); // Handle GETs of the admin pages $fn = 'get_' . $page; if ( method_exists( $this, $fn ) ) { $this->$fn(); exit; } // If a get_ function doesn't exist, just load the template and display it if ( $this->theme->template_exists( $page ) ) { $this->display( $page ); } else { // The requested console page doesn't exist header( 'HTTP/1.0 404 Not Found' ); $this->get_blank(_t('The page you were looking for was not found.')); } break; } } /** * Handle incoming requests to /admin_ajax for admin ajax requests */ public function act_admin_ajax() { header( 'Content-Type: text/javascript;charset=utf-8' ); $context = $this->handler_vars['context']; if ( method_exists( $this, 'ajax_' . $context ) ) { $type = ( isset( $this->handler_vars['content_type'] ) && !empty( $this->handler_vars['content_type'] ) ) ? $this->handler_vars['content_type'] : ''; // Access check to see if the user is allowed the requested page if ( $this->access_allowed( 'ajax_' . $context, $type ) ) { call_user_func( array( $this, 'ajax_' . $context ), $this->handler_vars ); } } else { header( 'HTTP/1.1 403 Forbidden', true, 403 ); die(); } } /** * Handles get requests from the options admin page */ public function get_options() { $this->post_options(); } /** * Handles POST requests from the options admin page */ public function post_options() { $option_items = array(); $timezones = DateTimeZone::listIdentifiers(); $timezones = array_merge( array( ''=>'' ), array_combine( array_values( $timezones ), array_values( $timezones ) ) ); $option_items[_t('Name & Tagline')] = array( 'title' => array( 'label' => _t('Site Name'), 'type' => 'text', 'helptext' => '', ), 'tagline' => array( 'label' => _t('Site Tagline'), 'type' => 'text', 'helptext' => '', ), 'about' => array( 'label' => _t('About'), 'type' => 'textarea', 'helptext' => '', ), ); $option_items[_t('Publishing')] = array( 'pagination' => array( 'label' => _t('Items per Page'), 'type' => 'text', 'helptext' => '', ), 'atom_entries' => array( 'label' => _t('Entries to show in Atom feed'), 'type' => 'text', 'helptext' => '', ), 'comments_require_id' => array( 'label' => _t('Require Comment Author Info'), 'type' => 'checkbox', 'helptext' => '', ), ); $option_items[_t('Time & Date')] = array( /*'presets' => array( 'label' => _t('Presets'), 'type' => 'select', 'selectarray' => array( 'europe' => _t('Europe') ), 'helptext' => '', ),*/ 'timezone' => array( 'label' => _t('Time Zone'), 'type' => 'select', 'selectarray' => $timezones, 'helptext' => _t( 'Current Date Time: %s', array( HabariDateTime::date_create()->format() ) ), ), 'dateformat' => array( 'label' => _t('Date Format'), 'type' => 'text', 'helptext' => _t( 'Current Date: %s', array( HabariDateTime::date_create()->date ) ), ), 'timeformat' => array( 'label' => _t('Time Format'), 'type' => 'text', 'helptext' => _t( 'Current Time: %s', array( HabariDateTime::date_create()->time ) ), ) ); $option_items[_t('Language')] = array( 'locale' => array( 'label' => _t( 'Locale' ), 'type' => 'select', 'selectarray' => array_merge( array( '' => 'default' ), array_combine( HabariLocale::list_all(), HabariLocale::list_all() ) ), 'helptext' => _t( 'International language code' ), ), 'system_locale' => array( 'label' => _t('System Locale'), 'type' => 'text', 'helptext' => _t( 'The appropriate locale code for your server' ), ), ); $option_items[_t('Troubleshooting')] = array( 'log_backtraces' => array( 'label' => _t( 'Log Backtraces' ), 'type' => 'checkbox', 'helptext' => _t( 'Logs error backtraces to the log table\'s data column. Can drastically increase log size!' ), ), ); /*$option_items[_t('Presentation')] = array( 'encoding' => array( 'label' => _t('Encoding'), 'type' => 'select', 'selectarray' => array( 'UTF-8' => 'UTF-8' ), 'helptext' => '', ), );*/ $option_items = Plugins::filter( 'admin_option_items', $option_items ); $form = new FormUI('Admin Options'); $tab_index = 3; foreach ( $option_items as $name => $option_fields ) { $fieldset = $form->append( 'wrapper', Utils::slugify( _u($name) ), $name ); $fieldset->class = 'container settings'; $fieldset->append( 'static', $name, '<h2>' . htmlentities( $name, ENT_COMPAT, 'UTF-8' ) . '</h2>' ); foreach ( $option_fields as $option_name => $option ) { $field = $fieldset->append( $option['type'], $option_name, $option_name, $option['label'] ); $field->template = 'optionscontrol_' . $option['type']; $field->class = 'item clear'; if ( $option['type'] == 'select' && isset( $option['selectarray'] ) ) { $field->options = $option['selectarray']; } $field->tabindex = $tab_index; $tab_index++; if ( isset( $option['helptext'] ) ) { $field->helptext = $option['helptext']; } else { $field->helptext = ''; } } } /* @todo: filter for additional options from plugins * We could either use existing config forms and simply extract * the form controls, or we could create something different */ $submit = $form->append( 'submit', 'apply', _t('Apply'), 'admincontrol_submit' ); $submit->tabindex = $tab_index; $form->on_success( array( $this, 'form_options_success' ) ); $this->theme->form = $form->get(); $this->theme->option_names = array_keys( $option_items ); $this->theme->display( 'options' ); } /** * Display a message when the site options are saved, and save those options * * @param FormUI $form The successfully submitted form */ public function form_options_success($form) { Session::notice( _t( 'Successfully updated options' ) ); $form->save(); Utils::redirect(); } /** * Handles POST requests from the dashboard. */ public function post_dashboard() { $this->get_dashboard(); } /** * Handles get requests for the dashboard * @todo update check should probably be cron'd and cached, not re-checked every load */ public function get_dashboard() { // Not sure how best to determine this yet, maybe set an option on install, maybe do this: $firstpostdate = DB::get_value('SELECT min(pubdate) FROM {posts} WHERE status = ?', array(Post::status('published'))); if ( intval( $firstpostdate ) !== 0 ) $firstpostdate = time() - $firstpostdate; $this->theme->active_time = array( 'years' => floor($firstpostdate / 31556736), 'months' => floor(($firstpostdate % 31556736) / 2629728), 'days' => round(($firstpostdate % 2629728) / 86400), ); // get the active theme, so we can check it $active_theme = Themes::get_active(); $active_theme = $active_theme->name . ':' . $active_theme->version; // if the active plugin list has changed, expire the updates cache if ( Cache::has( 'dashboard_updates' ) && ( Cache::get( 'dashboard_updates_plugins' ) != Options::get( 'active_plugins' ) ) ) { Cache::expire( 'dashboard_updates' ); } // if the theme version has changed, expire the updates cache if ( Cache::has( 'dashboard_updates' ) && ( Cache::get( 'dashboard_updates_theme' ) != $active_theme ) ) { Cache::expire( 'dashboard_updates' ); } /* * Check for updates to core and any hooked plugins * cache the output so we don't make a request every load but can still display updates */ if ( Cache::has( 'dashboard_updates' ) ) { $this->theme->updates = Cache::get( 'dashboard_updates' ); } else { $updates = Update::check(); if ( !Error::is_error( $updates ) ) { Cache::set( 'dashboard_updates', $updates ); $this->theme->updates = $updates; // cache the set of plugins we just used to check for Cache::set( 'dashboard_updates_plugins', Options::get( 'active_plugins' ) ); // cache the active theme we just used to check for Cache::set( 'dashboard_updates_theme', $active_theme ); } else { $this->theme->updates = array(); } } $this->theme->stats = array( 'author_count' => Users::get( array( 'count' => 1 ) ), 'page_count' => Posts::get( array( 'count' => 1, 'content_type' => Post::type('page'), 'status' => Post::status('published') ) ), 'entry_count' => Posts::get( array( 'count' => 1, 'content_type' => Post::type('entry'), 'status' => Post::status('published') ) ), 'comment_count' => Comments::count_total( Comment::STATUS_APPROVED, FALSE ), 'tag_count' => Tags::count_total(), 'page_draft_count' => Posts::get( array( 'count' => 1, 'content_type' => Post::type('page'), 'status' => Post::status('draft'), 'user_id' => User::identify()->id ) ), 'entry_draft_count' => Posts::get( array( 'count' => 1, 'content_type' => Post::type('entry'), 'status' => Post::status('draft'), 'user_id' => User::identify()->id ) ), 'unapproved_comment_count' => User::identify()->can( 'manage_all_comments' ) ? Comments::count_total( Comment::STATUS_UNAPPROVED, FALSE ) : Comments::count_by_author( User::identify()->id, Comment::STATUS_UNAPPROVED ), 'spam_comment_count' => User::identify()->can( 'manage_all_comments' ) ? Comments::count_total( Comment::STATUS_SPAM, FALSE ) : Comments::count_by_author( User::identify()->id, Comment::STATUS_SPAM ), 'user_entry_scheduled_count' => Posts::get( array( 'count' => 1, 'content_type' => Post::type( 'any' ), 'status' => Post::status( 'scheduled' ), 'user_id' => User::identify()->id ) ), ); $this->fetch_dashboard_modules(); // check for first run $u = User::identify(); if ( ! isset( $u->info->experience_level ) ) { $this->theme->first_run = true; $u->info->experience_level = 'user'; $u->info->commit(); } else { $this->theme->first_run = false; } $this->display( 'dashboard' ); } /** * Fetches active modules for display on the dashboard */ /* public function fetch_dashboard_modules() { if ( count( Modules::get_all() ) == 0 ) { $this->theme->modules = array(); return; } // get the active module list $modules = Modules::get_active(); if(User::identify()->can('manage_dash_modules')) { // append the 'Add Item' module $modules['nosort'] = 'Add Item'; // register the 'Add Item' filter Plugins::register( array( $this, 'filter_dash_module_add_item' ), 'filter', 'dash_module_add_item'); } foreach ( $modules as $id => $module_name ) { $slug = Utils::slugify( (string) $module_name, '_' ); $module = array( 'name' => $module_name, 'title' => $module_name, 'content' => '', 'options' => '' ); $module = Plugins::filter( 'dash_module_' .$slug, $module, $id, $this->theme ); $modules[$id] = $module; } $this->theme->modules = $modules; } */ /** * Handles POST requests from the publish page. */ public function post_publish() { $this->get_publish(); } public static function form_publish_success( FormUI $form , $thispost ) { $post_id = 0; if ( isset($thispost->id) ) { $post_id = intval($thispost->id); } Utils::debug( $post_id ); // If an id has been passed in, we're updating an existing post, otherwise we're creating one if ( 0 !== $post_id ) { $post = Post::get( array( 'id' => $post_id, 'status' => Post::status( 'any' ) ) ); $thispost->theme->admin_page = sprintf(_t('Publish %s'), Plugins::filter('post_type_display', Post::type_name($post->content_type), 'singular')); // $form = $post->get_form( 'admin' ); // Verify that the post hasn't already been updated since the form was loaded if ( $post->modified != $form->modified->value ) { Session::notice( _t( 'The post %1$s was updated since you made changes. Please review those changes before overwriting them.', array( sprintf('<a href="%1$s">\'%2$s\'</a>', $post->permalink, htmlspecialchars( $post->title ) ) ) ) ); Utils::redirect( URL::get( 'admin', 'page=publish&id=' . $post->id ) ); exit; } // Don't try to update form values that have been removed by plugins $expected = array('title', 'tags', 'content'); foreach ( $expected as $field ) { if ( isset($form->$field) ) { $post->$field = $form->$field->value; } } if ( $form->newslug->value == '' ) { Session::notice( _t( 'A post slug cannot be empty. Keeping old slug.' ) ); } elseif ( $form->newslug->value != $form->slug->value ) { $post->slug = $form->newslug->value; } // sorry, we just don't allow changing posts you don't have rights to if ( ! ACL::access_check( $post->get_access(), 'edit' ) ) { Session::error( _t( 'You don\'t have permission to edit that post' ) ); $thispost->get_blank(); } // sorry, we just don't allow changing content types to types you don't have rights to $user = User::identify(); $type = 'post_' . Post::type_name( $form->content_type->value ); if ( $form->content_type->value != $post->content_type && ( $user->cannot( $type ) || ! $user->can_any( array( 'own_posts' => 'edit', 'post_any' => 'edit', $type => 'edit' ) ) ) ) { Session::error(_t('Changing content types is not allowed')); $thispost->get_blank(); } $post->content_type = $form->content_type->value; // if not previously published and the user wants to publish now, change the pubdate to the current date/time // if the post pubdate is <= the current date/time. if ( ( $post->status != Post::status( 'published' ) ) && ( $form->status->value == Post::status( 'published' ) ) && ( HabariDateTime::date_create( $form->pubdate->value )->int <= HabariDateTime::date_create()->int ) ) { $post->pubdate = HabariDateTime::date_create(); } // else let the user change the publication date. // If previously published and the new date is in the future, the post will be unpublished and scheduled. Any other status, and the post will just get the new pubdate. // This will result in the post being scheduled for future publication if the date/time is in the future and the new status is published. else { $post->pubdate = HabariDateTime::date_create( $form->pubdate->value ); } $minor = $form->minor_edit->value && ($post->status != Post::status('draft')); $post->status = $form->status->value; } else { $post = new Post(); // $form = $post->get_form( 'admin' ); // check the user can create new posts of the set type. $user = User::identify(); $type = 'post_' . Post::type_name($form->content_type->value); if ( ACL::user_cannot( $user, $type) || ( ! ACL::user_can( $user, 'post_any', 'create' ) && ! ACL::user_can( $user, $type, 'create') ) ) { Session::error(_t('Creating that post type is denied')); $thispost->get_blank(); } // $form->set_option( 'form_action', URL::get('admin', 'page=publish' ) ); $form->on_success( array( $thispost, 'form_publish_success' ) ); if ( HabariDateTime::date_create( $form->pubdate->value )->int > $post->pubdate->int ) { $post->pubdate = HabariDateTime::date_create( $form->pubdate->value ); } $postdata = array( 'slug' => $form->newslug->value, 'user_id' => User::identify()->id, 'pubdate' => $post->pubdate, 'status' => $form->status->value, 'content_type' => $form->content_type->value, ); // Don't try to add form values that have been removed by plugins $expected = array('title', 'tags', 'content'); foreach ( $expected as $field ) { if ( isset($form->$field) ) { $postdata[$field] = $form->$field->value; } } $minor = false; $post = Post::create( $postdata ); } if ( $post->pubdate->int > HabariDateTime::date_create()->int && $post->status == Post::status( 'published' ) ) { $post->status = Post::status( 'scheduled' ); } $post->info->comments_disabled = !$form->comments_enabled->value; Plugins::act('publish_post', $post, $form); $post->update( $minor ); $permalink = ( $post->status != Post::status( 'published' ) ) ? $post->permalink . '?preview=1' : $post->permalink; Session::notice( sprintf( _t( 'The post %1$s has been saved as %2$s.' ), sprintf('<a href="%1$s">\'%2$s\'</a>', $permalink, htmlspecialchars( $post->title ) ), Post::status_name( $post->status ) ) ); if ( $post->slug != Utils::slugify( $post->title ) ) { Session::notice( sprintf( _t( 'The content address is \'%1$s\'.'), $post->slug )); } Utils::redirect( URL::get( 'admin', 'page=publish&id=' . $post->id ) ); } /** * Handles GET requests of the publish page. */ public function get_publish( $template = 'publish') { $extract = $this->handler_vars->filter_keys('id', 'content_type'); foreach ( $extract as $key => $value ) { $$key = $value; } // Utils::debug( $id ); // 0 is what's assigned to new posts if ( isset( $id ) && ( $id != 0 )) { $post = Post::get( array( 'id' => $id, 'status' => Post::status( 'any' ) ) ); if ( !$post ) { Session::error(_t('Access to that post id is denied')); $this->get_blank(); } $this->theme->post = $post; } else { $post = new Post(); $this->theme->post = $post; $post->content_type = Post::type( ( isset( $content_type ) ) ? $content_type : 'entry' ); // check the user can create new posts of the set type. $user = User::identify(); $type = 'post_' . Post::type_name( $post->content_type ); if ( ACL::user_cannot( $user, $type ) || ( ! ACL::user_can( $user, 'post_any', 'create' ) && ! ACL::user_can( $user, $type, 'create' ) ) ) { Session::error( _t( 'Access to create posts of type %s is denied', array( Post::type_name( $post->content_type ) ) ) ); $this->get_blank(); } } // Utils::debug( $this->theme ); $this->theme->admin_page = sprintf(_t('Publish %s'), Plugins::filter('post_type_display', Post::type_name($post->content_type), 'singular')); $this->theme->admin_title = sprintf(_t('Publish %s'), Plugins::filter('post_type_display', Post::type_name($post->content_type), 'singular')); // Utils::debug( $this->theme ); $statuses = Post::list_post_statuses( false ); $this->theme->statuses = $statuses; $form = $post->get_form( 'admin' ); $form->set_option( 'form_action', URL::get('admin', 'page=publish' ) ); $this->theme->form = $form; $this->theme->wsse = Utils::WSSE(); $this->display( $template ); } /** * Deletes a post from the database. */ public function post_delete_post() { $extract = $this->handler_vars->filter_keys('id', 'nonce', 'timestamp', 'digest'); foreach ( $extract as $key => $value ) { $$key = $value; } $okay = TRUE; if ( empty( $id ) || empty( $nonce ) || empty( $timestamp ) || empty( $digest ) ) { $okay = FALSE; } $wsse = Utils::WSSE( $nonce, $timestamp ); if ( $digest != $wsse['digest'] ) { $okay = FALSE; } $post = Post::get( array( 'id' => $id, 'status' => Post::status( 'any' ) ) ); if ( ! ACL::access_check( $post->get_access(), 'delete' ) ) { $okay = FALSE; } if ( !$okay ) { Utils::redirect( URL::get( 'admin', 'page=posts&type='. Post::status( 'any' ) ) ); } $post->delete(); Session::notice( sprintf( _t( 'Deleted the %1$s titled "%2$s".' ), Post::type_name( $post->content_type ), htmlspecialchars( $post->title ) ) ); Utils::redirect( URL::get( 'admin', 'page=posts&type=' . Post::status( 'any' ) ) ); } /** * Handles GET requests of a user page. */ public function get_user() { $edit_user = User::identify(); $permission = false; if ( ($this->handler_vars['user'] == '') || (User::get_by_name($this->handler_vars['user']) == $edit_user) ) { if($edit_user->can('manage_self') || $edit_user->can('manage_users')) { $permission = true; } $who = _t("You"); $possessive = _t("Your User Information"); } else { if($edit_user->can('manage_users')) { $permission = true; } $edit_user = User::get_by_name($this->handler_vars['user']); $who = $edit_user->username; $possessive = sprintf( _t("%s's User Information"), $who ); } if(!$permission) { Session::error(_t('Access to that page has been denied by the administrator.')); $this->get_blank(); return; } // Get author list $author_list = Users::get_all(); $authors[0] = _t('nobody'); foreach ( $author_list as $author ) { $authors[ $author->id ] = $author->displayname; } unset($authors[ $edit_user->id ]); // We can't reassign posts to ourself $this->theme->authors = $authors; $this->theme->edit_user = $edit_user; $this->theme->who = $who; $this->theme->possessive = $possessive; // Redirect to the users management page if we're trying to edit a non-existent user if ( !$edit_user ) { Session::error( _t( 'No such user!' ) ); Utils::redirect( URL::get( 'admin', 'page=users' ) ); } $this->theme->edit_user = $edit_user; $field_sections = array( 'user_info' => $possessive, 'change_password' => _t('Change Password'), 'regional_settings' => _t('Regional Settings'), 'dashboard' => _t( 'Dashboard' ), ); $form = new FormUI('User Options'); // Create a tracker for who we are dealing with $form->append('hidden', 'edit_user', 'edit_user'); $form->edit_user->value = $edit_user->id; // Generate sections foreach ( $field_sections as $key => $name ) { $fieldset = $form->append( 'wrapper', $key, $name ); $fieldset->class = 'container settings'; $fieldset->append( 'static', $key, '<h2>' . htmlentities( $name, ENT_COMPAT, 'UTF-8' ) . '</h2>' ); } // User Info $displayname = $form->user_info->append('text', 'displayname', 'null:null', _t('Display Name'), 'optionscontrol_text'); $displayname->class[] = 'important item clear'; $displayname->value = $edit_user->displayname; $username = $form->user_info->append('text', 'username', 'null:null', _t('User Name'), 'optionscontrol_text'); $username->class[] = 'item clear'; $username->value = $edit_user->username; $username->add_validator('validate_username', $edit_user->username); $email = $form->user_info->append('text', 'email', 'null:null', _t('Email'), 'optionscontrol_text'); $email->class[] = 'item clear'; $email->value = $edit_user->email; $email->add_validator('validate_email'); $imageurl = $form->user_info->append('text', 'imageurl', 'null:null', _t('Portrait URL'), 'optionscontrol_text'); $imageurl->class[] = 'item clear'; $imageurl->value = $edit_user->info->imageurl; // Change Password $password1 = $form->change_password->append('text', 'password1', 'null:null', _t('New Password'), 'optionscontrol_text'); $password1->class[] = 'item clear'; $password1->type = 'password'; $password1->value = ''; $password2 = $form->change_password->append('text', 'password2', 'null:null', _t('New Password Again'), 'optionscontrol_text'); $password2->class[] = 'item clear'; $password2->type = 'password'; $password2->value = ''; $delete = $this->handler_vars->filter_keys('delete'); // don't validate password match if action is delete if (!isset($delete['delete'])) { $password2->add_validator('validate_same', $password1, _t('Passwords must match.')); } // Regional settings $timezones = DateTimeZone::listIdentifiers(); $timezones = array_merge( array_combine( array_values( $timezones ), array_values( $timezones ) ) ); $locale_tz = $form->regional_settings->append('text', 'locale_tz', 'null:null', _t('Timezone'), 'optionscontrol_select'); $locale_tz->class[] = 'item clear'; $locale_tz->value = $edit_user->info->locale_tz; $locale_tz->options = $timezones; $locale_tz->multiple = false; $locale_date_format = $form->regional_settings->append('text', 'locale_date_format', 'null:null', _t('Date Format'), 'optionscontrol_text'); $locale_date_format->class[] = 'item clear'; $locale_date_format->value = $edit_user->info->locale_date_format; if ( isset($edit_user->info->locale_date_format) && $edit_user->info->locale_date_format != '' ) { $current = HabariDateTime::date_create()->get($edit_user->info->locale_date_format); } else { $current = HabariDateTime::date_create()->date; } $locale_date_format->helptext = _t('See <a href="%s">php.net/date</a> for details. Current format: %s', array('http://php.net/date', $current) ); $locale_time_format = $form->regional_settings->append('text', 'locale_time_format', 'null:null', _t('Time Format'), 'optionscontrol_text'); $locale_time_format->class[] = 'item clear'; $locale_time_format->value = $edit_user->info->locale_time_format; if ( isset($edit_user->info->locale_time_format) && $edit_user->info->locale_time_format != '' ) { $current = HabariDateTime::date_create()->get($edit_user->info->locale_time_format); } else { $current = HabariDateTime::date_create()->time; } $locale_time_format->helptext = _t('See <a href="%s">php.net/date</a> for details. Current format: %s', array('http://php.net/date', $current) ); $spam_count = $form->dashboard->append( 'checkbox', 'dashboard_hide_spam_count', 'null:null', _t( 'Hide Spam Count' ), 'optionscontrol_checkbox' ); $spam_count->class[] = 'item clear'; $spam_count->helptext = _t( 'Hide the number of SPAM comments on your dashboard.' ); $spam_count->value = $edit_user->info->dashboard_hide_spam_count; // Controls $controls = $form->append( 'wrapper', 'page_controls' ); $controls->class = 'container controls transparent'; $submit = $controls->append( 'submit', 'apply', _t('Apply'), 'optionscontrol_submit' ); $submit->class[] = 'pct30'; $controls->append( 'static', 'reassign', '<span class="pct35 reassigntext">' . _t('Reassign posts to: %s', array(Utils::html_select('reassign', $authors)) ) . '</span><span class="minor pct5 conjunction">' . _t('and') . '</span><span class="pct30"><input type="submit" name="delete" value="' . _t('Delete') . '" class="delete button"></span>'); $form->on_success( array( $this, 'form_user_success' ) ); // Let plugins alter this form Plugins::act('form_user', $form, $edit_user); $this->theme->form = $form->get(); $this->theme->display('user'); } /** * Handles form submission from a user's page. */ public function form_user_success($form) { $edit_user = User::get_by_id($form->edit_user->value); $current_user = User::identify(); // Let's check for deletion if ( Controller::get_var('delete') != NULL ) { if ( $current_user->id != $edit_user->id ) { // We're going to delete the user before we need it, so store the username $username = $edit_user->username; $posts = Posts::get( array( 'user_id' => $edit_user->id, 'nolimit' => true ) ); if ( ( Controller::get_var('reassign') != NULL ) && (Controller::get_var('reassign') != 0) && (Controller::get_var('reassign') != $edit_user->id)) { // we're going to re-assign all of this user's posts $newauthor = Controller::get_var('reassign'); Posts::reassign( $newauthor, $posts ); $edit_user->delete(); } else { // delete user, then delete posts $edit_user->delete(); // delete posts foreach ( $posts as $post ) { $post->delete(); } } Session::notice( sprintf( _t( '%s has been deleted' ), $username ) ); Utils::redirect(URL::get('admin', array('page' => 'users'))); } else { Session::notice( _t( 'You cannot delete yourself.') ); } } $update = false; // Change username if ( isset($form->username) && $edit_user->username != $form->username->value ) { Session::notice( _t( '%1$s has been renamed to %2$s.', array($edit_user->username, $form->username->value) ) ); $edit_user->username = $form->username->value; $update = true; } // Change email if ( isset($form->email) && $edit_user->email != $form->email->value ) { $edit_user->email = $form->email->value; $update = true; } // Change password if ( isset($form->password1) && !(Utils::crypt($form->password1->value, $edit_user->password)) && ($form->password1->value != '') ) { Session::notice( _t( 'Password changed.' ) ); $edit_user->password = Utils::crypt( $form->password1->value ); $edit_user->update(); } // Set various info fields $info_fields = array('displayname', 'imageurl', 'locale_tz', 'locale_date_format', 'locale_time_format', 'dashboard_hide_spam_count'); // let plugins easily specify other user info fields to pick $info_fields = Plugins::filter( 'adminhandler_post_user_fields', $info_fields ); foreach ( $info_fields as $info_field ) { if ( isset($form->{$info_field}) && ($edit_user->info->{$info_field} != $form->{$info_field}->value) ) { $edit_user->info->{$info_field} = $form->$info_field->value; $update = true; } } // Let plugins tell us to update $update = Plugins::filter( 'form_user_update', $update, $form, $edit_user ); if ( $update ) { $edit_user->update(); Session::notice( _t('User updated.') ); } Utils::redirect(URL::get('admin', array('page' => 'user', 'user' => $edit_user->username))); } /** * Handles POST requests from the user profile page. */ public function post_user() { $this->get_user(); } /** * Handles AJAX from /users. * Used to delete users and fetch new ones. */ public function ajax_update_users($handler_vars) { Utils::check_request_method( array( 'POST' ) ); echo json_encode( $this->update_users( $handler_vars ) ); } /** * Update an array of POSTed users. */ public function update_users($handler_vars) { if ( isset($handler_vars['delete']) ) { $currentuser = User::identify(); $wsse = Utils::WSSE( $handler_vars['nonce'], $handler_vars['timestamp'] ); if ( isset($handler_vars['digest']) && $handler_vars['digest'] != $wsse['digest'] ) { Session::error( _t('WSSE authentication failed.') ); return Session::messages_get( true, 'array' ); } foreach ( $_POST as $id => $delete ) { // skip POST elements which are not log ids if ( preg_match( '/^p\d+/', $id ) && $delete ) { $id = substr($id, 1); $ids[] = array( 'id' => $id ); } } if ( isset( $handler_vars['checkbox_ids'] ) ) { $checkbox_ids = $handler_vars['checkbox_ids']; foreach ( $checkbox_ids as $id => $delete ) { if ( $delete ) { $ids[] = array( 'id' => $id ); } } } $count = 0; if ( ! isset($ids) ) { Session::notice( _t('No users deleted.') ); return Session::messages_get( true, 'array' ); } foreach ( $ids as $id ) { $id = $id['id']; $user = User::get_by_id( $id ); if ( $currentuser != $user ) { $assign = intval( $handler_vars['reassign'] ); if ( $user->id == $assign ) { return; } $posts = Posts::get( array( 'user_id' => $user->id, 'nolimit' => 1) ); if ( isset($posts[0]) ) { if ( 0 == $assign ) { foreach ( $posts as $post ) { $post->delete(); } } else { Posts::reassign( $assign, $posts ); } } $user->delete(); } else { $msg_status = _t('You cannot delete yourself.'); } $count++; } if ( !isset($msg_status) ) { $msg_status = sprintf( _t('Deleted %d users.'), $count ); } Session::notice( $msg_status ); } } /** * Assign values needed to display the users listing * */ private function fetch_users($params = NULL) { // prepare the WSSE tokens $this->theme->wsse = Utils::WSSE(); // Get author list $author_list = Users::get_all(); $authors[0] = _t('nobody'); foreach ( $author_list as $author ) { $authors[ $author->id ] = $author->displayname; } $this->theme->authors = $authors; } /** * Handles GET requests of the users page. */ public function get_users() { $this->fetch_users(); $this->theme->display('users'); } /** * Handles POST requests from the Users listing (ie: creating a new user) */ public function post_users() { $this->fetch_users(); $extract = $this->handler_vars->filter_keys('newuser', 'delete', 'new_pass1', 'new_pass2', 'new_email', 'new_username'); foreach ( $extract as $key => $value ) { $$key = $value; } if ( isset($newuser) ) { $action = 'newuser'; } elseif ( isset($delete) ) { $action = 'delete'; } $error = ''; if ( isset( $action ) && ( 'newuser' == $action ) ) { if ( !isset( $new_pass1 ) || !isset( $new_pass2 ) || empty( $new_pass1 ) || empty( $new_pass2 ) ) { Session::error( _t( 'Password is required.' ), 'adduser' ); } else if ( $new_pass1 !== $new_pass2 ) { Session::error( _t( 'Password mis-match.'), 'adduser' ); } if ( !isset( $new_email ) || empty( $new_email ) || ( !strstr( $new_email, '@' ) ) ) { Session::error( _t( 'Please supply a valid email address.' ), 'adduser' ); } if ( !isset( $new_username ) || empty( $new_username ) ) { Session::error( _t( 'Please supply a user name.' ), 'adduser' ); } // safety check to make sure no such username exists $user = User::get_by_name( $new_username ); if ( isset( $user->id ) ) { Session::error( _t( 'That username is already assigned.' ), 'adduser' ); } if ( !Session::has_errors( 'adduser' ) ) { $user = new User( array( 'username' => $new_username, 'email' => $new_email, 'password' => Utils::crypt( $new_pass1 ) ) ); if ( $user->insert() ) { Session::notice( sprintf( _t( "Added user '%s'" ), $new_username ) ); } else { $dberror = DB::get_last_error(); Session::error( $dberror[2], 'adduser' ); } } else { $settings = array(); if ( isset($username) ) { $settings['new_username'] = $new_username; } if ( isset( $new_email ) ) { $settings['new_email'] = $new_email; } $this->theme->assign( 'settings', $settings ); } } else if ( isset( $action ) && ( 'delete' == $action ) ) { $this->update_users($this->handler_vars); } $this->theme->display('users'); } /** * Handles plugin activation or deactivation. */ public function get_plugin_toggle() { $extract = $this->handler_vars->filter_keys('plugin_id', 'action'); foreach ( $extract as $key => $value ) { $$key = $value; } $plugins = Plugins::list_all(); foreach ( $plugins as $file ) { if ( Plugins::id_from_file($file) == $plugin_id ) { switch ( strtolower($action) ) { case 'activate': if ( Plugins::activate_plugin($file) ) { $plugins = Plugins::get_active(); Session::notice( _t( "Activated plugin '%s'", array($plugins[Plugins::id_from_file( $file )]->info->name) ), $plugins[Plugins::id_from_file($file)]->plugin_id ); } break; case 'deactivate': if ( Plugins::deactivate_plugin($file) ) { $plugins = Plugins::get_active(); Session::notice( _t( "Deactivated plugin '%s'", array($plugins[Plugins::id_from_file( $file )]->info->name) ), $plugins[Plugins::id_from_file($file)]->plugin_id ); } break; default: Plugins::act( 'adminhandler_get_plugin_toggle_action', $action, $file, $plugin_id, $plugins ); break; } } } Utils::redirect( URL::get( 'admin', 'page=plugins' ) ); } /** * A POST handler for the admin themes page that simply passes those options through. */ public function post_themes() { return $this->get_themes(); } /** * Handles GET requests for the theme listing */ public function get_themes() { $all_themes = Themes::get_all_data(); foreach ( $all_themes as $name => $theme ) { if ( isset($all_themes[$name]['info']->update) && $all_themes[$name]['info']->update != '' && isset($all_themes[$name]['info']->version) && $all_themes[$name]['info']->version != '' ) { Update::add($name, $all_themes[$name]['info']->update, $all_themes[$name]['info']->version); } } $updates = Update::check(); foreach ( $all_themes as $name => $theme ) { if ( isset($all_themes[$name]['info']->update) && isset($updates[$all_themes[$name]['info']->update]) ) { $all_themes[$name]['info']->update = $updates[$all_themes[$name]['info']->update]['latest_version']; } else { $all_themes[$name]['info']->update = ''; } } $this->theme->all_themes = $all_themes; $this->theme->active_theme = Themes::get_active_data(); $this->theme->active_theme_dir = $this->theme->active_theme['path']; // If the active theme is configurable, allow it to configure $this->theme->active_theme_name = $this->theme->active_theme['info']->name; $this->theme->configurable = Plugins::filter( 'theme_config', false, $this->active_theme); $this->theme->assign( 'configure', Controller::get_var('configure') ); $activedata = Themes::get_active_data(); $areas = array(); if ( isset($activedata['info']->areas->area) ) { foreach ( $activedata['info']->areas->area as $area ) { $areas[] = (string)$area; } } $this->theme->areas = $areas; $this->theme->blocks = Plugins::filter('block_list', array()); $this->theme->block_instances = DB::get_results('SELECT b.* FROM {blocks} b ORDER BY b.title ASC', array(), 'Block'); $blocks_areas_t = DB::get_results('SELECT b.*, ba.scope_id, ba.area FROM {blocks} b INNER JOIN {blocks_areas} ba ON ba.block_id = b.id ORDER BY ba.scope_id ASC, ba.area ASC, ba.display_order ASC', array(), 'Block'); $blocks_areas = array(); foreach ( $blocks_areas_t as $block ) { if ( !isset($blocks_areas[$block->scope_id]) ) { $blocks_areas[$block->scope_id] = array(); } $blocks_areas[$block->scope_id][$block->area][$block->display_order] = $block; } $this->theme->blocks_areas = $blocks_areas; $this->theme->scopes = DB::get_results('SELECT * FROM {scopes} ORDER BY id ASC;'); $this->theme->display( 'themes' ); } /** * Activates a theme. */ public function get_activate_theme() { $theme_name = $this->handler_vars['theme_name']; $theme_dir = $this->handler_vars['theme_dir']; if ( isset($theme_name) && isset($theme_dir) ) { Themes::activate_theme( $theme_name, $theme_dir ); } Session::notice( sprintf( _t( "Activated theme '%s'" ), $theme_name ) ); Utils::redirect( URL::get( 'admin', 'page=themes' ) ); } /** * Handles GET requests for the import page. */ public function get_import() { $importer = isset( $_POST['importer'] ) ? $_POST['importer'] : ''; $stage = isset( $_POST['stage'] ) ? $_POST['stage'] : ''; $this->theme->enctype = Plugins::filter( 'import_form_enctype', 'application/x-www-form-urlencoded', $importer, $stage ); $this->display( 'import' ); } /** * Handles the submission of the import form, importing data from a WordPress database. * This function should probably be broken into an importer class, since it is WordPress-specific. */ public function post_import() { if ( !isset( $_POST['importer'] ) ) { Utils::redirect( URL::get( 'admin', 'page=import' ) ); } $importer = isset( $_POST['importer'] ) ? $_POST['importer'] : ''; $stage = isset( $_POST['stage'] ) ? $_POST['stage'] : ''; $this->theme->enctype = Plugins::filter( 'import_form_enctype', 'application/x-www-form-urlencoded', $importer, $stage ); $this->display( 'import' ); } /** * Construct a form for a comment. * @return FormUI The comment's form. */ public function form_comment($comment, $actions) { $form = new FormUI( 'comment' ); $user = User::identify(); // Create the top description $top = $form->append('wrapper', 'buttons_1'); $top->class = 'container buttons comment overview'; $top->append('static', 'overview', $this->theme->fetch('comment.overview')); $buttons_1 = $top->append('wrapper', 'buttons_1'); $buttons_1->class = 'item buttons'; foreach ( $actions as $status => $action ) { $id = $action . '_1'; $buttons_1->append('submit', $id, _t(ucfirst($action))); $buttons_1->$id->class = 'button ' . $action; if ( Comment::status_name($comment->status) == $status ) { $buttons_1->$id->class = 'button active ' . $action; $buttons_1->$id->disabled = true; } else { $buttons_1->$id->disabled = false; } } // Content $form->append('wrapper', 'content_wrapper'); $content = $form->content_wrapper->append('textarea', 'content', 'null:null', _t('Comment'), 'admincontrol_textarea'); $content->class = 'resizable'; $content->value = $comment->content; // Create the splitter $comment_controls = $form->append('tabs', 'comment_controls'); // Create the author info $author = $comment_controls->append('fieldset', 'authorinfo', _t('Author')); $author->append('text', 'author_name', 'null:null', _t('Author Name'), 'tabcontrol_text'); $author->author_name->value = $comment->name; $author->append('text', 'author_email', 'null:null', _t('Author Email'), 'tabcontrol_text'); $author->author_email->value = $comment->email; $author->append('text', 'author_url', 'null:null', _t('Author URL'), 'tabcontrol_text'); $author->author_url->value = $comment->url; $author->append('text', 'author_ip', 'null:null', _t('IP Address:'), 'tabcontrol_text'); $author->author_ip->value = long2ip($comment->ip); // Create the advanced settings $settings = $comment_controls->append('fieldset', 'settings', _t('Settings')); $settings->append('text', 'comment_date', 'null:null', _t('Date:'), 'tabcontrol_text'); $settings->comment_date->value = $comment->date->get('Y-m-d H:i:s'); $settings->append('text', 'comment_post', 'null:null', _t('Post ID:'), 'tabcontrol_text'); $settings->comment_post->value = $comment->post->id; $statuses = Comment::list_comment_statuses( false ); $statuses = Plugins::filter( 'admin_publish_list_comment_statuses', $statuses ); $settings->append('select', 'comment_status', 'null:null', _t('Status'), $statuses, 'tabcontrol_select'); $settings->comment_status->value = $comment->status; // // Create the stats // $comment_controls->append('fieldset', 'stats_tab', _t('Stats')); // $stats = $form->stats_tab->append('wrapper', 'tags_buttons'); // $stats->class = 'container'; // // $stats->append('static', 'post_count', '<div class="container"><p class="pct25">'._t('Comments on this post:').'</p><p><strong>' . Comments::count_by_id($comment->post->id) . '</strong></p></div><hr />'); // $stats->append('static', 'ip_count', '<div class="container"><p class="pct25">'._t('Comments from this IP:').'</p><p><strong>' . Comments::count_by_ip($comment->ip) . '</strong></p></div><hr />'); // $stats->append('static', 'email_count', '<div class="container"><p class="pct25">'._t('Comments by this author:').'</p><p><strong>' . Comments::count_by_email($comment->email) . '</strong></p></div><hr />'); // $stats->append('static', 'url_count', '<div class="container"><p class="pct25">'._t('Comments with this URL:').'</p><p><strong>' . Comments::count_by_url($comment->url) . '</strong></p></div><hr />'); // Create the second set of action buttons $buttons_2 = $form->append('wrapper', 'buttons_2'); $buttons_2->class = 'container buttons comment'; foreach ( $actions as $status => $action ) { $id = $action . '_2'; $buttons_2->append('submit', $id, _t(ucfirst($action))); $buttons_2->$id->class = 'button ' . $action; if ( Comment::status_name($comment->status) == $status ) { $buttons_2->$id->class = 'button active ' . $action; $buttons_2->$id->disabled = true; } else { $buttons_2->$id->disabled = false; } } // Allow plugins to alter form Plugins::act('form_comment_edit', $form, $comment); return $form; } /** * Handles GET requests for an individual comment. */ public function get_comment($update = FALSE) { if ( isset( $this->handler_vars['id'] ) && $comment = Comment::get( $this->handler_vars['id'] ) ) { $this->theme->comment = $comment; // Convenience array to output actions twice $actions = array( 'deleted' => 'delete', 'spam' => 'spam', 'unapproved' => 'unapprove', 'approved' => 'approve', 'saved' => 'save' ); $form = $this->form_comment( $comment, $actions ); if ( $update ) { foreach ( $actions as $key => $action ) { $id_one = $action . '_1'; $id_two = $action . '_2'; if ( $form->$id_one->value != NULL || $form->$id_two->value != NULL ) { if ( $action == 'delete' ) { $comment->delete(); Utils::redirect(URL::get('admin', 'page=comments')); } if ( $action != 'save' ) { foreach ( Comment::list_comment_statuses() as $status ) { if ( $status == $key ) { $comment->status = Comment::status_name( $status ); $set_status = true; } } } } } $comment->content = $form->content; $comment->name = $form->author_name; $comment->url = $form->author_url; $comment->email = $form->author_email; $comment->ip = ip2long( $form->author_ip ); $comment->date = HabariDateTime::date_create( $form->comment_date ); $comment->post_id = $form->comment_post; if ( ! isset($set_status) ) { $comment->status = $form->comment_status->value; } $comment->update(); Plugins::act('comment_edit', $comment, $form); Utils::redirect(); } $comment->content = $form; $this->theme->form = $form; $this->display('comment'); } else { Utils::redirect(URL::get('admin', 'page=comments')); } } /** * Handles POST requests for an individual comment. */ public function post_comment() { $this->get_comment(true); } /** * Handles GET requests for the comments page. */ public function get_comments() { $this->post_comments(); } /** * Handles the submission of the comment moderation form. * @todo Separate delete from "delete until purge" */ public function post_comments() { // Get special search statuses $statuses = Comment::list_comment_statuses(); $statuses = array_combine( $statuses, array_map( create_function('$a', 'return "status:{$a}";'), $statuses ) ); // Get special search types $types = Comment::list_comment_types(); $types = array_combine( $types, array_map( create_function('$a', 'return "type:{$a}";'), $types ) ); $this->theme->special_searches = array_merge($statuses, $types); $this->fetch_comments(); $this->display( 'comments' ); } /** * Retrieve comments. */ public function fetch_comments( $params = array() ) { // Make certain handler_vars local with defaults, and add them to the theme output $locals = array( 'do_delete' => false, 'do_spam' => false, 'do_approve' => false, 'do_unapprove' => false, 'comment_ids' => null, 'nonce' => '', 'timestamp' => '', 'PasswordDigest' => '', 'mass_spam_delete' => null, 'mass_delete' => null, 'type' => 'All', 'limit' => 20, 'offset' => 0, 'search' => '', 'status' => 'All', 'orderby' => 'date DESC', ); foreach ( $locals as $varname => $default ) { $$varname = isset( $this->handler_vars[$varname] ) ? $this->handler_vars[$varname] : (isset($params[$varname]) ? $params[$varname] : $default); $this->theme->{$varname} = $$varname; } // Setting these mass_delete options prevents any other processing. Desired? if ( isset( $mass_spam_delete ) && $status == Comment::STATUS_SPAM ) { // Delete all comments that have the spam status. Comments::delete_by_status( Comment::STATUS_SPAM ); // let's optimize the table $result = DB::query('OPTIMIZE TABLE {comments}'); Session::notice( _t( 'Deleted all spam comments' ) ); Utils::redirect(); } elseif ( isset( $mass_delete ) && $status == Comment::STATUS_UNAPPROVED ) { // Delete all comments that are unapproved. Comments::delete_by_status( Comment::STATUS_UNAPPROVED ); Session::notice( _t( 'Deleted all unapproved comments' ) ); Utils::redirect(); } // if we're updating posts, let's do so: elseif ( ( $do_delete || $do_spam || $do_approve || $do_unapprove ) && isset( $comment_ids )) { $okay = true; if ( empty( $nonce ) || empty( $timestamp ) || empty( $PasswordDigest ) ) { $okay = false; } $wsse = Utils::WSSE( $nonce, $timestamp ); if ( $PasswordDigest != $wsse['digest'] ) { $okay = false; } if ( $okay ) { if ( $do_delete ) { $action = 'delete'; } elseif ( $do_spam ) { $action = 'spam'; } elseif ( $do_approve ) { $action = 'approve'; } elseif ( $do_unapprove ) { $action = 'unapprove'; } $ids = array(); foreach ( $comment_ids as $id => $id_value ) { if ( ! isset( ${'$comment_ids['.$id.']'} ) ) { // Skip unmoderated submitted comment_ids $ids[] = $id; } } $to_update = Comments::get( array( 'id' => $ids ) ); $modstatus = array( _t( 'Deleted %d comments' ) => 0, _t( 'Marked %d comments as spam' ) => 0, _t( 'Approved %d comments' ) => 0, _t( 'Unapproved %d comments' ) => 0, _t( 'Edited %d comments' ) => 0 ); Plugins::act( 'admin_moderate_comments', $action, $to_update, $this ); switch ( $action ) { case 'delete': // This comment was marked for deletion $to_update = $this->comment_access_filter( $to_update, 'delete' ); Comments::delete_these( $to_update ); $modstatus[_t( 'Deleted %d comments' )] = count( $to_update ); break; case 'spam': // This comment was marked as spam $to_update = $this->comment_access_filter( $to_update, 'edit' ); Comments::moderate_these( $to_update, Comment::STATUS_SPAM ); $modstatus[_t( 'Marked %d comments as spam' )] = count( $to_update ); break; case 'approve': case 'approved': // Comments marked for approval $to_update = $this->comment_access_filter( $to_update, 'edit' ); Comments::moderate_these( $to_update, Comment::STATUS_APPROVED ); $modstatus[_t('Approved %d comments')] = count( $to_update ); foreach ( $to_update as $comment ) { $modstatus[_t( 'Approved comments on these posts: %s' )] = (isset($modstatus[_t( 'Approved comments on these posts: %s' )])? $modstatus[_t( 'Approved comments on these posts: %s' )] . ' · ' : '') . '<a href="' . $comment->post->permalink . '">' . $comment->post->title . '</a> '; } break; case 'unapprove': case 'unapproved': // This comment was marked for unapproval $to_update = $this->comment_access_filter( $to_update, 'edit' ); Comments::moderate_these( $to_update, Comment::STATUS_UNAPPROVED ); $modstatus[_t( 'Unapproved %d comments' )] = count ( $to_update ); break; case 'edit': $to_update = $this->comment_access_filter( $to_update, 'edit' ); foreach ( $to_update as $comment ) { // This comment was edited if ( $_POST['name_' . $comment->id] != NULL ) { $comment->name = $_POST['name_' . $comment->id]; } if ( $_POST['email_' . $comment->id] != NULL ) { $comment->email = $_POST['email_' . $comment->id]; } if ( $_POST['url_' . $comment->id] != NULL ) { $comment->url = $_POST['url_' . $comment->id]; } if ( $_POST['content_' . $comment->id] != NULL ) { $comment->content = $_POST['content_' . $comment->id]; } $comment->update(); } $modstatus[_t( 'Edited %d comments' )] = count( $to_update ); break; } foreach ( $modstatus as $key => $value ) { if ( $value ) { Session::notice( sprintf( $key, $value ) ); } } } Utils::redirect(); } // we load the WSSE tokens // for use in the delete button $this->theme->wsse = Utils::WSSE(); $arguments = array( 'type' => $type, 'status' => $status, 'limit' => $limit, 'offset' => $offset, 'orderby' => $orderby, ); // only get comments the user is allowed to manage if ( !User::identify()->can( 'manage_all_comments' ) ) { $arguments['post_author'] = User::identify()->id; } // there is no explicit 'all' type/status for comments, so we need to unset these arguments // if that's what we want. At the same time we can set up the search field $this->theme->search_args = ''; if ( $type == 'All') { unset( $arguments['type'] ); } else { $this->theme->search_args = 'type:' . Comment::type_name( $type ) . ' '; } if ( $status == 'All') { unset ( $arguments['status'] ); } else { $this->theme->search_args .= 'status:' . Comment::status_name( $status ); } if ( '' != $search ) { $arguments = array_merge( $arguments, Comments::search_to_get( $search ) ); } $this->theme->comments = Comments::get( $arguments ); $monthcts = Comments::get( array_merge( $arguments, array( 'month_cts' => 1 ) ) ); $years = array(); foreach ( $monthcts as $month ) { if ( isset($years[$month->year]) ) { $years[$month->year][] = $month; } else { $years[$month->year] = array( $month ); } } $this->theme->years = $years; $baseactions = array(); $statuses = Comment::list_comment_statuses(); foreach ( $statuses as $statusid => $statusname ) { $baseactions[$statusname] = array('url' => 'javascript:itemManage.update(\'' . $statusname . '\',__commentid__);', 'title' => _t('Change this comment\'s status to %s', array($statusname)), 'label' => Comment::status_action($statusid), 'access' => 'edit' ); } /* Standard actions */ $baseactions['delete'] = array('url' => 'javascript:itemManage.update(\'delete\',__commentid__);', 'title' => _t('Delete this comment'), 'label' => _t('Delete'), 'access' => 'delete' ); $baseactions['edit'] = array('url' => URL::get('admin', 'page=comment&id=__commentid__'), 'title' => _t('Edit this comment'), 'label' => _t('Edit'), 'access' => 'edit' ); /* Allow plugins to apply actions */ $actions = Plugins::filter('comments_actions', $baseactions, $this->theme->comments); foreach ( $this->theme->comments as $comment ) { // filter the actions based on the user's permissions $comment_access = $comment->get_access(); $menu = array(); foreach ( $actions as $name => $action ) { if ( !isset( $action['access'] ) || ACL::access_check( $comment_access, $action['access'] ) ) { $menu[$name] = $action; } } // remove the current status from the dropmenu unset($menu[Comment::status_name($comment->status)]); $comment->menu = Plugins::filter('comment_actions', $menu, $comment); } } /** * A helper function for fetch_comments() * Filters a list of comments by ACL access * @param object $comments an array of Comment objects * @param string $access the access type to check for * @return a filtered array of Comment objects. */ public function comment_access_filter( $comments, $access ) { $result = array(); foreach ( $comments as $comment ) { if ( ACL::access_check( $comment->get_access(), $access ) ) { $result[] = $comment; } } return $result; } /** * A POST handler for the admin plugins page that simply passes those options through. */ public function post_plugins() { return $this->get_plugins(); } /** * Display the plugin administration page */ public function get_plugins() { $all_plugins = Plugins::list_all(); $active_plugins = Plugins::get_active(); $sort_active_plugins = array(); $sort_inactive_plugins = array(); foreach ( $all_plugins as $file ) { $plugin = array(); $plugin_id = Plugins::id_from_file( $file ); $plugin['plugin_id'] = $plugin_id; $plugin['file'] = $file; $error = ''; $providing = array(); if ( Utils::php_check_file_syntax( $file, $error ) ) { $plugin['debug'] = false; if ( array_key_exists( $plugin_id, $active_plugins ) ) { $plugin['verb'] = _t( 'Deactivate' ); $pluginobj = $active_plugins[$plugin_id]; $plugin['active'] = true; $plugin_actions = array(); $plugin_actions = Plugins::filter( 'plugin_config', $plugin_actions, $plugin_id ); $plugin['actions'] = array(); foreach ( $plugin_actions as $plugin_action => $plugin_action_caption ) { if ( is_numeric($plugin_action) ) { $plugin_action = $plugin_action_caption; } $action = array( 'caption' => $plugin_action_caption, 'action' => $plugin_action, ); $urlparams = array('page' => 'plugins', 'configure'=>$plugin_id); $action['url'] = URL::get( 'admin', $urlparams ); if ( $action['caption'] == '?' ) { if ( isset($_GET['configaction']) ) { $urlparams['configaction'] = $_GET['configaction']; } if ( $_GET['help'] != $plugin_action ) { $urlparams['help'] = $plugin_action; } $action['url'] = URL::get( 'admin', $urlparams ); $plugin['help'] = $action; } else { if ( isset($_GET['help']) ) { $urlparams['help'] = $_GET['help']; } $urlparams['configaction'] = $plugin_action; $action['url'] = URL::get( 'admin', $urlparams ); $plugin['actions'][$plugin_action] = $action; } } $plugin['actions']['deactivate'] = array( 'url' => URL::get( 'admin', 'page=plugin_toggle&plugin_id=' . $plugin['plugin_id'] . '&action=deactivate'), 'caption' => _t('Deactivate'), 'action' => 'Deactivate', ); if ( isset($plugin['info']->provides) ) { foreach ( $plugin['info']->provides->feature as $feature ) { $providing[(string) $feature] = $feature; } } } else { // instantiate this plugin // in order to get its info() $plugin['active'] = false; $plugin['verb'] = _t( 'Activate' ); $plugin['actions'] = array( 'activate' => array( 'url' => URL::get( 'admin', 'page=plugin_toggle&plugin_id=' . $plugin['plugin_id'] . '&action=activate'), 'caption' => _t('Activate'), 'action' => 'activate', ), ); } $plugin['info'] = Plugins::load_info( $file ); } else { $plugin['debug'] = true; $plugin['error'] = $error; $plugin['active'] = false; } if ( isset( $this->handler_vars['configure'] ) && ( $this->handler_vars['configure'] == $plugin['plugin_id'] ) ) { if ( i
| Referring Domain | Hits |
|---|---|
| Unknown Referer | 149 |
| pastoid.com | 28 |
Tip: Use Pastoid to shorten URLs with this bookmarklet: Pastoid This