Completed
Branch master (e8947e)
by Andreas
15:09
created

net_nehmer_comments_handler_view   F

Complexity

Total Complexity 55

Size/Duplication

Total Lines 408
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
dl 0
loc 408
rs 2.9203
c 0
b 0
f 0
wmc 55
lcom 1
cbo 18

11 Methods

Rating   Name   Duplication   Size   Complexity  
A _prepare_request_data() 0 8 1
A _init_display_datamanager() 0 5 1
C _load_schemadb() 0 31 7
A _init_post_controller() 0 21 3
B dm2_create_callback() 0 56 8
C _handler_comments() 0 66 11
A _process_admintoolbar() 0 20 4
B _process_post() 0 26 6
B _get_last_modified() 0 24 5
A _relocate_to_self() 0 4 1
C _show_comments() 0 38 8

How to fix   Complexity   

Complex Class

Complex classes like net_nehmer_comments_handler_view often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use net_nehmer_comments_handler_view, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package net.nehmer.comments
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * Comments view handler.
11
 *
12
 * This handler is a sigle handler which displays the thread for a given object GUID.
13
 * It checks for various commands in $_REQUEST during startup and processes them
14
 * if applicable. It relocates to the same page (using $_SERVER info) to prevent
15
 * duplicate request runs.
16
 *
17
 * @package net.nehmer.comments
18
 */
19
class net_nehmer_comments_handler_view extends midcom_baseclasses_components_handler
1 ignored issue
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
20
{
21
    /**
22
     * The schema database to use.
23
     *
24
     * @var Array
25
     */
26
    private $_schemadb = null;
27
28
    /**
29
     * List of comments we are currently working with.
30
     *
31
     * @var Array
32
     */
33
    private $_comments = null;
34
35
    /**
36
     * A new comment just created for posting.
37
     *
38
     * @var net_nehmer_comments_comment
39
     */
40
    private $_new_comment = null;
41
42
    /**
43
     * The GUID of the object we're bound to.
44
     *
45
     * @var string GUID
46
     */
47
    private $_objectguid = null;
48
49
    /**
50
     * The controller used to post a new comment. Only set if we have a valid user.
51
     *
52
     * This is a Creation Mode DM2 controller.
53
     *
54
     * @var midcom_helper_datamanager2_controller_create
55
     */
56
    private $_post_controller = null;
57
58
    /**
59
     * This datamanager instance is used to display an existing comment. only set
60
     * if there are actually comments to display.
61
     *
62
     * @var midcom_helper_datamanager2_datamanager
63
     */
64
    private $_display_datamanager = null;
65
66
    var $custom_view = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $custom_view.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
67
68
    /**
69
     * Prepares the request data
70
     */
71
    private function _prepare_request_data()
72
    {
73
        $this->_request_data['comments'] = $this->_comments;
74
        $this->_request_data['objectguid'] = $this->_objectguid;
75
        $this->_request_data['post_controller'] = $this->_post_controller;
76
        $this->_request_data['display_datamanager'] = $this->_display_datamanager;
77
        $this->_request_data['custom_view'] = $this->custom_view;
78
    }
79
80
    /**
81
     * Prepares the _display_datamanager member.
82
     */
83
    private function _init_display_datamanager()
84
    {
85
        $this->_load_schemadb();
86
        $this->_display_datamanager = new midcom_helper_datamanager2_datamanager($this->_schemadb);
87
    }
88
89
    /**
90
     * Loads the schemadb (unless it has already been loaded).
91
     */
92
    private function _load_schemadb()
93
    {
94
        if (!$this->_schemadb)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_schemadb of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
95
        {
96
            $this->_schemadb = midcom_helper_datamanager2_schema::load_database($this->_config->get('schemadb'));
97
98
            if (   $this->_config->get('use_captcha')
99
                || (   !midcom::get()->auth->user
100
                    && $this->_config->get('use_captcha_if_anonymous')))
101
            {
102
                $this->_schemadb['comment']->append_field
103
                (
104
                    'captcha',
105
                    array
106
                    (
107
                        'title' => $this->_l10n_midcom->get('captcha field title'),
108
                        'storage' => null,
109
                        'type' => 'captcha',
110
                        'widget' => 'captcha',
111
                        'widget_config' => $this->_config->get('captcha_config'),
112
                    )
113
                );
114
            }
115
116
            if (   $this->_config->get('ratings_enable')
117
                && array_key_exists('rating', $this->_schemadb['comment']->fields))
118
            {
119
                $this->_schemadb['comment']->fields['rating']['hidden'] = false;
120
            }
121
        }
122
    }
123
124
    /**
125
     * Initializes a DM2 for posting.
126
     */
127
    private function _init_post_controller()
128
    {
129
        $this->_load_schemadb();
130
131
        $defaults = array();
132
        if (midcom::get()->auth->user)
133
        {
134
            $defaults['author'] = midcom::get()->auth->user->name;
135
        }
136
137
        $this->_post_controller = midcom_helper_datamanager2_controller::create('create');
138
        $this->_post_controller->schemadb =& $this->_schemadb;
139
        $this->_post_controller->schema = 'comment';
140
        $this->_post_controller->defaults = $defaults;
141
        $this->_post_controller->callback_object =& $this;
142
143
        if (! $this->_post_controller->initialize())
144
        {
145
            throw new midcom_error('Failed to initialize a DM2 create controller.');
146
        }
147
    }
148
149
    /**
150
     * DM2 creation callback, binds the new object directly to the _objectguid.
151
     */
152
    function & dm2_create_callback (&$controller)
0 ignored issues
show
Coding Style introduced by
dm2_create_callback uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
dm2_create_callback uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
153
    {
154
        $this->_new_comment = new net_nehmer_comments_comment();
155
        $this->_new_comment->objectguid = $this->_objectguid;
156
        //Proxy check
157
        if (!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
158
        {
159
            $this->_new_comment->ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
160
        }
161
        else
162
        {
163
            $this->_new_comment->ip = $_SERVER['REMOTE_ADDR'];
164
        }
165
166
        if (midcom::get()->auth->user)
167
        {
168
            $this->_new_comment->status = net_nehmer_comments_comment::NEW_USER;
169
            $this->_new_comment->author = midcom::get()->auth->user->name;
170
        }
171
        else
172
        {
173
            $this->_new_comment->status = net_nehmer_comments_comment::NEW_ANONYMOUS;
174
        }
175
176
        if ($this->_config->get('enable_notify'))
177
        {
178
           $this->_new_comment->_send_notification = true;
179
        }
180
181
        if (! $this->_new_comment->create())
182
        {
183
            debug_print_r('We operated on this object:', $this->_new_comment);
184
            throw new midcom_error('Failed to create a new comment, cannot continue. Last Midgard error was: '. midcom_connection::get_error_string());
185
        }
186
187
        if (   isset($_POST['subscribe'])
188
            && midcom::get()->auth->user)
189
        {
190
            // User wants to subscribe to receive notifications about this comments thread
191
192
            // Get the object we're commenting
193
            $parent = midcom::get()->dbfactory->get_object_by_guid($this->_objectguid);
194
195
            // Sudo so we can update the parent object
196
            if (midcom::get()->auth->request_sudo('net.nehmer.comments'))
197
            {
198
                // Save the subscription
199
                $parent->set_parameter('net.nehmer.comments:subscription', midcom::get()->auth->user->guid, time());
200
201
                // Return back from the sudo state
202
                midcom::get()->auth->drop_sudo();
203
            }
204
        }
205
206
        return $this->_new_comment;
207
    }
208
209
    /**
210
     * Loads the comments, does any processing according to the state of the GET list.
211
     * On successful processing we relocate once to ourself.
212
     *
213
     * @param mixed $handler_id The ID of the handler.
214
     * @param array $args The argument list.
215
     * @param array &$data The local request data.
216
     */
217
    public function _handler_comments($handler_id, array $args, array &$data)
0 ignored issues
show
Coding Style introduced by
_handler_comments uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
218
    {
219
        if (! mgd_is_guid($args[0]))
220
        {
221
            throw new midcom_error("The GUID '{$args[0]}' is invalid. Cannot continue.");
222
        }
223
224
        $this->_objectguid = $args[0];
225
        midcom::get()->cache->content->register($this->_objectguid);
226
227
        if ($handler_id == 'view-comments-nonempty')
228
        {
229
            $this->_comments = net_nehmer_comments_comment::list_by_objectguid_filter_anonymous(
230
                $this->_objectguid,
231
                $this->_config->get('items_to_show'),
232
                $this->_config->get('item_ordering'),
233
                $this->_config->get('paging')
234
            );
235
        }
236
        else
237
        {
238
            $this->_comments = net_nehmer_comments_comment::list_by_objectguid(
239
                $this->_objectguid,
240
                $this->_config->get('items_to_show'),
241
                $this->_config->get('item_ordering'),
242
                $this->_config->get('paging')
243
            );
244
        }
245
246
        if ($this->_config->get('paging') !== false)
247
        {
248
            $data['qb_pager'] = $this->_comments;
249
            $this->_comments = $this->_comments->execute();
250
        }
251
252
        if (   midcom::get()->auth->user
253
            || $this->_config->get('allow_anonymous'))
254
        {
255
            $this->_init_post_controller();
256
            $this->_process_post();
257
            // This might exit.
258
        }
259
        if ($this->_comments)
260
        {
261
            $this->_init_display_datamanager();
262
        }
263
264
        $this->_process_admintoolbar();
265
        // This might exit.
266
267
        if (   $handler_id = 'view-comments-custom'
0 ignored issues
show
Unused Code introduced by
$handler_id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $handler_id = ('view-com...m' && count($args) > 1), Probably Intended Meaning: ($handler_id = 'view-com...m') && count($args) > 1
Loading history...
268
            && count($args) > 1)
269
        {
270
            midcom::get()->skip_page_style = true;
271
            $this->custom_view = $args[1];
272
        }
273
274
        $this->_prepare_request_data();
275
        midcom::get()->metadata->set_request_metadata($this->_get_last_modified(), $this->_objectguid);
276
277
        if (   isset($_SERVER['HTTP_X_REQUESTED_WITH'])
278
            && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
279
        {
280
            midcom::get()->skip_page_style = true;
281
        }
282
    }
283
284
    /**
285
     * Checks if a button of the admin toolbar was pressed. Detected by looking for the
286
     * net_nehmer_comment_adminsubmit value in the Request.
287
     *
288
     * As of this point, this tool assumes at least owner level privileges for all
289
     */
290
    private function _process_admintoolbar()
0 ignored issues
show
Coding Style introduced by
_process_admintoolbar uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
291
    {
292
        if (!array_key_exists('net_nehmer_comment_adminsubmit', $_REQUEST))
293
        {
294
            // Nothing to do.
295
            return;
296
        }
297
298
        if (array_key_exists('action_delete', $_REQUEST))
299
        {
300
            $comment = new net_nehmer_comments_comment($_REQUEST['guid']);
301
            if (!$comment->delete())
302
            {
303
                throw new midcom_error("Failed to delete comment GUID '{$_REQUEST['guid']}': " . midcom_connection::get_error_string());
304
            }
305
306
            midcom::get()->cache->invalidate($comment->objectguid);
307
            $this->_relocate_to_self();
308
        }
309
    }
310
311
    /**
312
     * Checks if a new post has been submitted.
313
     */
314
    private function _process_post()
315
    {
316
        if (   !midcom::get()->auth->user
317
            && !midcom::get()->auth->request_sudo('net.nehmer.comments'))
318
        {
319
            throw new midcom_error('We were anonymous but could not acquire SUDO privileges, aborting');
320
        }
321
322
        switch ($this->_post_controller->process_form())
323
        {
324
            case 'save':
325
                // Check against comment spam
326
                $this->_new_comment->check_spam($this->_config);
327
328
                midcom::get()->cache->invalidate($this->_objectguid);
329
                // Fall-through intentional
330
331
            case 'cancel':
332
                if (! midcom::get()->auth->user)
333
                {
334
                    midcom::get()->auth->drop_sudo();
335
                }
336
                $this->_relocate_to_self();
337
                // This will exit();
338
        }
339
    }
340
341
    /**
342
     * Determines the last modified timestamp. It is the max out of all revised timestamps
343
     * of the comments (or 0 in case nothing was found).
344
     *
345
     * @return int Last-Modified Timestamp
346
     */
347
    private function _get_last_modified()
348
    {
349
        if (! $this->_comments)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_comments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
350
        {
351
            return 0;
352
        }
353
354
        $lastmod = $this->_comments[0]->metadata->revised;
355
356
        foreach ($this->_comments as $comment)
357
        {
358
            if ($comment->metadata->revised > $lastmod)
359
            {
360
                $lastmod = $comment->metadata->revised;
361
            }
362
        }
363
364
        if ($lastmod)
365
        {
366
            return strtotime($lastmod);
367
        }
368
369
        return 0;
370
    }
371
372
    /**
373
     * This is a shortcut for midcom::get()->relocate which relocates to the very same page we
374
     * are viewing right now, including all GET parameters we had in the original request.
375
     * We do this by taking the $_SERVER['REQUEST_URI'] variable.
376
     */
377
    private function _relocate_to_self()
0 ignored issues
show
Coding Style introduced by
_relocate_to_self uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
378
    {
379
        midcom::get()->relocate($_SERVER['REQUEST_URI']);
380
    }
381
382
    /**
383
     * Display the comment list and the submit-comment form.
384
     *
385
     * @param mixed $handler_id The ID of the handler.
386
     * @param array &$data The local request data.
387
     */
388
    public function _show_comments($handler_id, array &$data)
389
    {
390
        midcom_show_style('comments-header');
391
        if ($this->_comments)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_comments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
392
        {
393
            midcom_show_style('comments-start');
394
            foreach ($this->_comments as $comment)
395
            {
396
                $this->_display_datamanager->autoset_storage($comment);
397
                $data['comment'] = $comment;
398
                $data['comment_toolbar'] = $this->_master->_populate_post_toolbar($comment);
399
                midcom_show_style('comments-item');
400
401
                if (   midcom::get()->auth->admin
402
                    || (   midcom::get()->auth->user
403
                        && $comment->can_do('midgard:delete')))
404
                {
405
                    midcom_show_style('comments-admintoolbar');
406
                }
407
            }
408
            midcom_show_style('comments-end');
409
        }
410
        else
411
        {
412
            midcom_show_style('comments-nonefound');
413
        }
414
415
        if (   midcom::get()->auth->user
416
            || $this->_config->get('allow_anonymous'))
417
        {
418
            midcom_show_style('post-comment');
419
        }
420
        else
421
        {
422
            midcom_show_style('post-denied');
423
        }
424
        midcom_show_style('comments-footer');
425
    }
426
}
427