×

คำเตือน

JFolder::create: Path not in open_basedir paths.

สวัสดีครับ พอดีมีคนสอบถามมาว่าทำไมไม่ค่อยมีใครเขียนโค๊ดของปลั๊กอิน หาอ่านไม่ค่อยได้ ถ้าเป็นบทความหรือคนไทยก็คงใช่ครับ ไม่ค่อยจะมีแต่ถ้าเป็นภาษาอังกฤษก็มีมากเหมือนกัน วันนี้ก็เลยจะนำเสนอบทความเกี่ยวกับปลั๊กอินเรื่องการทำงานของมันก่อนแล้วกันนะครับ เพราะโค๊ดของปลัํ๊กอินเขียนน้อยครับแต่ต้องเข้าใจหลักการ แต่คนที่่คิดปลั๊กอินนี่น่าทึ่งมากครับสำหรับผมที่ไม่ได้เล่าเรียนมาทางด้านคอมพิวเตอร์หรือโปรแกรมมิ่งโดยตรง

 

ปลั๊กอินเป็นโค๊ดส่วนเล็กๆ ที่ทำงานอยู่ในลักษณะของ background สามารถทำงานได้หลายแบบเช่นการแก้ไขการแสดงผลบางส่วนหลังจากคอมโพเนนท์ทำการสร้าง HTML แล้ว หรืออาจจะการทำงานที่ไม่เกี่ยวข้องกับการแสดงผลก็ได้เหมือนกับเช่นการตรวจสอบผู้ใช้งานตอนที่ทำการล็อกอิน การส่ง e-mail เมื่อมีการบันทึกข้อมูล เป็นต้น

ประเภทของปลั๊กอิน

ประเภทของปลั๊กอินในที่นี้ก็สามารถแยกออกได้ตามการจัดกลุ่มของจูมล่าเองนะครับ โดยตัวปลั๊กอินจะอยู่ในโฟลเดอร์ plugins ภายใต้โฟลเดอร์ที่เราติดตั้งจูมล่าในเบื้องต้นก็จะประกอบไปด้วยโฟลเดอร์ย่อยตามประเภทของปลั๊กอินซึ่งประเภทหรือกลุ่มของปลั๊กอินนี้เราสามารถสร้างเพิ่มได้ ในการทำงานจูมล่าจะโหลดปลั๊กอินขึ้นมาทั้งกลุ่ม เมื่อมีเหตุการณ์เกิดขึ้นก็จะเรียกปลั๊กอินให้ทำงานทีละตัว แต่ทั้งนี้ก็ขึ้นอยู่กับว่าในปลั๊กอินนั้นๆ มีฟังก์ชั่นที่รองรับการทำงานตามเหตุการณ์นั้นๆ หรือไม่อีกทีหนึ่ง สำหรับกลุ่มของปลั๊กอินมีดังนี้ครับ

  • ส่วนขยายของเอดิเตอร์ (editors-xtd) เช่นพวกปุ่ม READ MORE หรือ PAGE BREAK ที่เราเห็นเวลาแก้ไขบทความ
  • ส่วนของการค้นหาแบบสมาร์ท (finder)
  • ส่วนของการค้นหา (search) จะถูกเรียกเมื่่อมีการค้นหา แต่ละปลั๊กอินจะทำการค้นหาประเภทรายการที่ตัวเองรับผิดชอบ และส่งรายการที่เจอกลับไป
  • ส่วนของ captcha (captcha) จะถูกเรียกใช้เมื่อมีการแสดงผลการตรวจสอล captcha
  • ส่วนของเอดิเตอร์ (editors) จะถูกเรียกใช้เมื่อมีเหตุการณ์เฉพาะของเอดิเตอร์เกิดขี้นเช่น กำหนดค่า หรืออ่านค่าจากเอดิเตอร์ หรือผู้ใช้ต้องการบันทึก
  • ส่วนของการตรวจสอบผู้ใช้ (authentication) จะถูกเรียกใช้เมื่่อจะทำการตรวจสอบผู้ใช้งานเว็บไซต์
  • ส่วนของผู้ใช้ (user) จะถูกเรียกใช้เมื่่อผู้ใช้ทำการล็อกอิน ล็อกเอาท์ และเมื่อการบันทึกข้อมูล หรือลบข้อมูลผู้ใช้
  • ส่วนของบทความ หรือเนื้อหา (content) จะถูกเรียกเมื่่อก่อนการแสดงผลหรือสร้างโค๊ด HTML หรือบันทึก และลบข้อมูลเป็นต้น
  • ส่วนของระบบ (system) เป็นส่วนของ core หรือระบบของจูมล่าเป็นส่วนแรกที่ถูกโหลดขึ้นมาทำงาน จะมีเหตุการณ์หลายตัว เช่นหลังจาก initial หลังจากรันคอมโพเนนท์แล้ว ก่อนและหลังการแสดงผลทั้งหน้าเว็บ เป็นต้น

การทำงานแบบ Observer Pattern

การทำงานของปลั๊กอินในแง่ของนักเขียนโปรแกรมก็คือการทำงานแบบ Observer Pattern นั่นคือการที่เราเขียนโค๊ดไปแปะไว้กับเหตุการณ์เพื่อบอกว่าถ้ามีเหตุการณ์นี้เกิดขึ้นให้เรียกโปรแกรมของเราด้วยนะ เช่นตอนที่ทำการตรวจสอบผู้ใช้งานโดยใช้ชื่อผู้ใช้และรหัสผ่านก็เป็นการทำงานของปลั๊กอิน ที่ตัวจูมล่าจะทำการโหลดปลั๊กอินในกลุ่ม authentication ขึ้นมาแล้วทำการเรียกใช้งานทีละตัวเพื่อดูว่ามีปลั๊กอินใดที่ยืนยันการตรวจสอบผู้ใช้หรือไม่ เหตุการณ์นี้เกิดขึ้นโดยตัวคอมโพเนนท์คือ com_users ที่สร้างเหตุการณ์นี้ขึ้น คุณสามารถดูรายละเอียดของ Observer Pattern ได้จาก Wiki ครับ

การทำงานของ Plugin

การทำงานของปลั๊กอินในจูมล่านอกจากจะมีการทำงานตามเหตุการณ์ของระบบหรือคอมโพเนนท์ต่างๆ ของจูมล่าแล้ว ตัวคอมโพเนนท์ของเราเองก็สามารถสร้างเหตุการณ์มาเพื่อเรียกให้ปลั๊กอินทำงานด้วยก็ได้ โดยปกติแล้วเราจะทำการสร้างกลุ่มหรือ ประเภทของปลั๊กอินขึ้นมาใหม่ เนื่องจากในการเรียกใช้งานปลั๊กอินแต่ละครั้งจูมล่าจะทำการโหลดจากฐานข้อมูลทั้งกลุ่มแล้วจึงเรียกใช้งานฟังก์ชั่นหรือเหตุการณ์ที่ต้องการให้ปลั๊กอินทำงาน ดูตัวอย่างในโค๊ดของจูมล่า

	public function dispatch($component = null)
	{
		try
		{
			// Get the component if not set.
			if (!$component) {
				$component = JRequest::getCmd('option');
			}

			$document	= JFactory::getDocument();
			$user		= JFactory::getUser();
			$router		= $this->getRouter();
			$params		= $this->getParams();

			switch($document->getType())
			{
				case 'html':
					// Get language
					$lang_code = JFactory::getLanguage()->getTag();
					$languages = JLanguageHelper::getLanguages('lang_code');

					// Set metadata
					if (isset($languages[$lang_code]) && $languages[$lang_code]->metakey) {
						$document->setMetaData('keywords', $languages[$lang_code]->metakey);
					} else {
						$document->setMetaData('keywords', $this->getCfg('MetaKeys'));
					}
					$document->setMetaData('rights', $this->getCfg('MetaRights'));
					if ($router->getMode() == JROUTER_MODE_SEF) {
						$document->setBase(htmlspecialchars(JURI::current()));
					}
					break;

				case 'feed':
					$document->setBase(htmlspecialchars(JURI::current()));
					break;
			}

			$document->setTitle($params->get('page_title'));
			$document->setDescription($params->get('page_description'));

			// Add version number or not based on global configuration
			if ($this->getCfg('MetaVersion', 0))
			{
				$document->setGenerator('Joomla! - Open Source Content Management  - Version ' . JVERSION);
			}
			else
			{
				$document->setGenerator('Joomla! - Open Source Content Management');
			}

			$contents = JComponentHelper::renderComponent($component);
			$document->setBuffer($contents, 'component');

			// Trigger the onAfterDispatch event.
			JPluginHelper::importPlugin('system');
			$this->triggerEvent('onAfterDispatch');
		}
		// Mop up any uncaught exceptions.
		catch (Exception $e)
		{
			$code = $e->getCode();
			JError::raiseError($code ? $code : 500, $e->getMessage());
		}
	}

 ในบรรทัดที่ 201 และ 202 จะทำการโหลดปลั๊กอินในกลุ่ม system แล้วจึิงทำการ trigger เหตุการณ์ onAfterDispatch โดยไม่มีการส่งพารามีเตอร์ใดๆ และในที่นี้ก็ไม่มีการส่งเอาท์พุทกลับมาเพื่อทำงานต่อด้วย ซึ่งจะแตกต่างกับบางกรณีที่จะมีการส่งค่าไปหาปลั๊กอินและมีการส่งค่ากลับจากปลั๊กอินเพื่่อนำมาทำงานต่อในตัวโปรแกรม สำหรับโค๊ดส่วนนี้เป็นส่วนของ application ของจูมล่าตอนที่ทำการรันคอมโพเนนท์ครับ

สำหรับส่วนของปลั๊กอินเองนั้น เราจะต้องเขียนโดยการสืบทอดคลาสจาก JPlugin ส่วนคลาสที่ใช้สำหรับเรียกให้ปลั๊กอินทำงานคือ JDispatcher ในจูมล่า 2.5 และ JEventDispatcher ในจูมล่า 3.x ซึ่งในบทความตอนต่อๆไป ผมจะเขียนถึงการพัฒนาปลั๊กอินต่อไปครับ ทิ้งท้ายด้วยตัวอย่างของปลั๊กอินครับ 

defined('JPATH_BASE') or die;

jimport('joomla.application.component.helper');

/**
 * System plugin to highlight terms.
 *
 * @package     Joomla.Plugin
 * @subpackage  System.Highlight
 * @since       2.5
 */
class PlgSystemHighlight extends JPlugin
{
	/**
	 * Method to catch the onAfterDispatch event.
	 *
	 * This is where we setup the click-through content highlighting for.
	 * The highlighting is done with JavaScript so we just
	 * need to check a few parameters and the JHtml behavior will do the rest.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   2.5
	 */
	public function onAfterDispatch()
	{
		// Check that we are in the site application.
		if (JFactory::getApplication()->isAdmin())
		{
			return true;
		}

		// Set the variables
		$input = JFactory::getApplication()->input;
		$extension = $input->get('option', '', 'cmd');

		// Check if the highlighter is enabled.
		if (!JComponentHelper::getParams($extension)->get('highlight_terms', 1))
		{
			return true;
		}

		// Check if the highlighter should be activated in this environment.
		if (JFactory::getDocument()->getType() !== 'html' || $input->get('tmpl', '', 'cmd') === 'component')
		{
			return true;
		}

		// Get the terms to highlight from the request.
		$terms = $input->request->get('highlight', null, 'base64');
		$terms = $terms ? json_decode(base64_decode($terms)) : null;

		// Check the terms.
		if (empty($terms))
		{
			return true;
		}
		
		// Clean the terms array
		$filter = JFilterInput::getInstance();

		$cleanTerms = array();
		foreach ($terms as $term)
		{
			$cleanTerms[] = htmlspecialchars($filter->clean($term, 'string'));
		}

		// Activate the highlighter.
		JHtml::_('behavior.highlighter', $cleanTerms);

		// Adjust the component buffer.
		$doc = JFactory::getDocument();
		$buf = $doc->getBuffer('component');
		$buf = '<br id="highlighter-start" />' . $buf . '<br id="highlighter-end" />';
		$doc->setBuffer($buf, 'component');

		return true;
	}
}

 สำหรับตัวอย่างด้านบนเป็นโค้ดสำหรับการทำ Highlight คือทำการค้นหาแล้วแทรกโค๊ดสำหรับการทำ highlight โดยเป็นการทำงานหลังจากรันคอมโพเนนท์แล้ว (onAfterDispatch) 

comments