porting 3.0 mods to 3.1 extensions...

General discussion of development ideas and the approaches taken in the 3.x branch of phpBB. The current feature release of phpBB 3 is 3.3/Proteus.
Forum rules
Please do not post support questions regarding installing, updating, or upgrading phpBB 3.3.x. If you need support for phpBB 3.3.x please visit the 3.3.x Support Forum on phpbb.com.

If you have questions regarding writing extensions please post in Extension Writers Discussion to receive proper guidance from our staff and community.
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: porting 3.0 mods to 3.1 extensions...

Post by asinshesq »

rxu wrote:Looking at the tutorial by Joas, it seems that creating extensions for 3.1 is rather impossible for regular community member unlike it was for 3.0 MODs.
I have the same concern, since it seems pretty involved and I suspect a lot of mod authors (like me) have never even written classes. If we spend enough time trying to copy the approach others take maybe we can get there but it does seem like it's considerably more involved (the flip side of making it super easy for end users to install). But can someone answer whether the hook system (or some other technique) allows the mod author to exit the core code at one place and then jump back in at another place lower down in the core code? Without that ability, I continue to have a hard time visualizing how some mods can be ported without that ability (though perhaps when I spend more time looking at what others have done I will suddenly say eureka?).
Alan
User avatar
nickvergessen
Former Team Member
Posts: 733
Joined: Sun Oct 07, 2007 11:54 am
Location: Stuttgart, Germany
Contact:

Re: porting 3.0 mods to 3.1 extensions...

Post by nickvergessen »

asinshesq wrote:
rxu wrote:Looking at the tutorial by Joas, it seems that creating extensions for 3.1 is rather impossible for regular community member unlike it was for 3.0 MODs.
I have the same concern, since it seems pretty involved and I suspect a lot of mod authors (like me) have never even written classes. If we spend enough time trying to copy the approach others take maybe we can get there but it does seem like it's considerably more involved (the flip side of making it super easy for end users to install).
I guess it was the same problem with 3.0
asinshesq wrote:But can someone answer whether the hook system (or some other technique) allows the mod author to exit the core code at one place and then jump back in at another place lower down in the core code? Without that ability, I continue to have a hard time visualizing how some mods can be ported without that ability (though perhaps when I spend more time looking at what others have done I will suddenly say eureka?).
Well we need to add an event for every case. So if you name an example where you need such stuff, we can add it.
Member of the Development-TeamNo Support via PM
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: porting 3.0 mods to 3.1 extensions...

Post by asinshesq »

nickvergessen wrote:
asinshesq wrote:
rxu wrote:Looking at the tutorial by Joas, it seems that creating extensions for 3.1 is rather impossible for regular community member unlike it was for 3.0 MODs.
I have the same concern, since it seems pretty involved and I suspect a lot of mod authors (like me) have never even written classes. If we spend enough time trying to copy the approach others take maybe we can get there but it does seem like it's considerably more involved (the flip side of making it super easy for end users to install).
I guess it was the same problem with 3.0
You're right, it was definitely more challenging for non-professionals to write mods for phpbb3 than for phpbb2, and one of the reasons was that we had to be able to figure out how to use some classes (another was the added mechanical hassle of using modx). Phpbb3.1 is the next natural evolution and perhaps people like me will be able to get ourselves there but the learning curve is getting steeper (which is of course the price of evolution),
nickvergessen wrote:
asinshesq wrote:But can someone answer whether the hook system (or some other technique) allows the mod author to exit the core code at one place and then jump back in at another place lower down in the core code? Without that ability, I continue to have a hard time visualizing how some mods can be ported without that ability (though perhaps when I spend more time looking at what others have done I will suddenly say eureka?).
Well we need to add an event for every case. So if you name an example where you need such stuff, we can add it.
This is the key point but let me make sure I understand it. If we have an event (I gather that's the new name for hook?) at line xyz in includes/functions_posting.php and a second event 100 lines later in that same file, will an extension writer be able to effectively cause the code execution jump from line xyz to some code the writer writes and then back to line xyz + 100 (which means that in effect the extension writer is able to do the same thing as 'replace-with')? If so, I can see how every mod can be ported (though I also think it will become much more difficult to support extensions when they conflict with other extensions).
Alan
User avatar
Pico88
Registered User
Posts: 73
Joined: Tue Apr 12, 2011 2:32 pm

Re: porting 3.0 mods to 3.1 extensions...

Post by Pico88 »

Have a look on my code from Reputation System event listner (it is doesn't fit new code (it doesn't use namespace), but you will help you undestand how events work - concentrate on viewtopic event.

Code: Select all

<?php
/**
*
* @package Reputation System
* @copyright (c) 2013 Pico88
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
*/

/**
* @ignore
*/

if (!defined('IN_PHPBB'))
{
    exit;
}

/**
* Event listener
*/
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class phpbb_ext_pico88_reputation_event_listener implements EventSubscriberInterface
{
	/**
	* Get subscribed events
	*
	* @return array
	* @static
	*/
	static public function getSubscribedEvents()
	{
		return array(
			// Event loaded on each page
			'core.common'								=> 'common_setup',

			// ACP events
			'core.acp_manage_forums_request_data'		=> 'forum_request',
			'core.acp_manage_forums_initialise_data'	=> 'forum_initialise',
			'core.acp_manage_forums_display_form'		=> 'forums_display',
			'core.permissions'							=> 'permissions_add_reputation',

			//Index event
			'core.index_modify_page_title'				=> 'index_reputation_toplist',

			// Memberlist event
			'core.memberlist_prepare_profile_data'		=> 'memberlist_add_user_reputation',

			// Viewtopic events
			'core.viewtopic_get_post_data'				=> 'viewtopic_modify_sql_array',
			'core.viewtopic_cache_guest_data'			=> 'viewtopic_cache_data',
			'core.viewtopic_cache_user_data'			=> 'viewtopic_cache_data',
			'core.viewtopic_modify_post_row'			=> 'viewtopic_postrow_add_reputation',
			'core.viewtopic_modify_page_title'			=> 'viewtopic_add_reputation',
		);
	}

	public function common_setup()
	{
		global $config, $table_prefix, $template, $phpbb_root_path, $phpEx;

		// Define additonal reputation tables
		define('REPUTATIONS_TABLE', $table_prefix . 'reputations');
		define('REPUTATIONS_RANKS_TABLE', $table_prefix . 'reputations_ranks');

		$template->assign_vars(array(
			'S_REPUTATION'			=> $config['rs_enable'] ? true : false,
		));
	}

	public function index_reputation_toplist()
	{
		global $config, $db, $user, $template;

		if (!$config['rs_enable'] && (!$config['rs_enable_toplist'] || !$config['rs_toplist_num']))
		{
			return;
		}

		$user->add_lang_ext('pico88/reputation', 'reputation_system');

		$reputation_toplist = '';
		$sql = 'SELECT user_id, username, user_colour, user_reputation
			FROM ' . USERS_TABLE . '
			WHERE user_id <> ' . ANONYMOUS . '
				AND user_reputation > 0
			ORDER BY user_reputation DESC';
		$result = $db->sql_query_limit($sql, $config['rs_toplist_num']);

		while ($row = $db->sql_fetchrow($result))
		{
			$direction = $config['rs_toplist_direction'] ? '<br />' : ', ';
			$reputation_toplist .= (($reputation_toplist != '') ? $direction : '') . get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']) . ' (' . $row['user_reputation'] . ')';
		}
		$db->sql_freeresult($result);

		$template->assign_vars(array(
			'S_RS_TOPLIST'	=> true,
			'RS_TOPLIST'	=> $config['rs_toplist_direction'] ? '<br />' . $reputation_toplist : $reputation_toplist
		));
	}

	public function forum_request($event)
	{
		$forum_data = $event['forum_data'];
		$forum_data = array_merge($forum_data, array('enable_reputation' => request_var('enable_reputation', 0)));
		$event['forum_data'] = $forum_data;
	}

	public function forum_initialise($event)
	{
		$forum_data = $event['forum_data'];
		$forum_data = array_merge($forum_data, array('enable_reputation' => false));
		$event['forum_data'] = $forum_data;
	}

	public function forums_display($event)
	{
		$template_data = $event['template_data'];
		$template_data = array_merge($template_data, array('S_ENABLE_REPUTATION' => $event['forum_data']['enable_reputation']));
		$event['template_data'] = $template_data;
	}

	public function permissions_add_reputation($event)
	{
		$categories = $event['categories'];
		$categories = array_merge($categories, array('reputation' => 'ACL_CAT_REPUTATION'));
		$event['categories'] = $categories;

		$permissions = $event['permissions'];
		$permissions = array_merge($permissions, array(
			// Admin Permissions
			'a_reputation'		=> array('lang' => 'ACL_A_REPUTATION', 'cat' => 'misc'),

			// Forum Permissions
			'f_rs_give'				=> array('lang' => 'ACL_F_RS_GIVE', 'cat' => 'reputation'),
			'f_rs_give_negative'	=> array('lang' => 'ACL_F_RS_GIVE_NEGATIVE', 'cat' => 'reputation'),

			// Moderator Permissions
			'm_rs_moderate'		=> array('lang' => 'ACL_M_RS_MODERATE', 'cat' => 'reputation'),
			'm_rs_give'			=> array('lang' => 'ACL_M_RS_GIVE', 'cat' => 'reputation'),

			// User Permissions
			'u_rs_give'		=> array('lang' => 'ACL_U_RS_GIVE', 'cat' => 'reputation'),
			'u_rs_give_negative'	=> array('lang' => 'ACL_U_RS_GIVE_NEGATIVE', 'cat' => 'reputation'),
			'u_rs_view'	=> array('lang' => 'ACL_U_RS_VIEW', 'cat' => 'reputation'),
			'u_rs_ratepost'	=> array('lang' => 'ACL_U_RS_RATEPOST', 'cat' => 'reputation'),
			'u_rs_delete'		=> array('lang' => 'ACL_U_RS_DELETE', 'cat' => 'reputation'),
		)		);
		$event['permissions'] = $permissions;
	}

	public function memberlist_add_user_reputation($event)
	{
		global $auth, $config, $phpbb_root_path, $phpEx, $user;

		$user->add_lang_ext('pico88/reputation', 'reputation_system');

		$template_data = $event['template_data'];
		$template_data['USER_ID'] = $event['data']['user_id'];
		$template_data['REPUTATION'] = $event['data']['user_reputation'];
		$template_data['U_VIEW_REP_LIST'] = ($auth->acl_get('u_rs_view')) ? $phpbb_root_path . 'reputation/' . $event['data']['user_id'] : '';
		$template_data['S_RATE_USER'] = ($config['rs_user_rating'] && $auth->acl_get('u_rs_give')) ? true : false;
		$event['template_data'] = $template_data;
	}

	public function viewtopic_modify_sql_array($event)
	{
		global $config, $user;

		if (!$config['rs_enable'] && !$config['rs_post_rating'])
		{
			return;
		}

		$sql_ary = $event['sql_ary'];

		$sql_ary['LEFT_JOIN'] = array_merge($sql_ary['LEFT_JOIN'], array(
			array(
				'FROM'	=> array(REPUTATIONS_TABLE => 'r'),
				'ON'	=> 'r.rep_from = ' . $user->data['user_id'] . ' AND r.post_id = p.post_id'
			)
		));
		$sql_ary['SELECT'] .= ', r.rep_id AS rated, r.point AS voting_points';

		$event['sql_ary'] = $sql_ary;
	}

	public function viewtopic_cache_data($event)
	{
		global $config, $user;

		if (!$config['rs_enable'] && !$config['rs_post_rating'])
		{
			return;
		}

		$user_cache_data = $event['user_cache_data'];
		$row = $event['row'];

		$user_cache_data = array_merge($user_cache_data, array(
			'reputation'		=> $row['user_reputation'],
			'post_reputation'	=> $row['post_reputation'],
			'rated' 			=> (isset($row['rated'])) ? true : false,
			'post_vote_class'	=> (isset($row['rated'])) ? ($row['voting_points'] > 0 ? 'rated_good' : 'rated_bad') : '',
			'voting_points'		=> (isset($row['voting_points'])) ? $row['voting_points'] : 0,
			'rs_rank_title'		=> '',
			'rs_rank_img'		=> '',
			'rs_rank_img_src'	=> '',
			'rs_rank_color'		=> '',
		));
		$event['user_cache_data'] = $user_cache_data;
	}

	public function viewtopic_postrow_add_reputation($event)
	{
		global $auth, $cache, $config, $db, $user, $phpbb_root_path, $phpEx;

		if (!$config['rs_enable'] && !$config['rs_post_rating'])
		{
			return;
		}

		$user_poster_data = $event['user_poster_data'];

		$rs_box_color = $this->get_vote_class($user_poster_data['post_reputation']);

		//Own post? Too low rating? Rated_good? Rated_bad?
		if ($user->data['user_id'] == $event['row']['user_id'])
		{
			$post_vote_class = 'own';
		}
		else
		{
			$post_vote_class = $user_poster_data['post_vote_class'];
		}

		if ($config['rs_ranks'])
		{
			if (($rs_ranks = $cache->get('_rs_ranks')) === false)
			{
				$sql = 'SELECT *
					FROM ' . REPUTATIONS_RANKS_TABLE . '
					ORDER BY rank_points DESC';
				$result = $db->sql_query($sql);

				$rs_ranks = array();
				while ($row = $db->sql_fetchrow($result))
				{
					$rs_ranks[] = $row;
				}
				$db->sql_freeresult($result);

				$cache->put('_rs_ranks', $rs_ranks);
			}

			foreach ($rs_ranks as $rank)
			{
				if ($user_poster_data['reputation'] >= $rank['rank_points'])
				{
					$user_poster_data['rs_rank_title'] = $rank['rank_title'];
					$user_poster_data['rs_rank_img'] = (!empty($rank['rank_image'])) ? '<img src="' . $phpbb_root_path . $config['rs_ranks_path'] . '/' . $rank['rank_image'] . '" alt="' . $rank['rank_title'] . '" title="' . $rank['rank_title'] . '" />' : '';
					$user_poster_data['rs_rank_img_src'] = (!empty($rank['rank_image'])) ? $phpbb_root_path . $config['rs_ranks_path'] . '/' . $rank['rank_image'] : '';
					$user_poster_data['rs_rank_color'] = $rank['rank_color'];
					break;
				}
			}
		}

		$post_row = $event['post_row'];
		$post_row = array_merge($post_row, array(
			'S_VIEW_REPUTATION'		=> ($auth->acl_get('u_rs_view')) ? true : false,
			'S_GIVE_REPUTATION'		=> ($auth->acl_get('f_rs_give', $event['row']['forum_id']) && $auth->acl_get('u_rs_ratepost') && $event['row']['user_id'] != ANONYMOUS) ? true : false,
			'S_GIVE_NEGATIVE'		=> ($auth->acl_get('f_rs_give_negative', $event['row']['forum_id']) && $config['rs_negative_point']) ? true : false,

			'POST_REPUTATION'		=> $user_poster_data['post_reputation'],
			'RS_BOX_COLOR'			=> $rs_box_color,
			'USER_REPUTATION'		=> $user_poster_data['reputation'],

			'RS_POST_CLASS'			=> $post_vote_class,
			'RS_GIVEN_POINT'		=> $user_poster_data['voting_points'],
			//Reputation ranks
			'RS_RANK_TITLE'			=> $user_poster_data['rs_rank_title'],
			'RS_RANK_IMG'			=> $user_poster_data['rs_rank_img'],
			'RS_RANK_IMG_SRC'		=> $user_poster_data['rs_rank_img_src'],
		));
		$event['post_row'] = $post_row;
	}

	public function viewtopic_add_reputation($event)
	{
		global $config, $user, $template;

		if (!$config['rs_enable'] && !$config['rs_post_rating'])
		{
			return;
		}

		$user->add_lang_ext('pico88/reputation', 'reputation_system');

		$topic_data = $event['topic_data'];

		$template->assign_vars(array(
			'S_FORUM_REPUTATION'	=> ($topic_data['enable_reputation'] && $topic_data['topic_type'] != POST_GLOBAL && $config['rs_post_rating']) ? true : false,
		));
	}

	/**
	* @param $points Rating points
	* @return string String value of CSS class for voting placeholder
	*/
	private static function get_vote_class($points)
	{
		if ($points > 0) 
		{
			return 'positive';
		}
		else if ($points < 0) 
		{
			return 'negative';
		}
		else
		{
			return 'zero';
		}
	}
}
At first sight it looks strange, but after some minutes you will get know to it works and how to create/rewrite mods to extensions ;)
User avatar
nickvergessen
Former Team Member
Posts: 733
Joined: Sun Oct 07, 2007 11:54 am
Location: Stuttgart, Germany
Contact:

Re: porting 3.0 mods to 3.1 extensions...

Post by nickvergessen »

asinshesq wrote:This is the key point but let me make sure I understand it. If we have an event (I gather that's the new name for hook?) at line xyz in includes/functions_posting.php and a second event 100 lines later in that same file, will an extension writer be able to effectively cause the code execution jump from line xyz to some code the writer writes and then back to line xyz + 100 (which means that in effect the extension writer is able to do the same thing as 'replace-with')? If so, I can see how every mod can be ported (though I also think it will become much more difficult to support extensions when they conflict with other extensions).
Well in some cases a replace with is only the lazy way for doing something a different way.
So if you need something like that, we can add a variable and put a if ($var) around the block
Member of the Development-TeamNo Support via PM
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: porting 3.0 mods to 3.1 extensions...

Post by asinshesq »

nickvergessen wrote:Well in some cases a replace with is only the lazy way for doing something a different way.
So if you need something like that, we can add a variable and put a if ($var) around the block
Ah, but when you say 'we' you mean the mod author rather than the developers correct? I.e. the only thing the developers would do is to insert events and it would be up to the authors to effectively use those events to surround code with comment code, if statements, etc, right?

So to take an easy example, suppose an author wants to add a parameter to the call to submit_post(). Are you saying you would have an event immediately before that call and an event immediately after that call and the author could then in effect:

1. Use the event before the call to insert:

Code: Select all

/*
2. Use the event immediately after the call to insert:

Code: Select all

*/
submit_post([regular phpbb parameters], [new parameter added by mod author])
Is that the 'right' way to do things or are there other tricks we should be thinking about?
Alan
User avatar
nickvergessen
Former Team Member
Posts: 733
Joined: Sun Oct 07, 2007 11:54 am
Location: Stuttgart, Germany
Contact:

Re: porting 3.0 mods to 3.1 extensions...

Post by nickvergessen »

No, your code must be valid php code and is executed just like a function.
So we(developers) need to add the if and you just set the var to false or something similar.
Member of the Development-TeamNo Support via PM
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: porting 3.0 mods to 3.1 extensions...

Post by asinshesq »

Ok, in the simple example of adding a parameter to a call is that the way you actually want us to do it?
Alan
Paul
Infrastructure Team Leader
Infrastructure Team Leader
Posts: 371
Joined: Thu Sep 16, 2004 9:02 am
Contact:

Re: porting 3.0 mods to 3.1 extensions...

Post by Paul »

You really should not add parameters to functions like submit_post (even in phpBB 3.0)
asinshesq
Registered User
Posts: 156
Joined: Fri May 14, 2004 10:32 pm
Location: NYC

Re: porting 3.0 mods to 3.1 extensions...

Post by asinshesq »

paulus wrote:You really should not add parameters to functions like submit_post (even in phpBB 3.0)
Really??

How should a mod author pass additional variables to functions like that? In my first mod attempts (going back to around 2004) I simply added a line in the relevant function declaring the relevant variables global, but the mod validators at the time told me that was bad practice for all but a few types of heavily used variables (like $user) so I stopped doing it. Is there another way? :o
And if not, you just saved me a lot of time porting because I doubt there's a single mod I can port without this flexibility :mrgreen:
Alan
Post Reply