Dotclear

source: inc/core/class.dc.blog.php @ 3731:3770620079d4

Revision 3731:3770620079d4, 78.3 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Simplify licence block at the beginning of each file

Line 
1<?php
2/**
3 * @package Dotclear
4 * @subpackage Core
5 *
6 * @copyright Olivier Meunier & Association Dotclear
7 * @copyright GPL-2.0-only
8 *
9 * @brief Dotclear blog class.
10 *
11 * Dotclear blog class instance is provided by dcCore $blog property.
12 */
13
14if (!defined('DC_RC_PATH')) {return;}
15
16class dcBlog
17{
18    /** @var dcCore dcCore instance */
19    protected $core;
20    /** @var connection Database connection object */
21    public $con;
22    /** @var string Database table prefix */
23    public $prefix;
24
25    /** @var string Blog ID */
26    public $id;
27    /** @var string Blog unique ID */
28    public $uid;
29    /** @var string Blog name */
30    public $name;
31    /** @var string Blog description */
32    public $desc;
33    /** @var string Blog URL */
34    public $url;
35    /** @var string Blog host */
36    public $host;
37    /** @var string Blog creation date */
38    public $creadt;
39    /** @var string Blog last update date */
40    public $upddt;
41    /** @var string Blog status */
42    public $status;
43
44    /** @var dcSettings dcSettings object */
45    public $settings;
46    /** @var string Blog theme path */
47    public $themes_path;
48    /** @var string Blog public path */
49    public $public_path;
50
51    private $post_status    = array();
52    private $comment_status = array();
53
54    private $categories;
55
56    /** @var boolean Disallow entries password protection */
57    public $without_password = true;
58
59    /**
60    Inits dcBlog object
61
62    @param    core        <b>dcCore</b>        Dotclear core reference
63    @param    id        <b>string</b>        Blog ID
64     */
65    public function __construct($core, $id)
66    {
67        $this->con    = &$core->con;
68        $this->prefix = $core->prefix;
69        $this->core   = &$core;
70
71        if (($b = $this->core->getBlog($id)) !== false) {
72            $this->id     = $id;
73            $this->uid    = $b->blog_uid;
74            $this->name   = $b->blog_name;
75            $this->desc   = $b->blog_desc;
76            $this->url    = $b->blog_url;
77            $this->host   = http::getHostFromURL($this->url);
78            $this->creadt = strtotime($b->blog_creadt);
79            $this->upddt  = strtotime($b->blog_upddt);
80            $this->status = $b->blog_status;
81
82            $this->settings = new dcSettings($this->core, $this->id);
83
84            $this->themes_path = path::fullFromRoot($this->settings->system->themes_path, DC_ROOT);
85            $this->public_path = path::fullFromRoot($this->settings->system->public_path, DC_ROOT);
86
87            $this->post_status['-2'] = __('Pending');
88            $this->post_status['-1'] = __('Scheduled');
89            $this->post_status['0']  = __('Unpublished');
90            $this->post_status['1']  = __('Published');
91
92            $this->comment_status['-2'] = __('Junk');
93            $this->comment_status['-1'] = __('Pending');
94            $this->comment_status['0']  = __('Unpublished');
95            $this->comment_status['1']  = __('Published');
96
97            # --BEHAVIOR-- coreBlogConstruct
98            $this->core->callBehavior('coreBlogConstruct', $this);
99        }
100    }
101
102    /// @name Common public methods
103    //@{
104    /**
105    Returns blog URL ending with a question mark.
106     */
107    public function getQmarkURL()
108    {
109        if (substr($this->url, -1) != '?') {
110            return $this->url . '?';
111        }
112
113        return $this->url;
114    }
115
116    /**
117    Returns jQuery version selected for the blog.
118     */
119    public function getJsJQuery()
120    {
121        $version = $this->settings->system->jquery_version;
122        if ($version == '') {
123            $version = DC_DEFAULT_JQUERY; // defined in inc/prepend.php
124        }
125        return 'jquery/' . $version;
126    }
127
128    /**
129    Returns public URL of specified plugin file.
130     */
131    public function getPF($pf, $strip_host = true)
132    {
133        $ret = $this->getQmarkURL() . 'pf=' . $pf;
134        if ($strip_host) {
135            $ret = html::stripHostURL($ret);
136        }
137        return $ret;
138    }
139
140    /**
141    Returns public URL of specified var file.
142     */
143    public function getVF($vf, $strip_host = true)
144    {
145        $ret = $this->getQmarkURL() . 'vf=' . $vf;
146        if ($strip_host) {
147            $ret = html::stripHostURL($ret);
148        }
149        return $ret;
150    }
151
152    /**
153    Returns an entry status name given to a code. Status are translated, never
154    use it for tests. If status code does not exist, returns <i>unpublished</i>.
155
156    @param    s    <b>integer</b> Status code
157    @return    <b>string</b> Blog status name
158     */
159    public function getPostStatus($s)
160    {
161        if (isset($this->post_status[$s])) {
162            return $this->post_status[$s];
163        }
164        return $this->post_status['0'];
165    }
166
167    /**
168    Returns an array of available entry status codes and names.
169
170    @return    <b>array</b> Simple array with codes in keys and names in value
171     */
172    public function getAllPostStatus()
173    {
174        return $this->post_status;
175    }
176
177    /**
178    Returns an array of available comment status codes and names.
179
180    @return    <b>array</b> Simple array with codes in keys and names in value
181     */
182    public function getAllCommentStatus()
183    {
184        return $this->comment_status;
185    }
186
187    /**
188    Disallows entries password protection. You need to set it to
189    <var>false</var> while serving a public blog.
190
191    @param    v        <b>boolean</b>
192     */
193    public function withoutPassword($v)
194    {
195        $this->without_password = (boolean) $v;
196    }
197    //@}
198
199    /// @name Triggers methods
200    //@{
201    /**
202    Updates blog last update date. Should be called every time you change
203    an element related to the blog.
204     */
205    public function triggerBlog()
206    {
207        $cur = $this->con->openCursor($this->prefix . 'blog');
208
209        $cur->blog_upddt = date('Y-m-d H:i:s');
210
211        $cur->update("WHERE blog_id = '" . $this->con->escape($this->id) . "' ");
212
213        # --BEHAVIOR-- coreBlogAfterTriggerBlog
214        $this->core->callBehavior('coreBlogAfterTriggerBlog', $cur);
215    }
216
217    /**
218    Updates comment and trackback counters in post table. Should be called
219    every time a comment or trackback is added, removed or changed its status.
220
221    @param    id        <b>integer</b>        Comment ID
222    @param    del        <b>boolean</b>        If comment is delete, set this to true
223     */
224    public function triggerComment($id, $del = false)
225    {
226        $this->triggerComments($id, $del);
227    }
228
229    /**
230    Updates comments and trackbacks counters in post table. Should be called
231    every time comments or trackbacks are added, removed or changed their status.
232
233    @param    ids        <b>mixed</b>        Comment(s) ID(s)
234    @param    del        <b>boolean</b>        If comment is delete, set this to true
235    @param    affected_posts        <b>mixed</b>        Posts(s) ID(s)
236     */
237    public function triggerComments($ids, $del = false, $affected_posts = null)
238    {
239        $comments_ids = dcUtils::cleanIds($ids);
240
241        # Get posts affected by comments edition
242        if (empty($affected_posts)) {
243            $strReq =
244            'SELECT post_id ' .
245            'FROM ' . $this->prefix . 'comment ' .
246            'WHERE comment_id' . $this->con->in($comments_ids) .
247                'GROUP BY post_id';
248
249            $rs = $this->con->select($strReq);
250
251            $affected_posts = array();
252            while ($rs->fetch()) {
253                $affected_posts[] = (integer) $rs->post_id;
254            }
255        }
256
257        if (!is_array($affected_posts) || empty($affected_posts)) {
258            return;
259        }
260
261        # Count number of comments if exists for affected posts
262        $strReq =
263        'SELECT post_id, COUNT(post_id) AS nb_comment, comment_trackback ' .
264        'FROM ' . $this->prefix . 'comment ' .
265        'WHERE comment_status = 1 ' .
266        'AND post_id' . $this->con->in($affected_posts) .
267            'GROUP BY post_id,comment_trackback';
268
269        $rs = $this->con->select($strReq);
270
271        $posts = array();
272        while ($rs->fetch()) {
273            if ($rs->comment_trackback) {
274                $posts[$rs->post_id]['trackback'] = $rs->nb_comment;
275            } else {
276                $posts[$rs->post_id]['comment'] = $rs->nb_comment;
277            }
278        }
279
280        # Update number of comments on affected posts
281        $cur = $this->con->openCursor($this->prefix . 'post');
282        foreach ($affected_posts as $post_id) {
283            $cur->clean();
284
285            if (!array_key_exists($post_id, $posts)) {
286                $cur->nb_trackback = 0;
287                $cur->nb_comment   = 0;
288            } else {
289                $cur->nb_trackback = empty($posts[$post_id]['trackback']) ? 0 : $posts[$post_id]['trackback'];
290                $cur->nb_comment   = empty($posts[$post_id]['comment']) ? 0 : $posts[$post_id]['comment'];
291            }
292
293            $cur->update('WHERE post_id = ' . $post_id);
294        }
295    }
296    //@}
297
298    /// @name Categories management methods
299    //@{
300    public function categories()
301    {
302        if (!($this->categories instanceof dcCategories)) {
303            $this->categories = new dcCategories($this->core);
304        }
305
306        return $this->categories;
307    }
308
309    /**
310    Retrieves categories. <var>$params</var> is an associative array which can
311    take the following parameters:
312
313    - post_type: Get only entries with given type (default "post")
314    - cat_url: filter on cat_url field
315    - cat_id: filter on cat_id field
316    - start: start with a given category
317    - level: categories level to retrieve
318
319    @param    params    <b>array</b>        Parameters
320    @return    <b>record</b>
321     */
322    public function getCategories($params = array())
323    {
324        $c_params = array();
325        if (isset($params['post_type'])) {
326            $c_params['post_type'] = $params['post_type'];
327            unset($params['post_type']);
328        }
329        $counter = $this->getCategoriesCounter($c_params);
330
331        if (isset($params['without_empty']) && ($params['without_empty'] == false)) {
332            $without_empty = false;
333        } else {
334            $without_empty = $this->core->auth->userID() == false; # Get all categories if in admin display
335        }
336
337        $start = isset($params['start']) ? (integer) $params['start'] : 0;
338        $l     = isset($params['level']) ? (integer) $params['level'] : 0;
339
340        $rs = $this->categories()->getChildren($start, null, 'desc');
341
342        # Get each categories total posts count
343        $data  = array();
344        $stack = array();
345        $level = 0;
346        $cols  = $rs->columns();
347        while ($rs->fetch()) {
348            $nb_post = isset($counter[$rs->cat_id]) ? (integer) $counter[$rs->cat_id] : 0;
349
350            if ($rs->level > $level) {
351                $nb_total          = $nb_post;
352                $stack[$rs->level] = (integer) $nb_post;
353            } elseif ($rs->level == $level) {
354                $nb_total = $nb_post;
355                $stack[$rs->level] += $nb_post;
356            } else {
357                $nb_total = $stack[$rs->level + 1] + $nb_post;
358                if (isset($stack[$rs->level])) {
359                    $stack[$rs->level] += $nb_total;
360                } else {
361                    $stack[$rs->level] = $nb_total;
362                }
363                unset($stack[$rs->level + 1]);
364            }
365
366            if ($nb_total == 0 && $without_empty) {
367                continue;
368            }
369
370            $level = $rs->level;
371
372            $t = array();
373            foreach ($cols as $c) {
374                $t[$c] = $rs->f($c);
375            }
376            $t['nb_post']  = $nb_post;
377            $t['nb_total'] = $nb_total;
378
379            if ($l == 0 || ($l > 0 && $l == $rs->level)) {
380                array_unshift($data, $t);
381            }
382        }
383
384        # We need to apply filter after counting
385        if (isset($params['cat_id']) && $params['cat_id'] !== '') {
386            $found = false;
387            foreach ($data as $v) {
388                if ($v['cat_id'] == $params['cat_id']) {
389                    $found = true;
390                    $data  = array($v);
391                    break;
392                }
393            }
394            if (!$found) {
395                $data = array();
396            }
397        }
398
399        if (isset($params['cat_url']) && ($params['cat_url'] !== '')
400            && !isset($params['cat_id'])) {
401            $found = false;
402            foreach ($data as $v) {
403                if ($v['cat_url'] == $params['cat_url']) {
404                    $found = true;
405                    $data  = array($v);
406                    break;
407                }
408            }
409            if (!$found) {
410                $data = array();
411            }
412        }
413
414        return staticRecord::newFromArray($data);
415    }
416
417    /**
418    Retrieves a category by its ID.
419
420    @param    id        <b>integer</b>        Category ID
421    @return    <b>record</b>
422     */
423    public function getCategory($id)
424    {
425        return $this->getCategories(array('cat_id' => $id));
426    }
427
428    /**
429    Retrieves parents of a given category.
430
431    @param    id        <b>integer</b>        Category ID
432    @return    <b>record</b>
433     */
434    public function getCategoryParents($id)
435    {
436        return $this->categories()->getParents($id);
437    }
438
439    /**
440    Retrieves first parent of a given category.
441
442    @param    id        <b>integer</b>        Category ID
443    @return    <b>record</b>
444     */
445    public function getCategoryParent($id)
446    {
447        return $this->categories()->getParent($id);
448    }
449
450    /**
451    Retrieves all category's first children
452
453    @param    id        <b>integer</b>        Category ID
454    @return    <b>record</b>
455     */
456    public function getCategoryFirstChildren($id)
457    {
458        return $this->getCategories(array('start' => $id, 'level' => $id == 0 ? 1 : 2));
459    }
460
461    private function getCategoriesCounter($params = array())
462    {
463        $strReq =
464        'SELECT  C.cat_id, COUNT(P.post_id) AS nb_post ' .
465        'FROM ' . $this->prefix . 'category AS C ' .
466        'JOIN ' . $this->prefix . "post P ON (C.cat_id = P.cat_id AND P.blog_id = '" . $this->con->escape($this->id) . "' ) " .
467        "WHERE C.blog_id = '" . $this->con->escape($this->id) . "' ";
468
469        if (!$this->core->auth->userID()) {
470            $strReq .= 'AND P.post_status = 1 ';
471        }
472
473        if (!empty($params['post_type'])) {
474            $strReq .= 'AND P.post_type ' . $this->con->in($params['post_type']);
475        }
476
477        $strReq .= 'GROUP BY C.cat_id ';
478
479        $rs       = $this->con->select($strReq);
480        $counters = array();
481        while ($rs->fetch()) {
482            $counters[$rs->cat_id] = $rs->nb_post;
483        }
484
485        return $counters;
486    }
487
488    /**
489    Creates a new category. Takes a cursor as input and returns the new category
490    ID.
491
492    @param    cur        <b>cursor</b>        Category cursor
493    @return    <b>integer</b>        New category ID
494     */
495    public function addCategory($cur, $parent = 0)
496    {
497        if (!$this->core->auth->check('categories', $this->id)) {
498            throw new Exception(__('You are not allowed to add categories'));
499        }
500
501        $url = array();
502        if ($parent != 0) {
503            $rs = $this->getCategory($parent);
504            if ($rs->isEmpty()) {
505                $url = array();
506            } else {
507                $url[] = $rs->cat_url;
508            }
509        }
510
511        if ($cur->cat_url == '') {
512            $url[] = text::tidyURL($cur->cat_title, false);
513        } else {
514            $url[] = $cur->cat_url;
515        }
516
517        $cur->cat_url = implode('/', $url);
518
519        $this->getCategoryCursor($cur);
520        $cur->blog_id = (string) $this->id;
521
522        # --BEHAVIOR-- coreBeforeCategoryCreate
523        $this->core->callBehavior('coreBeforeCategoryCreate', $this, $cur);
524
525        $id = $this->categories()->addNode($cur, $parent);
526        # Update category's cursor
527        $rs = $this->getCategory($id);
528        if (!$rs->isEmpty()) {
529            $cur->cat_lft = $rs->cat_lft;
530            $cur->cat_rgt = $rs->cat_rgt;
531        }
532
533        # --BEHAVIOR-- coreAfterCategoryCreate
534        $this->core->callBehavior('coreAfterCategoryCreate', $this, $cur);
535        $this->triggerBlog();
536
537        return $cur->cat_id;
538    }
539
540    /**
541    Updates an existing category.
542
543    @param    id        <b>integer</b>        Category ID
544    @param    cur        <b>cursor</b>        Category cursor
545     */
546    public function updCategory($id, $cur)
547    {
548        if (!$this->core->auth->check('categories', $this->id)) {
549            throw new Exception(__('You are not allowed to update categories'));
550        }
551
552        if ($cur->cat_url == '') {
553            $url = array();
554            $rs  = $this->categories()->getParents($id);
555            while ($rs->fetch()) {
556                if ($rs->index() == $rs->count() - 1) {
557                    $url[] = $rs->cat_url;
558                }
559            }
560
561            $url[]        = text::tidyURL($cur->cat_title, false);
562            $cur->cat_url = implode('/', $url);
563        }
564
565        $this->getCategoryCursor($cur, $id);
566
567        # --BEHAVIOR-- coreBeforeCategoryUpdate
568        $this->core->callBehavior('coreBeforeCategoryUpdate', $this, $cur);
569
570        $cur->update(
571            'WHERE cat_id = ' . (integer) $id . ' ' .
572            "AND blog_id = '" . $this->con->escape($this->id) . "' ");
573
574        # --BEHAVIOR-- coreAfterCategoryUpdate
575        $this->core->callBehavior('coreAfterCategoryUpdate', $this, $cur);
576
577        $this->triggerBlog();
578    }
579
580    /**
581    Set category position
582
583    @param  id              <b>integer</b>          Category ID
584    @param  left            <b>integer</b>          Category ID before
585    @param  right           <b>integer</b>          Category ID after
586     */
587    public function updCategoryPosition($id, $left, $right)
588    {
589        $this->categories()->updatePosition($id, $left, $right);
590        $this->triggerBlog();
591    }
592
593    /**
594    DEPRECATED METHOD. Use dcBlog::setCategoryParent and dcBlog::moveCategory
595    instead.
596
597    @param    id        <b>integer</b>        Category ID
598    @param    order    <b>integer</b>        Category position
599     */
600    public function updCategoryOrder($id, $order)
601    {
602        return;
603    }
604
605    /**
606    Set a category parent
607
608    @param    id        <b>integer</b>        Category ID
609    @param    parent    <b>integer</b>        Parent Category ID
610     */
611    public function setCategoryParent($id, $parent)
612    {
613        $this->categories()->setNodeParent($id, $parent);
614        $this->triggerBlog();
615    }
616
617    /**
618    Set category position
619
620    @param    id        <b>integer</b>        Category ID
621    @param    sibling    <b>integer</b>        Sibling Category ID
622    @param    move        <b>integer</b>        Order (before|after)
623     */
624    public function setCategoryPosition($id, $sibling, $move)
625    {
626        $this->categories()->setNodePosition($id, $sibling, $move);
627        $this->triggerBlog();
628    }
629
630    /**
631    Deletes a category.
632
633    @param    id        <b>integer</b>        Category ID
634     */
635    public function delCategory($id)
636    {
637        if (!$this->core->auth->check('categories', $this->id)) {
638            throw new Exception(__('You are not allowed to delete categories'));
639        }
640
641        $strReq = 'SELECT COUNT(post_id) AS nb_post ' .
642        'FROM ' . $this->prefix . 'post ' .
643        'WHERE cat_id = ' . (integer) $id . ' ' .
644        "AND blog_id = '" . $this->con->escape($this->id) . "' ";
645
646        $rs = $this->con->select($strReq);
647
648        if ($rs->nb_post > 0) {
649            throw new Exception(__('This category is not empty.'));
650        }
651
652        $this->categories()->deleteNode($id, true);
653        $this->triggerBlog();
654    }
655
656    /**
657    Reset categories order and relocate them to first level
658     */
659    public function resetCategoriesOrder()
660    {
661        if (!$this->core->auth->check('categories', $this->id)) {
662            throw new Exception(__('You are not allowed to reset categories order'));
663        }
664
665        $this->categories()->resetOrder();
666        $this->triggerBlog();
667    }
668
669    private function checkCategory($title, $url, $id = null)
670    {
671        # Let's check if URL is taken...
672        $strReq =
673        'SELECT cat_url FROM ' . $this->prefix . 'category ' .
674        "WHERE cat_url = '" . $this->con->escape($url) . "' " .
675        ($id ? 'AND cat_id <> ' . (integer) $id . ' ' : '') .
676        "AND blog_id = '" . $this->con->escape($this->id) . "' " .
677            'ORDER BY cat_url DESC';
678
679        $rs = $this->con->select($strReq);
680
681        if (!$rs->isEmpty()) {
682            if ($this->con->driver() == 'mysql' || $this->con->driver() == 'mysqli' || $this->con->driver() == 'mysqlimb4') {
683                $clause = "REGEXP '^" . $this->con->escape($url) . "[0-9]+$'";
684            } elseif ($this->con->driver() == 'pgsql') {
685                $clause = "~ '^" . $this->con->escape($url) . "[0-9]+$'";
686            } else {
687                $clause = "LIKE '" . $this->con->escape($url) . "%'";
688            }
689            $strReq =
690            'SELECT cat_url FROM ' . $this->prefix . 'category ' .
691            "WHERE cat_url " . $clause . ' ' .
692            ($id ? 'AND cat_id <> ' . (integer) $id . ' ' : '') .
693            "AND blog_id = '" . $this->con->escape($this->id) . "' " .
694                'ORDER BY cat_url DESC ';
695
696            $rs = $this->con->select($strReq);
697            $a  = array();
698            while ($rs->fetch()) {
699                $a[] = $rs->cat_url;
700            }
701
702            natsort($a);
703            $t_url = end($a);
704
705            if (preg_match('/(.*?)([0-9]+)$/', $t_url, $m)) {
706                $i   = (integer) $m[2];
707                $url = $m[1];
708            } else {
709                $i = 1;
710            }
711
712            return $url . ($i + 1);
713        }
714
715        # URL is empty?
716        if ($url == '') {
717            throw new Exception(__('Empty category URL'));
718        }
719
720        return $url;
721    }
722
723    private function getCategoryCursor($cur, $id = null)
724    {
725        if ($cur->cat_title == '') {
726            throw new Exception(__('You must provide a category title'));
727        }
728
729        # If we don't have any cat_url, let's do one
730        if ($cur->cat_url == '') {
731            $cur->cat_url = text::tidyURL($cur->cat_title, false);
732        }
733
734        # Still empty ?
735        if ($cur->cat_url == '') {
736            throw new Exception(__('You must provide a category URL'));
737        } else {
738            $cur->cat_url = text::tidyURL($cur->cat_url, true);
739        }
740
741        # Check if title or url are unique
742        $cur->cat_url = $this->checkCategory($cur->cat_title, $cur->cat_url, $id);
743
744        if ($cur->cat_desc !== null) {
745            $cur->cat_desc = $this->core->HTMLfilter($cur->cat_desc);
746        }
747    }
748    //@}
749
750    /// @name Entries management methods
751    //@{
752    /**
753    Retrieves entries. <b>$params</b> is an array taking the following
754    optionnal parameters:
755
756    - no_content: Don't retrieve entry content (excerpt and content)
757    - post_type: Get only entries with given type (default "post", array for many types and '' for no type)
758    - post_id: (integer or array) Get entry with given post_id
759    - post_url: Get entry with given post_url field
760    - user_id: (integer) Get entries belonging to given user ID
761    - cat_id: (string or array) Get entries belonging to given category ID
762    - cat_id_not: deprecated (use cat_id with "id ?not" instead)
763    - cat_url: (string or array) Get entries belonging to given category URL
764    - cat_url_not: deprecated (use cat_url with "url ?not" instead)
765    - post_status: (integer) Get entries with given post_status
766    - post_selected: (boolean) Get select flaged entries
767    - post_year: (integer) Get entries with given year
768    - post_month: (integer) Get entries with given month
769    - post_day: (integer) Get entries with given day
770    - post_lang: Get entries with given language code
771    - search: Get entries corresponding of the following search string
772    - columns: (array) More columns to retrieve
773    - sql: Append SQL string at the end of the query
774    - from: Append SQL string after "FROM" statement in query
775    - order: Order of results (default "ORDER BY post_dt DES")
776    - limit: Limit parameter
777    - sql_only : return the sql request instead of results. Only ids are selected
778    - exclude_post_id : (integer or array) Exclude entries with given post_id
779
780    Please note that on every cat_id or cat_url, you can add ?not to exclude
781    the category and ?sub to get subcategories.
782
783    @param    params        <b>array</b>        Parameters
784    @param    count_only    <b>boolean</b>        Only counts results
785    @return    <b>record</b>    A record with some more capabilities or the SQL request
786     */
787    public function getPosts($params = array(), $count_only = false)
788    {
789        # --BEHAVIOR-- coreBlogBeforeGetPosts
790        $params = new ArrayObject($params);
791        $this->core->callBehavior('coreBlogBeforeGetPosts', $params);
792
793        if ($count_only) {
794            $strReq = 'SELECT count(DISTINCT P.post_id) ';
795        } elseif (!empty($params['sql_only'])) {
796            $strReq = 'SELECT P.post_id ';
797        } else {
798            if (!empty($params['no_content'])) {
799                $content_req = '';
800            } else {
801                $content_req =
802                    'post_excerpt, post_excerpt_xhtml, ' .
803                    'post_content, post_content_xhtml, post_notes, ';
804            }
805
806            if (!empty($params['columns']) && is_array($params['columns'])) {
807                $content_req .= implode(', ', $params['columns']) . ', ';
808            }
809
810            $strReq =
811                'SELECT P.post_id, P.blog_id, P.user_id, P.cat_id, post_dt, ' .
812                'post_tz, post_creadt, post_upddt, post_format, post_password, ' .
813                'post_url, post_lang, post_title, ' . $content_req .
814                'post_type, post_meta, ' .
815                'post_status, post_firstpub, post_selected, post_position, ' .
816                'post_open_comment, post_open_tb, nb_comment, nb_trackback, ' .
817                'U.user_name, U.user_firstname, U.user_displayname, U.user_email, ' .
818                'U.user_url, ' .
819                'C.cat_title, C.cat_url, C.cat_desc ';
820        }
821
822        $strReq .=
823        'FROM ' . $this->prefix . 'post P ' .
824        'INNER JOIN ' . $this->prefix . 'user U ON U.user_id = P.user_id ' .
825        'LEFT OUTER JOIN ' . $this->prefix . 'category C ON P.cat_id = C.cat_id ';
826
827        if (!empty($params['from'])) {
828            $strReq .= $params['from'] . ' ';
829        }
830
831        $strReq .=
832        "WHERE P.blog_id = '" . $this->con->escape($this->id) . "' ";
833
834        if (!$this->core->auth->check('contentadmin', $this->id)) {
835            $strReq .= 'AND ((post_status = 1 ';
836
837            if ($this->without_password) {
838                $strReq .= 'AND post_password IS NULL ';
839            }
840            $strReq .= ') ';
841
842            if ($this->core->auth->userID()) {
843                $strReq .= "OR P.user_id = '" . $this->con->escape($this->core->auth->userID()) . "')";
844            } else {
845                $strReq .= ') ';
846            }
847        }
848
849        #Adding parameters
850        if (isset($params['post_type'])) {
851            if (is_array($params['post_type']) || $params['post_type'] != '') {
852                $strReq .= 'AND post_type ' . $this->con->in($params['post_type']);
853            }
854        } else {
855            $strReq .= "AND post_type = 'post' ";
856        }
857
858        if (isset($params['post_id']) && $params['post_id'] !== '') {
859            if (is_array($params['post_id'])) {
860                array_walk($params['post_id'], function (&$v, $k) {if ($v !== null) {$v = (integer) $v;}});
861            } else {
862                $params['post_id'] = array((integer) $params['post_id']);
863            }
864            $strReq .= 'AND P.post_id ' . $this->con->in($params['post_id']);
865        }
866
867        if (isset($params['exclude_post_id']) && $params['exclude_post_id'] !== '') {
868            if (is_array($params['exclude_post_id'])) {
869                array_walk($params['exclude_post_id'], function (&$v, $k) {if ($v !== null) {$v = (integer) $v;}});
870            } else {
871                $params['exclude_post_id'] = array((integer) $params['exclude_post_id']);
872            }
873            $strReq .= 'AND P.post_id NOT ' . $this->con->in($params['exclude_post_id']);
874        }
875
876        if (isset($params['post_url']) && $params['post_url'] !== '') {
877            $strReq .= "AND post_url = '" . $this->con->escape($params['post_url']) . "' ";
878        }
879
880        if (!empty($params['user_id'])) {
881            $strReq .= "AND U.user_id = '" . $this->con->escape($params['user_id']) . "' ";
882        }
883
884        if (isset($params['cat_id']) && $params['cat_id'] !== '') {
885            if (!is_array($params['cat_id'])) {
886                $params['cat_id'] = array($params['cat_id']);
887            }
888            if (!empty($params['cat_id_not'])) {
889                array_walk($params['cat_id'], function (&$v, $k) {$v = $v . " ?not";});
890            }
891            $strReq .= 'AND ' . $this->getPostsCategoryFilter($params['cat_id'], 'cat_id') . ' ';
892        } elseif (isset($params['cat_url']) && $params['cat_url'] !== '') {
893            if (!is_array($params['cat_url'])) {
894                $params['cat_url'] = array($params['cat_url']);
895            }
896            if (!empty($params['cat_url_not'])) {
897                array_walk($params['cat_url'], function (&$v, $k) {$v = $v . " ?not";});
898            }
899            $strReq .= 'AND ' . $this->getPostsCategoryFilter($params['cat_url'], 'cat_url') . ' ';
900        }
901
902        /* Other filters */
903        if (isset($params['post_status'])) {
904            $strReq .= 'AND post_status = ' . (integer) $params['post_status'] . ' ';
905        }
906
907        if (isset($params['post_firstpub'])) {
908            $strReq .= 'AND post_firstpub = ' . (integer) $params['post_firstpub'] . ' ';
909        }
910
911        if (isset($params['post_selected'])) {
912            $strReq .= 'AND post_selected = ' . (integer) $params['post_selected'] . ' ';
913        }
914
915        if (!empty($params['post_year'])) {
916            $strReq .= 'AND ' . $this->con->dateFormat('post_dt', '%Y') . ' = ' .
917            "'" . sprintf('%04d', $params['post_year']) . "' ";
918        }
919
920        if (!empty($params['post_month'])) {
921            $strReq .= 'AND ' . $this->con->dateFormat('post_dt', '%m') . ' = ' .
922            "'" . sprintf('%02d', $params['post_month']) . "' ";
923        }
924
925        if (!empty($params['post_day'])) {
926            $strReq .= 'AND ' . $this->con->dateFormat('post_dt', '%d') . ' = ' .
927            "'" . sprintf('%02d', $params['post_day']) . "' ";
928        }
929
930        if (!empty($params['post_lang'])) {
931            $strReq .= "AND P.post_lang = '" . $this->con->escape($params['post_lang']) . "' ";
932        }
933
934        if (!empty($params['search'])) {
935            $words = text::splitWords($params['search']);
936
937            if (!empty($words)) {
938                # --BEHAVIOR-- corePostSearch
939                if ($this->core->hasBehavior('corePostSearch')) {
940                    $this->core->callBehavior('corePostSearch', $this->core, array(&$words, &$strReq, &$params));
941                }
942
943                if ($words) {
944                    foreach ($words as $i => $w) {
945                        $words[$i] = "post_words LIKE '%" . $this->con->escape($w) . "%'";
946                    }
947                    $strReq .= 'AND ' . implode(' AND ', $words) . ' ';
948                }
949            }
950        }
951
952        if (isset($params['media'])) {
953            if ($params['media'] == '0') {
954                $strReq .= 'AND NOT ';
955            } else {
956                $strReq .= 'AND ';
957            }
958            $strReq .= 'EXISTS (SELECT M.post_id FROM ' . $this->prefix . 'post_media M ' .
959                'WHERE M.post_id = P.post_id ';
960            if (isset($params['link_type'])) {
961                $strReq .= " AND M.link_type " . $this->con->in($params['link_type']) . " ";
962            }
963            $strReq .= ")";
964        }
965
966        if (!empty($params['where'])) {
967            $strReq .= $params['where'] . ' ';
968        }
969
970        if (!empty($params['sql'])) {
971            $strReq .= $params['sql'] . ' ';
972        }
973
974        if (!$count_only) {
975            if (!empty($params['order'])) {
976                $strReq .= 'ORDER BY ' . $this->con->escape($params['order']) . ' ';
977            } else {
978                $strReq .= 'ORDER BY post_dt DESC ';
979            }
980        }
981
982        if (!$count_only && !empty($params['limit'])) {
983            $strReq .= $this->con->limit($params['limit']);
984        }
985
986        if (!empty($params['sql_only'])) {
987            return $strReq;
988        }
989
990        $rs            = $this->con->select($strReq);
991        $rs->core      = $this->core;
992        $rs->_nb_media = array();
993        $rs->extend('rsExtPost');
994
995        # --BEHAVIOR-- coreBlogGetPosts
996        $this->core->callBehavior('coreBlogGetPosts', $rs);
997
998        # --BEHAVIOR-- coreBlogAfterGetPosts
999        $alt = new arrayObject(array('rs' => null, 'params' => $params, 'count_only' => $count_only));
1000        $this->core->callBehavior('coreBlogAfterGetPosts', $rs, $alt);
1001        if ($alt['rs'] instanceof record) {
1002            $rs = $alt['rs'];
1003        }
1004
1005        return $rs;
1006    }
1007
1008    /**
1009    Returns a record with post id, title and date for next or previous post
1010    according to the post ID.
1011    $dir could be 1 (next post) or -1 (previous post).
1012
1013    @param    post_id                <b>integer</b>        Post ID
1014    @param    dir                    <b>integer</b>        Search direction
1015    @param    restrict_to_category    <b>boolean</b>        Restrict to post with same category
1016    @param    restrict_to_lang        <b>boolean</b>        Restrict to post with same lang
1017    @return    record
1018     */
1019    public function getNextPost($post, $dir, $restrict_to_category = false, $restrict_to_lang = false)
1020    {
1021        $dt      = $post->post_dt;
1022        $post_id = (integer) $post->post_id;
1023
1024        if ($dir > 0) {
1025            $sign  = '>';
1026            $order = 'ASC';
1027        } else {
1028            $sign  = '<';
1029            $order = 'DESC';
1030        }
1031
1032        $params['post_type'] = $post->post_type;
1033        $params['limit']     = 1;
1034        $params['order']     = 'post_dt ' . $order . ', P.post_id ' . $order;
1035        $params['sql']       =
1036        'AND ( ' .
1037        "   (post_dt = '" . $this->con->escape($dt) . "' AND P.post_id " . $sign . " " . $post_id . ") " .
1038        "   OR post_dt " . $sign . " '" . $this->con->escape($dt) . "' " .
1039            ') ';
1040
1041        if ($restrict_to_category) {
1042            $params['sql'] .= $post->cat_id ? 'AND P.cat_id = ' . (integer) $post->cat_id . ' ' : 'AND P.cat_id IS NULL ';
1043        }
1044
1045        if ($restrict_to_lang) {
1046            $params['sql'] .= $post->post_lang ? 'AND P.post_lang = \'' . $this->con->escape($post->post_lang) . '\' ' : 'AND P.post_lang IS NULL ';
1047        }
1048
1049        $rs = $this->getPosts($params);
1050
1051        if ($rs->isEmpty()) {
1052            return;
1053        }
1054
1055        return $rs;
1056    }
1057
1058    /**
1059    Retrieves different languages and post count on blog, based on post_lang
1060    field. <var>$params</var> is an array taking the following optionnal
1061    parameters:
1062
1063    - post_type: Get only entries with given type (default "post", '' for no type)
1064    - lang: retrieve post count for selected lang
1065    - order: order statement (default post_lang DESC)
1066
1067    @param    params    <b>array</b>        Parameters
1068    @return    record
1069     */
1070    public function getLangs($params = array())
1071    {
1072        $strReq = 'SELECT COUNT(post_id) as nb_post, post_lang ' .
1073        'FROM ' . $this->prefix . 'post ' .
1074        "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1075            "AND post_lang <> '' " .
1076            "AND post_lang IS NOT NULL ";
1077
1078        if (!$this->core->auth->check('contentadmin', $this->id)) {
1079            $strReq .= 'AND ((post_status = 1 ';
1080
1081            if ($this->without_password) {
1082                $strReq .= 'AND post_password IS NULL ';
1083            }
1084            $strReq .= ') ';
1085
1086            if ($this->core->auth->userID()) {
1087                $strReq .= "OR user_id = '" . $this->con->escape($this->core->auth->userID()) . "')";
1088            } else {
1089                $strReq .= ') ';
1090            }
1091        }
1092
1093        if (isset($params['post_type'])) {
1094            if ($params['post_type'] != '') {
1095                $strReq .= "AND post_type = '" . $this->con->escape($params['post_type']) . "' ";
1096            }
1097        } else {
1098            $strReq .= "AND post_type = 'post' ";
1099        }
1100
1101        if (isset($params['lang'])) {
1102            $strReq .= "AND post_lang = '" . $this->con->escape($params['lang']) . "' ";
1103        }
1104
1105        $strReq .= 'GROUP BY post_lang ';
1106
1107        $order = 'desc';
1108        if (!empty($params['order']) && preg_match('/^(desc|asc)$/i', $params['order'])) {
1109            $order = $params['order'];
1110        }
1111        $strReq .= 'ORDER BY post_lang ' . $order . ' ';
1112
1113        return $this->con->select($strReq);
1114    }
1115
1116    /**
1117    Returns a record with all distinct blog dates and post count.
1118    <var>$params</var> is an array taking the following optionnal parameters:
1119
1120    - type: (day|month|year) Get days, months or years
1121    - year: (integer) Get dates for given year
1122    - month: (integer) Get dates for given month
1123    - day: (integer) Get dates for given day
1124    - cat_id: (integer) Category ID filter
1125    - cat_url: Category URL filter
1126    - post_lang: lang of the posts
1127    - next: Get date following match
1128    - previous: Get date before match
1129    - order: Sort by date "ASC" or "DESC"
1130
1131    @param    params    <b>array</b>        Parameters array
1132    @return    record
1133     */
1134    public function getDates($params = array())
1135    {
1136        $dt_f  = '%Y-%m-%d';
1137        $dt_fc = '%Y%m%d';
1138        if (isset($params['type'])) {
1139            if ($params['type'] == 'year') {
1140                $dt_f  = '%Y-01-01';
1141                $dt_fc = '%Y0101';
1142            } elseif ($params['type'] == 'month') {
1143                $dt_f  = '%Y-%m-01';
1144                $dt_fc = '%Y%m01';
1145            }
1146        }
1147        $dt_f .= ' 00:00:00';
1148        $dt_fc .= '000000';
1149
1150        $cat_field = $catReq = $limit = '';
1151
1152        if (isset($params['cat_id']) && $params['cat_id'] !== '') {
1153            $catReq    = 'AND P.cat_id = ' . (integer) $params['cat_id'] . ' ';
1154            $cat_field = ', C.cat_url ';
1155        } elseif (isset($params['cat_url']) && $params['cat_url'] !== '') {
1156            $catReq    = "AND C.cat_url = '" . $this->con->escape($params['cat_url']) . "' ";
1157            $cat_field = ', C.cat_url ';
1158        }
1159        if (!empty($params['post_lang'])) {
1160            $catReq = 'AND P.post_lang = \'' . $params['post_lang'] . '\' ';
1161        }
1162
1163        $strReq = 'SELECT DISTINCT(' . $this->con->dateFormat('post_dt', $dt_f) . ') AS dt ' .
1164        $cat_field .
1165        ',COUNT(P.post_id) AS nb_post ' .
1166        'FROM ' . $this->prefix . 'post P LEFT JOIN ' . $this->prefix . 'category C ' .
1167        'ON P.cat_id = C.cat_id ' .
1168        "WHERE P.blog_id = '" . $this->con->escape($this->id) . "' " .
1169            $catReq;
1170
1171        if (!$this->core->auth->check('contentadmin', $this->id)) {
1172            $strReq .= 'AND ((post_status = 1 ';
1173
1174            if ($this->without_password) {
1175                $strReq .= 'AND post_password IS NULL ';
1176            }
1177            $strReq .= ') ';
1178
1179            if ($this->core->auth->userID()) {
1180                $strReq .= "OR P.user_id = '" . $this->con->escape($this->core->auth->userID()) . "')";
1181            } else {
1182                $strReq .= ') ';
1183            }
1184        }
1185
1186        if (!empty($params['post_type'])) {
1187            $strReq .= "AND post_type " . $this->con->in($params['post_type']) . " ";
1188        } else {
1189            $strReq .= "AND post_type = 'post' ";
1190        }
1191
1192        if (!empty($params['year'])) {
1193            $strReq .= 'AND ' . $this->con->dateFormat('post_dt', '%Y') . " = '" . sprintf('%04d', $params['year']) . "' ";
1194        }
1195
1196        if (!empty($params['month'])) {
1197            $strReq .= 'AND ' . $this->con->dateFormat('post_dt', '%m') . " = '" . sprintf('%02d', $params['month']) . "' ";
1198        }
1199
1200        if (!empty($params['day'])) {
1201            $strReq .= 'AND ' . $this->con->dateFormat('post_dt', '%d') . " = '" . sprintf('%02d', $params['day']) . "' ";
1202        }
1203
1204        # Get next or previous date
1205        if (!empty($params['next']) || !empty($params['previous'])) {
1206            if (!empty($params['next'])) {
1207                $pdir            = ' > ';
1208                $params['order'] = 'asc';
1209                $dt              = $params['next'];
1210            } else {
1211                $pdir            = ' < ';
1212                $params['order'] = 'desc';
1213                $dt              = $params['previous'];
1214            }
1215
1216            $dt = date('YmdHis', strtotime($dt));
1217
1218            $strReq .= 'AND ' . $this->con->dateFormat('post_dt', $dt_fc) . $pdir . "'" . $dt . "' ";
1219            $limit = $this->con->limit(1);
1220        }
1221
1222        $strReq .= 'GROUP BY dt ' . $cat_field;
1223
1224        $order = 'desc';
1225        if (!empty($params['order']) && preg_match('/^(desc|asc)$/i', $params['order'])) {
1226            $order = $params['order'];
1227        }
1228
1229        $strReq .=
1230            'ORDER BY dt ' . $order . ' ' .
1231            $limit;
1232
1233        $rs = $this->con->select($strReq);
1234        $rs->extend('rsExtDates');
1235        return $rs;
1236    }
1237
1238    /**
1239    Creates a new entry. Takes a cursor as input and returns the new entry
1240    ID.
1241
1242    @param    cur        <b>cursor</b>        Post cursor
1243    @return    <b>integer</b>        New post ID
1244     */
1245    public function addPost($cur)
1246    {
1247        if (!$this->core->auth->check('usage,contentadmin', $this->id)) {
1248            throw new Exception(__('You are not allowed to create an entry'));
1249        }
1250
1251        $this->con->writeLock($this->prefix . 'post');
1252        try
1253        {
1254            # Get ID
1255            $rs = $this->con->select(
1256                'SELECT MAX(post_id) ' .
1257                'FROM ' . $this->prefix . 'post '
1258            );
1259
1260            $cur->post_id     = (integer) $rs->f(0) + 1;
1261            $cur->blog_id     = (string) $this->id;
1262            $cur->post_creadt = date('Y-m-d H:i:s');
1263            $cur->post_upddt  = date('Y-m-d H:i:s');
1264            $cur->post_tz     = $this->core->auth->getInfo('user_tz');
1265
1266            # Post excerpt and content
1267            $this->getPostContent($cur, $cur->post_id);
1268
1269            $this->getPostCursor($cur);
1270
1271            $cur->post_url = $this->getPostURL($cur->post_url, $cur->post_dt, $cur->post_title, $cur->post_id);
1272
1273            if (!$this->core->auth->check('publish,contentadmin', $this->id)) {
1274                $cur->post_status = -2;
1275            }
1276
1277            # --BEHAVIOR-- coreBeforePostCreate
1278            $this->core->callBehavior('coreBeforePostCreate', $this, $cur);
1279
1280            $cur->insert();
1281            $this->con->unlock();
1282        } catch (Exception $e) {
1283            $this->con->unlock();
1284            throw $e;
1285        }
1286
1287        # --BEHAVIOR-- coreAfterPostCreate
1288        $this->core->callBehavior('coreAfterPostCreate', $this, $cur);
1289
1290        $this->triggerBlog();
1291
1292        $this->firstPublicationEntries($cur->post_id);
1293
1294        return $cur->post_id;
1295    }
1296
1297    /**
1298    Updates an existing post.
1299
1300    @param    id        <b>integer</b>        Post ID
1301    @param    cur        <b>cursor</b>        Post cursor
1302     */
1303    public function updPost($id, $cur)
1304    {
1305        if (!$this->core->auth->check('usage,contentadmin', $this->id)) {
1306            throw new Exception(__('You are not allowed to update entries'));
1307        }
1308
1309        $id = (integer) $id;
1310
1311        if (empty($id)) {
1312            throw new Exception(__('No such entry ID'));
1313        }
1314
1315        # Post excerpt and content
1316        $this->getPostContent($cur, $id);
1317
1318        $this->getPostCursor($cur);
1319
1320        if ($cur->post_url !== null) {
1321            $cur->post_url = $this->getPostURL($cur->post_url, $cur->post_dt, $cur->post_title, $id);
1322        }
1323
1324        if (!$this->core->auth->check('publish,contentadmin', $this->id)) {
1325            $cur->unsetField('post_status');
1326        }
1327
1328        $cur->post_upddt = date('Y-m-d H:i:s');
1329
1330        #If user is only "usage", we need to check the post's owner
1331        if (!$this->core->auth->check('contentadmin', $this->id)) {
1332            $strReq = 'SELECT post_id ' .
1333            'FROM ' . $this->prefix . 'post ' .
1334            'WHERE post_id = ' . $id . ' ' .
1335            "AND user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
1336
1337            $rs = $this->con->select($strReq);
1338
1339            if ($rs->isEmpty()) {
1340                throw new Exception(__('You are not allowed to edit this entry'));
1341            }
1342        }
1343
1344        # --BEHAVIOR-- coreBeforePostUpdate
1345        $this->core->callBehavior('coreBeforePostUpdate', $this, $cur);
1346
1347        $cur->update('WHERE post_id = ' . $id . ' ');
1348
1349        # --BEHAVIOR-- coreAfterPostUpdate
1350        $this->core->callBehavior('coreAfterPostUpdate', $this, $cur);
1351
1352        $this->triggerBlog();
1353
1354        $this->firstPublicationEntries($id);
1355    }
1356
1357    /**
1358    Updates post status.
1359
1360    @param    id        <b>integer</b>        Post ID
1361    @param    status    <b>integer</b>        Post status
1362     */
1363    public function updPostStatus($id, $status)
1364    {
1365        $this->updPostsStatus($id, $status);
1366    }
1367
1368    /**
1369    Updates posts status.
1370
1371    @param    ids        <b>mixed</b>        Post(s) ID(s)
1372    @param    status    <b>integer</b>        Post status
1373     */
1374    public function updPostsStatus($ids, $status)
1375    {
1376        if (!$this->core->auth->check('publish,contentadmin', $this->id)) {
1377            throw new Exception(__('You are not allowed to change this entry status'));
1378        }
1379
1380        $posts_ids = dcUtils::cleanIds($ids);
1381        $status    = (integer) $status;
1382
1383        $strReq = "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1384        "AND post_id " . $this->con->in($posts_ids);
1385
1386        #If user can only publish, we need to check the post's owner
1387        if (!$this->core->auth->check('contentadmin', $this->id)) {
1388            $strReq .= "AND user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
1389        }
1390
1391        $cur = $this->con->openCursor($this->prefix . 'post');
1392
1393        $cur->post_status = $status;
1394        $cur->post_upddt  = date('Y-m-d H:i:s');
1395
1396        $cur->update($strReq);
1397        $this->triggerBlog();
1398
1399        $this->firstPublicationEntries($posts_ids);
1400    }
1401
1402    /**
1403    Updates post selection.
1404
1405    @param    id        <b>integer</b>        Post ID
1406    @param    selected    <b>integer</b>        Is selected post
1407     */
1408    public function updPostSelected($id, $selected)
1409    {
1410        $this->updPostsSelected($id, $selected);
1411    }
1412
1413    /**
1414    Updates posts selection.
1415
1416    @param    ids        <b>mixed</b>        Post(s) ID(s)
1417    @param    selected    <b>integer</b>        Is selected post(s)
1418     */
1419    public function updPostsSelected($ids, $selected)
1420    {
1421        if (!$this->core->auth->check('usage,contentadmin', $this->id)) {
1422            throw new Exception(__('You are not allowed to change this entry category'));
1423        }
1424
1425        $posts_ids = dcUtils::cleanIds($ids);
1426        $selected  = (boolean) $selected;
1427
1428        $strReq = "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1429        "AND post_id " . $this->con->in($posts_ids);
1430
1431        # If user is only usage, we need to check the post's owner
1432        if (!$this->core->auth->check('contentadmin', $this->id)) {
1433            $strReq .= "AND user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
1434        }
1435
1436        $cur = $this->con->openCursor($this->prefix . 'post');
1437
1438        $cur->post_selected = (integer) $selected;
1439        $cur->post_upddt    = date('Y-m-d H:i:s');
1440
1441        $cur->update($strReq);
1442        $this->triggerBlog();
1443    }
1444
1445    /**
1446    Updates post category. <var>$cat_id</var> can be null.
1447
1448    @param    id        <b>integer</b>        Post ID
1449    @param    cat_id    <b>integer</b>        Category ID
1450     */
1451    public function updPostCategory($id, $cat_id)
1452    {
1453        $this->updPostsCategory($id, $cat_id);
1454    }
1455
1456    /**
1457    Updates posts category. <var>$cat_id</var> can be null.
1458
1459    @param    ids        <b>mixed</b>        Post(s) ID(s)
1460    @param    cat_id    <b>integer</b>        Category ID
1461     */
1462    public function updPostsCategory($ids, $cat_id)
1463    {
1464        if (!$this->core->auth->check('usage,contentadmin', $this->id)) {
1465            throw new Exception(__('You are not allowed to change this entry category'));
1466        }
1467
1468        $posts_ids = dcUtils::cleanIds($ids);
1469        $cat_id    = (integer) $cat_id;
1470
1471        $strReq = "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1472        "AND post_id " . $this->con->in($posts_ids);
1473
1474        # If user is only usage, we need to check the post's owner
1475        if (!$this->core->auth->check('contentadmin', $this->id)) {
1476            $strReq .= "AND user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
1477        }
1478
1479        $cur = $this->con->openCursor($this->prefix . 'post');
1480
1481        $cur->cat_id     = ($cat_id ?: null);
1482        $cur->post_upddt = date('Y-m-d H:i:s');
1483
1484        $cur->update($strReq);
1485        $this->triggerBlog();
1486    }
1487
1488    /**
1489    Updates posts category. <var>$new_cat_id</var> can be null.
1490
1491    @param    old_cat_id    <b>integer</b>        Old category ID
1492    @param    new_cat_id    <b>integer</b>        New category ID
1493     */
1494    public function changePostsCategory($old_cat_id, $new_cat_id)
1495    {
1496        if (!$this->core->auth->check('contentadmin,categories', $this->id)) {
1497            throw new Exception(__('You are not allowed to change entries category'));
1498        }
1499
1500        $old_cat_id = (integer) $old_cat_id;
1501        $new_cat_id = (integer) $new_cat_id;
1502
1503        $cur = $this->con->openCursor($this->prefix . 'post');
1504
1505        $cur->cat_id     = ($new_cat_id ?: null);
1506        $cur->post_upddt = date('Y-m-d H:i:s');
1507
1508        $cur->update(
1509            'WHERE cat_id = ' . $old_cat_id . ' ' .
1510            "AND blog_id = '" . $this->con->escape($this->id) . "' "
1511        );
1512        $this->triggerBlog();
1513    }
1514
1515    /**
1516    Deletes a post.
1517
1518    @param    id        <b>integer</b>        Post ID
1519     */
1520    public function delPost($id)
1521    {
1522        $this->delPosts($id);
1523    }
1524
1525    /**
1526    Deletes multiple posts.
1527
1528    @param    ids        <b>mixed</b>        Post(s) ID(s)
1529     */
1530    public function delPosts($ids)
1531    {
1532        if (!$this->core->auth->check('delete,contentadmin', $this->id)) {
1533            throw new Exception(__('You are not allowed to delete entries'));
1534        }
1535
1536        $posts_ids = dcUtils::cleanIds($ids);
1537
1538        if (empty($posts_ids)) {
1539            throw new Exception(__('No such entry ID'));
1540        }
1541
1542        $strReq = 'DELETE FROM ' . $this->prefix . 'post ' .
1543        "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1544        "AND post_id " . $this->con->in($posts_ids);
1545
1546        #If user can only delete, we need to check the post's owner
1547        if (!$this->core->auth->check('contentadmin', $this->id)) {
1548            $strReq .= "AND user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
1549        }
1550
1551        $this->con->execute($strReq);
1552        $this->triggerBlog();
1553    }
1554
1555    /**
1556    Publishes all entries flaged as "scheduled".
1557     */
1558    public function publishScheduledEntries()
1559    {
1560        $strReq = 'SELECT post_id, post_dt, post_tz ' .
1561        'FROM ' . $this->prefix . 'post ' .
1562        'WHERE post_status = -1 ' .
1563        "AND blog_id = '" . $this->con->escape($this->id) . "' ";
1564
1565        $rs = $this->con->select($strReq);
1566
1567        $now       = dt::toUTC(time());
1568        $to_change = new ArrayObject();
1569
1570        if ($rs->isEmpty()) {
1571            return;
1572        }
1573
1574        while ($rs->fetch()) {
1575            # Now timestamp with post timezone
1576            $now_tz = $now + dt::getTimeOffset($rs->post_tz, $now);
1577
1578            # Post timestamp
1579            $post_ts = strtotime($rs->post_dt);
1580
1581            # If now_tz >= post_ts, we publish the entry
1582            if ($now_tz >= $post_ts) {
1583                $to_change[] = (integer) $rs->post_id;
1584            }
1585        }
1586        if (count($to_change)) {
1587            # --BEHAVIOR-- coreBeforeScheduledEntriesPublish
1588            $this->core->callBehavior('coreBeforeScheduledEntriesPublish', $this, $to_change);
1589
1590            $strReq =
1591            'UPDATE ' . $this->prefix . 'post SET ' .
1592            'post_status = 1 ' .
1593            "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1594            'AND post_id ' . $this->con->in((array) $to_change) . ' ';
1595            $this->con->execute($strReq);
1596            $this->triggerBlog();
1597
1598            # --BEHAVIOR-- coreAfterScheduledEntriesPublish
1599            $this->core->callBehavior('coreAfterScheduledEntriesPublish', $this, $to_change);
1600
1601            $this->firstPublicationEntries($to_change);
1602        }
1603    }
1604
1605    /**
1606    First publication mecanism (on post create, update, publish, status)
1607
1608    @param    ids        <b>mixed</b>        Post(s) ID(s)
1609     */
1610    public function firstPublicationEntries($ids)
1611    {
1612        $posts = $this->getPosts(array(
1613            'post_id'       => dcUtils::cleanIds($ids),
1614            'post_status'   => 1,
1615            'post_firstpub' => 0
1616        ));
1617
1618        $to_change = array();
1619        while ($posts->fetch()) {
1620
1621            $to_change[] = $posts->post_id;
1622        }
1623
1624        if (count($to_change)) {
1625
1626            $strReq =
1627            'UPDATE ' . $this->prefix . 'post ' .
1628            'SET post_firstpub = 1 ' .
1629            "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1630            'AND post_id ' . $this->con->in((array) $to_change) . ' ';
1631            $this->con->execute($strReq);
1632
1633            # --BEHAVIOR-- coreFirstPublicationEntries
1634            $this->core->callBehavior('coreFirstPublicationEntries', $this, $to_change);
1635        }
1636    }
1637
1638    /**
1639    Retrieves all users having posts on current blog.
1640
1641    @param    post_type        <b>string</b>        post_type filter (post)
1642    @return    record
1643     */
1644    public function getPostsUsers($post_type = 'post')
1645    {
1646        $strReq = 'SELECT P.user_id, user_name, user_firstname, ' .
1647        'user_displayname, user_email ' .
1648        'FROM ' . $this->prefix . 'post P, ' . $this->prefix . 'user U ' .
1649        'WHERE P.user_id = U.user_id ' .
1650        "AND blog_id = '" . $this->con->escape($this->id) . "' ";
1651
1652        if ($post_type) {
1653            $strReq .= "AND post_type = '" . $this->con->escape($post_type) . "' ";
1654        }
1655
1656        $strReq .= 'GROUP BY P.user_id, user_name, user_firstname, user_displayname, user_email ';
1657
1658        return $this->con->select($strReq);
1659    }
1660
1661    private function getPostsCategoryFilter($arr, $field = 'cat_id')
1662    {
1663        $field = $field == 'cat_id' ? 'cat_id' : 'cat_url';
1664
1665        $sub     = array();
1666        $not     = array();
1667        $queries = array();
1668
1669        foreach ($arr as $v) {
1670            $v    = trim($v);
1671            $args = preg_split('/\s*[?]\s*/', $v, -1, PREG_SPLIT_NO_EMPTY);
1672            $id   = array_shift($args);
1673            $args = array_flip($args);
1674
1675            if (isset($args['not'])) {$not[$id] = 1;}
1676            if (isset($args['sub'])) {$sub[$id] = 1;}
1677            if ($field == 'cat_id') {
1678                if (preg_match('/^null$/i', $id)) {
1679                    $queries[$id] = 'P.cat_id IS NULL';
1680                } else {
1681                    $queries[$id] = 'P.cat_id = ' . (integer) $id;
1682                }
1683            } else {
1684                $queries[$id] = "C.cat_url = '" . $this->con->escape($id) . "' ";
1685            }
1686        }
1687
1688        if (!empty($sub)) {
1689            $rs = $this->con->select(
1690                'SELECT cat_id, cat_url, cat_lft, cat_rgt FROM ' . $this->prefix . 'category ' .
1691                "WHERE blog_id = '" . $this->con->escape($this->id) . "' " .
1692                'AND ' . $field . ' ' . $this->con->in(array_keys($sub))
1693            );
1694
1695            while ($rs->fetch()) {
1696                $queries[$rs->f($field)] = '(C.cat_lft BETWEEN ' . $rs->cat_lft . ' AND ' . $rs->cat_rgt . ')';
1697            }
1698        }
1699
1700        # Create queries
1701        $sql = array(
1702            0 => array(), # wanted categories
1703            1 => array() # excluded categories
1704        );
1705
1706        foreach ($queries as $id => $q) {
1707            $sql[(integer) isset($not[$id])][] = $q;
1708        }
1709
1710        $sql[0] = implode(' OR ', $sql[0]);
1711        $sql[1] = implode(' OR ', $sql[1]);
1712
1713        if ($sql[0]) {
1714            $sql[0] = '(' . $sql[0] . ')';
1715        } else {
1716            unset($sql[0]);
1717        }
1718
1719        if ($sql[1]) {
1720            $sql[1] = '(P.cat_id IS NULL OR NOT(' . $sql[1] . '))';
1721        } else {
1722            unset($sql[1]);
1723        }
1724
1725        return implode(' AND ', $sql);
1726    }
1727
1728    private function getPostCursor($cur, $post_id = null)
1729    {
1730        if ($cur->post_title == '') {
1731            throw new Exception(__('No entry title'));
1732        }
1733
1734        if ($cur->post_content == '') {
1735            throw new Exception(__('No entry content'));
1736        }
1737
1738        if ($cur->post_password === '') {
1739            $cur->post_password = null;
1740        }
1741
1742        if ($cur->post_dt == '') {
1743            $offset       = dt::getTimeOffset($this->core->auth->getInfo('user_tz'));
1744            $now          = time() + $offset;
1745            $cur->post_dt = date('Y-m-d H:i:00', $now);
1746        }
1747
1748        $post_id = is_int($post_id) ? $post_id : $cur->post_id;
1749
1750        if ($cur->post_content_xhtml == '') {
1751            throw new Exception(__('No entry content'));
1752        }
1753
1754        # Words list
1755        if ($cur->post_title !== null && $cur->post_excerpt_xhtml !== null
1756            && $cur->post_content_xhtml !== null) {
1757            $words =
1758            $cur->post_title . ' ' .
1759            $cur->post_excerpt_xhtml . ' ' .
1760            $cur->post_content_xhtml;
1761
1762            $cur->post_words = implode(' ', text::splitWords($words));
1763        }
1764
1765        if ($cur->isField('post_firstpub')) {
1766            $cur->unsetField('post_firstpub');
1767        }
1768    }
1769
1770    private function getPostContent($cur, $post_id)
1771    {
1772        $post_excerpt       = $cur->post_excerpt;
1773        $post_excerpt_xhtml = $cur->post_excerpt_xhtml;
1774        $post_content       = $cur->post_content;
1775        $post_content_xhtml = $cur->post_content_xhtml;
1776
1777        $this->setPostContent(
1778            $post_id, $cur->post_format, $cur->post_lang,
1779            $post_excerpt, $post_excerpt_xhtml,
1780            $post_content, $post_content_xhtml
1781        );
1782
1783        $cur->post_excerpt       = $post_excerpt;
1784        $cur->post_excerpt_xhtml = $post_excerpt_xhtml;
1785        $cur->post_content       = $post_content;
1786        $cur->post_content_xhtml = $post_content_xhtml;
1787    }
1788
1789    /**
1790    Creates post HTML content, taking format and lang into account.
1791
1792    @param        post_id        <b>integer</b>        Post ID
1793    @param        format        <b>string</b>        Post format
1794    @param        lang            <b>string</b>        Post lang
1795    @param        excerpt        <b>string</b>        Post excerpt
1796    @param[out]    excerpt_xhtml    <b>string</b>        Post excerpt HTML
1797    @param        content        <b>string</b>        Post content
1798    @param[out]    content_xhtml    <b>string</b>        Post content HTML
1799     */
1800    public function setPostContent($post_id, $format, $lang, &$excerpt, &$excerpt_xhtml, &$content, &$content_xhtml)
1801    {
1802        if ($format == 'wiki') {
1803            $this->core->initWikiPost();
1804            $this->core->wiki2xhtml->setOpt('note_prefix', 'pnote-' . $post_id);
1805            switch ($this->settings->system->note_title_tag) {
1806                case 1:
1807                    $tag = 'h3';
1808                    break;
1809                case 2:
1810                    $tag = 'p';
1811                    break;
1812                default:
1813                    $tag = 'h4';
1814                    break;
1815            }
1816            $this->core->wiki2xhtml->setOpt('note_str', '<div class="footnotes"><' . $tag . ' class="footnotes-title">' .
1817                __('Notes') . '</' . $tag . '>%s</div>');
1818            $this->core->wiki2xhtml->setOpt('note_str_single', '<div class="footnotes"><' . $tag . ' class="footnotes-title">' .
1819                __('Note') . '</' . $tag . '>%s</div>');
1820            if (strpos($lang, 'fr') === 0) {
1821                $this->core->wiki2xhtml->setOpt('active_fr_syntax', 1);
1822            }
1823        }
1824
1825        if ($excerpt) {
1826            $excerpt_xhtml = $this->core->callFormater($format, $excerpt);
1827            $excerpt_xhtml = $this->core->HTMLfilter($excerpt_xhtml);
1828        } else {
1829            $excerpt_xhtml = '';
1830        }
1831
1832        if ($content) {
1833            $content_xhtml = $this->core->callFormater($format, $content);
1834            $content_xhtml = $this->core->HTMLfilter($content_xhtml);
1835        } else {
1836            $content_xhtml = '';
1837        }
1838
1839        # --BEHAVIOR-- coreAfterPostContentFormat
1840        $this->core->callBehavior('coreAfterPostContentFormat', array(
1841            'excerpt'       => &$excerpt,
1842            'content'       => &$content,
1843            'excerpt_xhtml' => &$excerpt_xhtml,
1844            'content_xhtml' => &$content_xhtml
1845        ));
1846    }
1847
1848    /**
1849    Returns URL for a post according to blog setting <var>post_url_format</var>.
1850    It will try to guess URL and append some figures if needed.
1851
1852    @param    url            <b>string</b>        Origin URL, could be empty
1853    @param    post_dt        <b>string</b>        Post date (in YYYY-MM-DD HH:mm:ss)
1854    @param    post_title    <b>string</b>        Post title
1855    @param    post_id        <b>integer</b>        Post ID
1856    @return    <b>string</b>    result URL
1857     */
1858    public function getPostURL($url, $post_dt, $post_title, $post_id)
1859    {
1860        $url = trim($url);
1861
1862        $url_patterns = array(
1863            '{y}'  => date('Y', strtotime($post_dt)),
1864            '{m}'  => date('m', strtotime($post_dt)),
1865            '{d}'  => date('d', strtotime($post_dt)),
1866            '{t}'  => text::tidyURL($post_title),
1867            '{id}' => (integer) $post_id
1868        );
1869
1870        # If URL is empty, we create a new one
1871        if ($url == '') {
1872            # Transform with format
1873            $url = str_replace(
1874                array_keys($url_patterns),
1875                array_values($url_patterns),
1876                $this->settings->system->post_url_format
1877            );
1878        } else {
1879            $url = text::tidyURL($url);
1880        }
1881
1882        # Let's check if URL is taken...
1883        $strReq = 'SELECT post_url FROM ' . $this->prefix . 'post ' .
1884        "WHERE post_url = '" . $this->con->escape($url) . "' " .
1885        'AND post_id <> ' . (integer) $post_id . ' ' .
1886        "AND blog_id = '" . $this->con->escape($this->id) . "' " .
1887            'ORDER BY post_url DESC';
1888
1889        $rs = $this->con->select($strReq);
1890
1891        if (!$rs->isEmpty()) {
1892            if ($this->con->driver() == 'mysql' || $this->con->driver() == 'mysqli' || $this->con->driver() == 'mysqlimb4') {
1893                $clause = "REGEXP '^" . $this->con->escape(preg_quote($url)) . "[0-9]+$'";
1894            } elseif ($this->con->driver() == 'pgsql') {
1895                $clause = "~ '^" . $this->con->escape(preg_quote($url)) . "[0-9]+$'";
1896            } else {
1897                $clause = "LIKE '" .
1898                $this->con->escape(preg_replace(array('%', '_', '!'), array('!%', '!_', '!!'), $url)) .
1899                    "%' ESCAPE '!'";
1900            }
1901            $strReq = 'SELECT post_url FROM ' . $this->prefix . 'post ' .
1902            "WHERE post_url " . $clause . ' ' .
1903            'AND post_id <> ' . (integer) $post_id . ' ' .
1904            "AND blog_id = '" . $this->con->escape($this->id) . "' " .
1905                'ORDER BY post_url DESC ';
1906
1907            $rs = $this->con->select($strReq);
1908            $a  = array();
1909            while ($rs->fetch()) {
1910                $a[] = $rs->post_url;
1911            }
1912
1913            natsort($a);
1914            $t_url = end($a);
1915
1916            if (preg_match('/(.*?)([0-9]+)$/', $t_url, $m)) {
1917                $i   = (integer) $m[2];
1918                $url = $m[1];
1919            } else {
1920                $i = 1;
1921            }
1922
1923            return $url . ($i + 1);
1924        }
1925
1926        # URL is empty?
1927        if ($url == '') {
1928            throw new Exception(__('Empty entry URL'));
1929        }
1930
1931        return $url;
1932    }
1933    //@}
1934
1935    /// @name Comments management methods
1936    //@{
1937    /**
1938    Retrieves comments. <b>$params</b> is an array taking the following
1939    optionnal parameters:
1940
1941    - no_content: Don't retrieve comment content
1942    - post_type: Get only entries with given type (default no type, array for many types)
1943    - post_id: (integer) Get comments belonging to given post_id
1944    - cat_id: (integer or array) Get comments belonging to entries of given category ID
1945    - comment_id: (integer or array) Get comment with given ID (or IDs)
1946    - comment_site: (string) Get comments with given comment_site
1947    - comment_status: (integer) Get comments with given comment_status
1948    - comment_trackback: (integer) Get only comments (0) or trackbacks (1)
1949    - comment_ip: (string) Get comments with given IP address
1950    - post_url: Get entry with given post_url field
1951    - user_id: (integer) Get entries belonging to given user ID
1952    - q_author: Search comments by author
1953    - sql: Append SQL string at the end of the query
1954    - from: Append SQL string after "FROM" statement in query
1955    - order: Order of results (default "ORDER BY comment_dt DES")
1956    - limit: Limit parameter
1957    - sql_only : return the sql request instead of results. Only ids are selected
1958
1959    @param    params        <b>array</b>        Parameters
1960    @param    count_only    <b>boolean</b>        Only counts results
1961    @return    <b>record</b>    A record with some more capabilities
1962     */
1963    public function getComments($params = array(), $count_only = false)
1964    {
1965        if ($count_only) {
1966            $strReq = 'SELECT count(comment_id) ';
1967        } elseif (!empty($params['sql_only'])) {
1968            $strReq = 'SELECT P.post_id ';
1969        } else {
1970            if (!empty($params['no_content'])) {
1971                $content_req = '';
1972            } else {
1973                $content_req = 'comment_content, ';
1974            }
1975
1976            if (!empty($params['columns']) && is_array($params['columns'])) {
1977                $content_req .= implode(', ', $params['columns']) . ', ';
1978            }
1979
1980            $strReq =
1981                'SELECT C.comment_id, comment_dt, comment_tz, comment_upddt, ' .
1982                'comment_author, comment_email, comment_site, ' .
1983                $content_req . ' comment_trackback, comment_status, ' .
1984                'comment_spam_status, comment_spam_filter, comment_ip, ' .
1985                'P.post_title, P.post_url, P.post_id, P.post_password, P.post_type, ' .
1986                'P.post_dt, P.user_id, U.user_email, U.user_url ';
1987        }
1988
1989        $strReq .=
1990        'FROM ' . $this->prefix . 'comment C ' .
1991        'INNER JOIN ' . $this->prefix . 'post P ON C.post_id = P.post_id ' .
1992        'INNER JOIN ' . $this->prefix . 'user U ON P.user_id = U.user_id ';
1993
1994        if (!empty($params['from'])) {
1995            $strReq .= $params['from'] . ' ';
1996        }
1997
1998        $strReq .=
1999        "WHERE P.blog_id = '" . $this->con->escape($this->id) . "' ";
2000
2001        if (!$this->core->auth->check('contentadmin', $this->id)) {
2002            $strReq .= 'AND ((comment_status = 1 AND P.post_status = 1 ';
2003
2004            if ($this->without_password) {
2005                $strReq .= 'AND post_password IS NULL ';
2006            }
2007            $strReq .= ') ';
2008
2009            if ($this->core->auth->userID()) {
2010                $strReq .= "OR P.user_id = '" . $this->con->escape($this->core->auth->userID()) . "')";
2011            } else {
2012                $strReq .= ') ';
2013            }
2014        }
2015
2016        if (!empty($params['post_type'])) {
2017            $strReq .= 'AND post_type ' . $this->con->in($params['post_type']);
2018        }
2019
2020        if (isset($params['post_id']) && $params['post_id'] !== '') {
2021            $strReq .= 'AND P.post_id = ' . (integer) $params['post_id'] . ' ';
2022        }
2023
2024        if (isset($params['cat_id']) && $params['cat_id'] !== '') {
2025            $strReq .= 'AND P.cat_id = ' . (integer) $params['cat_id'] . ' ';
2026        }
2027
2028        if (isset($params['comment_id']) && $params['comment_id'] !== '') {
2029            if (is_array($params['comment_id'])) {
2030                array_walk($params['comment_id'], function (&$v, $k) {if ($v !== null) {$v = (integer) $v;}});
2031            } else {
2032                $params['comment_id'] = array((integer) $params['comment_id']);
2033            }
2034            $strReq .= 'AND comment_id ' . $this->con->in($params['comment_id']);
2035        }
2036
2037        if (isset($params['comment_email'])) {
2038            $comment_email = $this->con->escape(str_replace('*', '%', $params['comment_email']));
2039            $strReq .= "AND comment_email LIKE '" . $comment_email . "' ";
2040        }
2041
2042        if (isset($params['comment_site'])) {
2043            $comment_site = $this->con->escape(str_replace('*', '%', $params['comment_site']));
2044            $strReq .= "AND comment_site LIKE '" . $comment_site . "' ";
2045        }
2046
2047        if (isset($params['comment_status'])) {
2048            $strReq .= 'AND comment_status = ' . (integer) $params['comment_status'] . ' ';
2049        }
2050
2051        if (!empty($params['comment_status_not'])) {
2052            $strReq .= 'AND comment_status <> ' . (integer) $params['comment_status_not'] . ' ';
2053        }
2054
2055        if (isset($params['comment_trackback'])) {
2056            $strReq .= 'AND comment_trackback = ' . (integer) (boolean) $params['comment_trackback'] . ' ';
2057        }
2058
2059        if (isset($params['comment_ip'])) {
2060            $comment_ip = $this->con->escape(str_replace('*', '%', $params['comment_ip']));
2061            $strReq .= "AND comment_ip LIKE '" . $comment_ip . "' ";
2062        }
2063
2064        if (isset($params['q_author'])) {
2065            $q_author = $this->con->escape(str_replace('*', '%', strtolower($params['q_author'])));
2066            $strReq .= "AND LOWER(comment_author) LIKE '" . $q_author . "' ";
2067        }
2068
2069        if (!empty($params['search'])) {
2070            $words = text::splitWords($params['search']);
2071
2072            if (!empty($words)) {
2073                # --BEHAVIOR coreCommentSearch
2074                if ($this->core->hasBehavior('coreCommentSearch')) {
2075                    $this->core->callBehavior('coreCommentSearch', $this->core, array(&$words, &$strReq, &$params));
2076                }
2077
2078                if ($words) {
2079                    foreach ($words as $i => $w) {
2080                        $words[$i] = "comment_words LIKE '%" . $this->con->escape($w) . "%'";
2081                    }
2082                    $strReq .= 'AND ' . implode(' AND ', $words) . ' ';
2083                }
2084            }
2085        }
2086
2087        if (!empty($params['sql'])) {
2088            $strReq .= $params['sql'] . ' ';
2089        }
2090
2091        if (!$count_only) {
2092            if (!empty($params['order'])) {
2093                $strReq .= 'ORDER BY ' . $this->con->escape($params['order']) . ' ';
2094            } else {
2095                $strReq .= 'ORDER BY comment_dt DESC ';
2096            }
2097        }
2098
2099        if (!$count_only && !empty($params['limit'])) {
2100            $strReq .= $this->con->limit($params['limit']);
2101        }
2102
2103        if (!empty($params['sql_only'])) {
2104            return $strReq;
2105        }
2106
2107        $rs       = $this->con->select($strReq);
2108        $rs->core = $this->core;
2109        $rs->extend('rsExtComment');
2110
2111        # --BEHAVIOR-- coreBlogGetComments
2112        $this->core->callBehavior('coreBlogGetComments', $rs);
2113
2114        return $rs;
2115    }
2116
2117    /**
2118    Creates a new comment. Takes a cursor as input and returns the new comment
2119    ID.
2120
2121    @param    cur        <b>cursor</b>        Comment cursor
2122    @return    <b>integer</b>        New comment ID
2123     */
2124    public function addComment($cur)
2125    {
2126        $this->con->writeLock($this->prefix . 'comment');
2127        try
2128        {
2129            # Get ID
2130            $rs = $this->con->select(
2131                'SELECT MAX(comment_id) ' .
2132                'FROM ' . $this->prefix . 'comment '
2133            );
2134
2135            $cur->comment_id    = (integer) $rs->f(0) + 1;
2136            $cur->comment_upddt = date('Y-m-d H:i:s');
2137
2138            $offset          = dt::getTimeOffset($this->settings->system->blog_timezone);
2139            $cur->comment_dt = date('Y-m-d H:i:s', time() + $offset);
2140            $cur->comment_tz = $this->settings->system->blog_timezone;
2141
2142            $this->getCommentCursor($cur);
2143
2144            if ($cur->comment_ip === null) {
2145                $cur->comment_ip = http::realIP();
2146            }
2147
2148            # --BEHAVIOR-- coreBeforeCommentCreate
2149            $this->core->callBehavior('coreBeforeCommentCreate', $this, $cur);
2150
2151            $cur->insert();
2152            $this->con->unlock();
2153        } catch (Exception $e) {
2154            $this->con->unlock();
2155            throw $e;
2156        }
2157
2158        # --BEHAVIOR-- coreAfterCommentCreate
2159        $this->core->callBehavior('coreAfterCommentCreate', $this, $cur);
2160
2161        $this->triggerComment($cur->comment_id);
2162        if ($cur->comment_status != -2) {
2163            $this->triggerBlog();
2164        }
2165        return $cur->comment_id;
2166    }
2167
2168    /**
2169    Updates an existing comment.
2170
2171    @param    id        <b>integer</b>        Comment ID
2172    @param    cur        <b>cursor</b>        Comment cursor
2173     */
2174    public function updComment($id, $cur)
2175    {
2176        if (!$this->core->auth->check('usage,contentadmin', $this->id)) {
2177            throw new Exception(__('You are not allowed to update comments'));
2178        }
2179
2180        $id = (integer) $id;
2181
2182        if (empty($id)) {
2183            throw new Exception(__('No such comment ID'));
2184        }
2185
2186        $rs = $this->getComments(array('comment_id' => $id));
2187
2188        if ($rs->isEmpty()) {
2189            throw new Exception(__('No such comment ID'));
2190        }
2191
2192        #If user is only usage, we need to check the post's owner
2193        if (!$this->core->auth->check('contentadmin', $this->id)) {
2194            if ($rs->user_id != $this->core->auth->userID()) {
2195                throw new Exception(__('You are not allowed to update this comment'));
2196            }
2197        }
2198
2199        $this->getCommentCursor($cur);
2200
2201        $cur->comment_upddt = date('Y-m-d H:i:s');
2202
2203        if (!$this->core->auth->check('publish,contentadmin', $this->id)) {
2204            $cur->unsetField('comment_status');
2205        }
2206
2207        # --BEHAVIOR-- coreBeforeCommentUpdate
2208        $this->core->callBehavior('coreBeforeCommentUpdate', $this, $cur, $rs);
2209
2210        $cur->update('WHERE comment_id = ' . $id . ' ');
2211
2212        # --BEHAVIOR-- coreAfterCommentUpdate
2213        $this->core->callBehavior('coreAfterCommentUpdate', $this, $cur, $rs);
2214
2215        $this->triggerComment($id);
2216        $this->triggerBlog();
2217    }
2218
2219    /**
2220    Updates comment status.
2221
2222    @param    id        <b>integer</b>        Comment ID
2223    @param    status    <b>integer</b>        Comment status
2224     */
2225    public function updCommentStatus($id, $status)
2226    {
2227        $this->updCommentsStatus($id, $status);
2228    }
2229
2230    /**
2231    Updates comments status.
2232
2233    @param    ids        <b>mixed</b>        Comment(s) ID(s)
2234    @param    status    <b>integer</b>        Comment status
2235     */
2236    public function updCommentsStatus($ids, $status)
2237    {
2238        if (!$this->core->auth->check('publish,contentadmin', $this->id)) {
2239            throw new Exception(__("You are not allowed to change this comment's status"));
2240        }
2241
2242        $co_ids = dcUtils::cleanIds($ids);
2243        $status = (integer) $status;
2244
2245        $strReq =
2246        'UPDATE ' . $this->prefix . 'comment ' .
2247            'SET comment_status = ' . $status . ' ';
2248        $strReq .=
2249        'WHERE comment_id' . $this->con->in($co_ids) .
2250        'AND post_id in (SELECT tp.post_id ' .
2251        'FROM ' . $this->prefix . 'post tp ' .
2252        "WHERE tp.blog_id = '" . $this->con->escape($this->id) . "' ";
2253        if (!$this->core->auth->check('contentadmin', $this->id)) {
2254            $strReq .=
2255            "AND user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
2256        }
2257        $strReq .= ')';
2258        $this->con->execute($strReq);
2259        $this->triggerComments($co_ids);
2260        $this->triggerBlog();
2261    }
2262
2263    /**
2264    Delete a comment
2265
2266    @param    id        <b>integer</b>        Comment ID
2267     */
2268    public function delComment($id)
2269    {
2270        $this->delComments($id);
2271    }
2272
2273    /**
2274    Delete comments
2275
2276    @param    ids        <b>mixed</b>        Comment(s) ID(s)
2277     */
2278    public function delComments($ids)
2279    {
2280        if (!$this->core->auth->check('delete,contentadmin', $this->id)) {
2281            throw new Exception(__('You are not allowed to delete comments'));
2282        }
2283
2284        $co_ids = dcUtils::cleanIds($ids);
2285
2286        if (empty($co_ids)) {
2287            throw new Exception(__('No such comment ID'));
2288        }
2289
2290        # Retrieve posts affected by comments edition
2291        $affected_posts = array();
2292        $strReq         =
2293        'SELECT post_id ' .
2294        'FROM ' . $this->prefix . 'comment ' .
2295        'WHERE comment_id' . $this->con->in($co_ids) .
2296            'GROUP BY post_id';
2297
2298        $rs = $this->con->select($strReq);
2299
2300        while ($rs->fetch()) {
2301            $affected_posts[] = (integer) $rs->post_id;
2302        }
2303
2304        $strReq =
2305        'DELETE FROM ' . $this->prefix . 'comment ' .
2306        'WHERE comment_id' . $this->con->in($co_ids) . ' ' .
2307        'AND post_id in (SELECT tp.post_id ' .
2308        'FROM ' . $this->prefix . 'post tp ' .
2309        "WHERE tp.blog_id = '" . $this->con->escape($this->id) . "' ";
2310        #If user can only delete, we need to check the post's owner
2311        if (!$this->core->auth->check('contentadmin', $this->id)) {
2312            $strReq .=
2313            "AND tp.user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
2314        }
2315        $strReq .= ")";
2316        $this->con->execute($strReq);
2317        $this->triggerComments($co_ids, true, $affected_posts);
2318        $this->triggerBlog();
2319    }
2320
2321    public function delJunkComments()
2322    {
2323        if (!$this->core->auth->check('delete,contentadmin', $this->id)) {
2324            throw new Exception(__('You are not allowed to delete comments'));
2325        }
2326
2327        $strReq =
2328        'DELETE FROM ' . $this->prefix . 'comment ' .
2329        'WHERE comment_status = -2 ' .
2330        'AND post_id in (SELECT tp.post_id ' .
2331        'FROM ' . $this->prefix . 'post tp ' .
2332        "WHERE tp.blog_id = '" . $this->con->escape($this->id) . "' ";
2333        #If user can only delete, we need to check the post's owner
2334        if (!$this->core->auth->check('contentadmin', $this->id)) {
2335            $strReq .=
2336            "AND tp.user_id = '" . $this->con->escape($this->core->auth->userID()) . "' ";
2337        }
2338        $strReq .= ")";
2339        $this->con->execute($strReq);
2340        $this->triggerBlog();
2341    }
2342
2343    private function getCommentCursor($cur)
2344    {
2345        if ($cur->comment_content !== null && $cur->comment_content == '') {
2346            throw new Exception(__('You must provide a comment'));
2347        }
2348
2349        if ($cur->comment_author !== null && $cur->comment_author == '') {
2350            throw new Exception(__('You must provide an author name'));
2351        }
2352
2353        if ($cur->comment_email != '' && !text::isEmail($cur->comment_email)) {
2354            throw new Exception(__('Email address is not valid.'));
2355        }
2356
2357        if ($cur->comment_site !== null && $cur->comment_site != '') {
2358            if (!preg_match('|^http(s?)://|i', $cur->comment_site, $matches)) {
2359                $cur->comment_site = 'http://' . $cur->comment_site;
2360            } else {
2361                $cur->comment_site = strtolower($matches[0]) . substr($cur->comment_site, strlen($matches[0]));
2362            }
2363        }
2364
2365        if ($cur->comment_status === null) {
2366            $cur->comment_status = (integer) $this->settings->system->comments_pub;
2367        }
2368
2369        # Words list
2370        if ($cur->comment_content !== null) {
2371            $cur->comment_words = implode(' ', text::splitWords($cur->comment_content));
2372        }
2373    }
2374    //@}
2375}
Note: See TracBrowser for help on using the repository browser.

Sites map