1Building a Blog Plugin 2###################### 3 4This tutorial will teach you how to create a simple blog plugin. 5The basic functions of the blog will be creating posts, 6saving them and viewing them. 7The plugin duplicates features that are found in the 8bundled ``blog`` plugin. 9You can disable the bundled ``blog`` plugin if you wish, 10but it is not necessary since the features do not conflict 11each other. 12 13.. contents:: Contents 14 :local: 15 :depth: 1 16 17Prerequisites: 18 19 - :doc:`Install Elgg</intro/install>` 20 21Create the plugin's directory and manifest file 22=============================================== 23 24First, choose a simple and descriptive name for your plugin. 25In this tutorial, the name will be ``my_blog``. 26Then, create a directory for your plugin in the ``/mod/`` directory 27found in your Elgg installation directory. Other plugins are also located 28in ``/mod/``. In this case, the name of the directory should 29be ``/mod/my_blog/``. This directory is the root of your plugin and all the 30files that you create for the new plugin will go somewhere under it. 31 32Next, in the root of the plugin, create the plugin's manifest file, 33``manifest.xml``: 34 35.. code-block:: xml 36 37 <?xml version="1.0" encoding="UTF-8"?> 38 <plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8"> 39 <name>My Blog</name> 40 <id>my_blog</id> 41 <author>Your Name Here</author> 42 <version>0.1</version> 43 <description>Adds blogging capabilities.</description> 44 <requires> 45 <type>elgg_release</type> 46 <version>2.0</version> 47 </requires> 48 </plugin_manifest> 49 50See :doc:`Plugins</guides/plugins>` for more information 51about the manifest file. 52 53Create the form for creating a new blog post 54============================================ 55 56Create a file at ``/mod/my_blog/views/default/forms/my_blog/save.php`` 57that contains the form body. The form should have input fields for the title, 58body and tags of the my_blog post. It does not need form tag markup. 59 60.. code-block:: php 61 62 echo elgg_view_field([ 63 '#type' => 'text', 64 '#label' => elgg_echo('title'), 65 'name' => 'title', 66 'required' => true, 67 ]); 68 69 echo elgg_view_field([ 70 '#type' => 'longtext', 71 '#label' => elgg_echo('body'), 72 'name' => 'body', 73 'required' => true, 74 ]); 75 76 echo elgg_view_field([ 77 '#type' => 'tags', 78 '#label' => elgg_echo('tags'), 79 '#help' => elgg_echo('tags:help'), 80 'name' => 'tags', 81 ]); 82 83 $submit = elgg_view_field(array( 84 '#type' => 'submit', 85 '#class' => 'elgg-foot', 86 'value' => elgg_echo('save'), 87 )); 88 elgg_set_form_footer($submit); 89 90 91Notice how the form is calling ``elgg_view_field()`` to render inputs. This helper 92function maintains consistency in field markup, and is used as a shortcut for 93rendering field elements, such as label, help text, and input. See :doc:`/guides/actions`. 94 95You can see a complete list of input views in the 96``/vendor/elgg/elgg/views/default/input/`` directory. 97 98It is recommended that you make your plugin translatable by using ``elgg_echo()`` 99whenever there is a string of text that will be shown to the user. Read more at 100:doc:`Internationalization</guides/i18n>`. 101 102Create a page for composing the blogs 103===================================== 104 105Create the file ``/mod/my_blog/views/default/resources/my_blog/add.php``. 106This page will view the form you created in the above section. 107 108.. code-block:: php 109 110 <?php 111 // make sure only logged in users can see this page 112 gatekeeper(); 113 114 // set the title 115 $title = "Create a new my_blog post"; 116 117 // add the form to the main column 118 $content = elgg_view_form("my_blog/save"); 119 120 // optionally, add the content for the sidebar 121 $sidebar = ""; 122 123 // draw the page, including the HTML wrapper and basic page layout 124 echo elgg_view_page($title, [ 125 'content' => $content, 126 'sidebar' => $sidebar 127 ]); 128 129The function ``elgg_view_form("my_blog/save")`` views the form that 130you created in the previous section. It also automatically wraps 131the form with a ``<form>`` tag and the necessary attributes as well 132as anti-csrf tokens. 133 134The form's action will be ``"<?= elgg_get_site_url() ?>action/my_blog/save"``. 135 136Create the action file for saving the blog post 137=============================================== 138 139The action file will save the my_blog post to the database. 140Create the file ``/mod/my_blog/actions/my_blog/save.php``: 141 142.. code-block:: php 143 144 <?php 145 // get the form inputs 146 $title = get_input('title'); 147 $body = get_input('body'); 148 $tags = string_to_tag_array(get_input('tags')); 149 150 // create a new my_blog object and put the content in it 151 $blog = new ElggObject(); 152 $blog->title = $title; 153 $blog->description = $body; 154 $blog->tags = $tags; 155 156 // the object can and should have a subtype 157 $blog->subtype = 'my_blog'; 158 159 // for now, make all my_blog posts public 160 $blog->access_id = ACCESS_PUBLIC; 161 162 // owner is logged in user 163 $blog->owner_guid = elgg_get_logged_in_user_guid(); 164 165 // save to database and get id of the new my_blog 166 $blog_guid = $blog->save(); 167 168 // if the my_blog was saved, we want to display the new post 169 // otherwise, we want to register an error and forward back to the form 170 if ($blog_guid) { 171 system_message("Your blog post was saved."); 172 forward($blog->getURL()); 173 } else { 174 register_error("The blog post could not be saved."); 175 forward(REFERER); // REFERER is a global variable that defines the previous page 176 } 177 178As you can see in the above code, Elgg objects have several fields built 179into them. The title of the my_blog post is stored 180in the ``title`` field while the body is stored in the 181``description`` field. There is also a field for tags which are stored as 182metadata. 183 184Objects in Elgg are a subclass of something called an "entity". 185Users, sites, and groups are also subclasses of entity. 186An entity's subtype allows granular control for listing and displaying, 187which is why every entity should have a subtype. 188In this tutorial, the subtype "``my_blog``\ " identifies a my\_blog post, 189but any alphanumeric string can be a valid subtype. 190When picking subtypes, be sure to pick ones that make sense for your plugin. 191 192The ``getURL`` method fetches the URL of the new post. It is recommended 193that you override this method. The overriding will be done in the 194``start.php`` file. 195 196Create elgg-plugin.php 197====================== 198 199The ``/mod/my_blog/elgg-plugin.php`` file is used to declare various functionalities of the plugin. 200It can, for example, be used to configure entities, actions, widgets and routes. 201 202.. code-block:: php 203 204 <?php 205 206 return [ 207 'entities' => [ 208 [ 209 'type' => 'object', 210 'subtype' => 'my_blog', 211 'searchable' => true, 212 ], 213 ], 214 'actions' => [ 215 'my_blog/save' => [], 216 ], 217 'routes' => [ 218 'view:object:blog' => [ 219 'path' => '/my_blog/view/{guid}/{title?}', 220 'resource' => 'my_blog/view', 221 ], 222 'add:object:blog' => [ 223 'path' => '/my_blog/add/{guid?}', 224 'resource' => 'my_blog/add', 225 ], 226 'edit:object:blog' => [ 227 'path' => '/my_blog/edit/{guid}/{revision?}', 228 'resource' => 'my_blog/edit', 229 'requirements' => [ 230 'revision' => '\d+', 231 ], 232 ], 233 ], 234 ]; 235 236 237Create start.php 238================ 239 240The ``/mod/my_blog/start.php`` file needs to register a hook to override the URL generation. 241 242.. code-block:: php 243 244 <?php 245 246 function my_blog_init() { 247 // register a hook handler to override urls 248 elgg_register_plugin_hook_handler('entity:url', 'object', 'my_blog_set_url'); 249 } 250 251 return function() { 252 // register an initializer 253 elgg_register_event_handler('init', 'system', 'my_blog_init'); 254 } 255 256Registering the save action will make it available as ``/action/my_blog/save``. 257By default, all actions are available only to logged in users. 258If you want to make an action available to only admins or open it up to unauthenticated users, 259you can pass 'admin' or 'public' as the third parameter of ``elgg_register_action``. 260 261The URL overriding function will extract the ID of the given entity and use it to make 262a simple URL for the page that is supposed to view the entity. In this case 263the entity should of course be a my_blog post. Add this function to your 264``start.php`` file: 265 266.. code-block:: php 267 268 function my_blog_set_url($hook, $type, $url, $params) { 269 $entity = $params['entity']; 270 if ($entity->getSubtype() === 'my_blog') { 271 return "my_blog/view/{$entity->guid}"; 272 } 273 } 274 275The page handler makes it possible to serve the page that generates the form 276and the page that views the post. The next section will show how to create 277the page that views the post. Add this function to your ``start.php`` file: 278 279.. code-block:: php 280 281 function my_blog_page_handler($segments) { 282 if ($segments[0] == 'add') { 283 echo elgg_view_resource('my_blog/add'); 284 return true; 285 } 286 287 else if ($segments[0] == 'view') { 288 $resource_vars['guid'] = elgg_extract(1, $segments); 289 echo elgg_view_resource('my_blog/view', $resource_vars); 290 return true; 291 } 292 293 return false; 294 } 295 296The ``$segments`` variable contains the different parts of the URL as separated by /. 297 298Page handling functions need to return ``true`` or ``false``. ``true`` 299means the page exists and has been handled by the page handler. 300``false`` means that the page does not exist and the user will be 301forwarded to the site's 404 page (requested page does not exist or not found). 302In this particular example, the URL must contain either ``/my_blog/add`` or 303``/my_blog/view/id`` where id is a valid ID of an entity with the ``my_blog`` subtype. 304More information about page handling is at 305:doc:`Page handler</guides/routing>`. 306 307.. _tutorials/blog#view: 308 309Create a page for viewing a blog post 310===================================== 311 312To be able to view a my_blog post on its own page, you need to make a view page. 313Create the file ``/mod/my_blog/views/default/resources/my_blog/view.php``: 314 315.. code-block:: php 316 317 <?php 318 319 // get the entity 320 $guid = elgg_extract('guid', $vars); 321 $my_blog = get_entity($guid); 322 323 // get the content of the post 324 $content = elgg_view_entity($my_blog, array('full_view' => true)); 325 326 echo elgg_view_page($my_blog->getDisplayName(), [ 327 'content' => $content, 328 ]); 329 330This page has much in common with the ``add.php`` page. The biggest differences 331are that some information is extracted from the my_blog entity, and instead of 332viewing a form, the function ``elgg_view_entity`` is called. This function 333gives the information of the entity to something called the object view. 334 335Create the object view 336====================== 337 338When ``elgg_view_entity`` is called or when my_blogs are viewed in a list 339for example, the object view will generate the appropriate content. 340Create the file ``/mod/my_blog/views/default/object/my_blog.php``: 341 342.. code-block:: php 343 344 <?php 345 346 echo elgg_view('output/longtext', array('value' => $vars['entity']->description)); 347 echo elgg_view('output/tags', array('tags' => $vars['entity']->tags)); 348 349As you can see in the previous section, each my\_blog post is passed to the object 350view as ``$vars['entity']``. (``$vars`` is an array used in the views system to 351pass variables to a view.) 352 353The last line takes the tags on the my\_blog post and automatically 354displays them as a series of clickable links. Search is handled 355automatically. 356 357(If you're wondering about the "``default``" in ``/views/default/``, 358you can create alternative views. RSS, OpenDD, FOAF, mobile and others 359are all valid view types.) 360 361Trying it out 362============= 363 364Go to your Elgg site's administration page, list the plugins and activate 365the ``my_blog`` plugin. 366 367The page to create a new my\_blog post should now be accessible at 368``https://elgg.example.com/my_blog/add``, and after successfully saving the post, 369you should see it viewed on its own page. 370 371Displaying a list of blog posts 372=============================== 373 374Let's also create a page that lists my\_blog entries that have been created. 375 376Create ``/mod/my_blog/views/default/resources/my_blog/all.php``: 377 378.. code-block:: php 379 380 <?php 381 $titlebar = "All Site My_Blogs"; 382 $pagetitle = "List of all my_blogs"; 383 384 $body = elgg_list_entities(array( 385 'type' => 'object', 386 'subtype' => 'my_blog', 387 )); 388 389 echo elgg_view_page($titlebar, [ 390 'title' => $pagetitle, 391 'content' => $body, 392 ]); 393 394The ``elgg_list_entities`` function grabs the latest my_blog posts and 395passes them to the object view file. 396Note that this function returns only the posts that the user can see, 397so access restrictions are handled transparently. 398The function (and its cousins) also 399transparently handles pagination and even creates an RSS feed for your 400my\_blogs if you have defined that view. 401 402The list function can also limit the my_blog posts to those of a specified user. 403For example, the function ``elgg_get_logged_in_user_guid`` grabs the Global Unique 404IDentifier (GUID) of the logged in user, and by giving that to 405``elgg_list_entities``, the list only displays the posts of the current user: 406 407.. code-block:: php 408 409 echo elgg_list_entities(array( 410 'type' => 'object', 411 'subtype' => 'my_blog', 412 'owner_guid' => elgg_get_logged_in_user_guid() 413 )); 414 415Next, you will need to modify your my\_blog page handler to grab the new 416page when the URL is set to ``/my_blog/all``. Change the 417``my_blog_page_handler`` function in ``start.php`` to look like this: 418 419.. code-block:: php 420 421 function my_blog_page_handler($segments) { 422 switch ($segments[0]) { 423 case 'add': 424 echo elgg_view_resource('my_blog/add'); 425 break; 426 427 case 'view': 428 $resource_vars['guid'] = elgg_extract(1, $segments); 429 echo elgg_view_resource('my_blog/view', $resource_vars); 430 break; 431 432 case 'all': 433 default: 434 echo elgg_view_resource('my_blog/all'); 435 break; 436 } 437 438 return true; 439 } 440 441Now, if the URL contains ``/my_blog/all``, the user will see an 442"All Site My_Blogs" page. Because of the default case, the list of all my_blogs 443will also be shown if the URL is something invalid, 444like ``/my_blog`` or ``/my_blog/xyz``. 445 446You might also want to update the object view to handle different kinds of viewing, 447because otherwise the list of all my_blogs will also show the full content of all my_blogs. 448Change ``/mod/my_blog/views/default/object/my_blog.php`` to look like this: 449 450.. code-block:: php 451 452 <?php 453 $full = elgg_extract('full_view', $vars, FALSE); 454 455 // full view 456 if ($full) { 457 echo elgg_view('output/longtext', array('value' => $vars['entity']->description)); 458 echo elgg_view('output/tags', array('tags' => $vars['entity']->tags)); 459 460 // list view or short view 461 } else { 462 // make a link out of the post's title 463 echo elgg_view_title( 464 elgg_view('output/url', array( 465 'href' => $vars['entity']->getURL(), 466 'text' => $vars['entity']->getDisplayName(), 467 'is_trusted' => true, 468 ))); 469 echo elgg_view('output/tags', array('tags' => $vars['entity']->tags)); 470 } 471 472Now, if ``full_view`` is ``true`` (as it was pre-emptively set to be in 473:ref:`this section <tutorials/blog#view>`), the object view will show 474the post's content and tags (the title is shown by ``view.php``). 475Otherwise the object view will render just the title and 476tags of the post. 477 478The end 479======= 480 481There's much more that could be done, 482but hopefully this gives you a good idea of how to get started. 483