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