Dotclear

source: plugins/zoneclearFeedServer/inc/class.zoneclear.feed.server.php @ 2915

Revision 2915, 19.1 KB checked in by JcDenis, 13 years ago (diff)

zoneclearFeedServer 1.3

  • Fixed install on nightly build
  • Fixed bug on null blog (and settings)
  • Added verbose on admin side
  • Added behaviors to open to others plugins (ie messenger)
  • Added support of plugin soCialMe (writer part)
  • Removed messenger functions (as it's to another plugin to do this)
Line 
1<?php
2# -- BEGIN LICENSE BLOCK ----------------------------------
3# This file is part of zoneclearFeedServer, a plugin for Dotclear 2.
4#
5# Copyright (c) 2009-2011 JC Denis, BG and contributors
6# jcdenis@gdwd.com
7#
8# Licensed under the GPL version 2.0 license.
9# A copy of this license is available in LICENSE file or at
10# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
11# -- END LICENSE BLOCK ------------------------------------
12
13if (!defined('DC_RC_PATH')){return;}
14
15class zoneclearFeedServer
16{
17     public static $nethttp_timeout = 2;
18     public static $nethttp_agent = 'zoneclearFeedServer - http://zoneclear.org';
19     public static $nethttp_maxredirect = 2;
20     
21     public $core;
22     public $con;
23     private $blog;
24     private $table;
25     private $lock = null;
26     
27     public function __construct($core)
28     {
29          $this->core = $core;
30          $this->con = $core->con;
31          $this->blog = $core->con->escape($core->blog->id);
32          $this->table = $core->prefix.'zc_feed';
33     }
34     
35     # Short openCursor
36     public function openCursor()
37     {
38          return $this->con->openCursor($this->table);
39     }
40     
41     # Update record
42     public function updFeed($id,$cur)
43     {
44          $this->con->writeLock($this->table);
45         
46          try
47          {
48               $id = (integer) $id;
49
50               if ($id < 1)
51               {
52                    throw new Exception(__('No such ID'));
53               }
54               $cur->feed_upddt = date('Y-m-d H:i:s');
55               
56               $cur->update("WHERE feed_id = ".$id." AND blog_id = '".$this->blog."' ");
57               $this->con->unlock();
58               $this->trigger();
59          }
60          catch (Exception $e)
61          {
62               $this->con->unlock();
63               throw $e;
64          }
65         
66          # --BEHAVIOR-- zoneclearFeedServerAfterUpdFeed
67          $this->core->callBehavior('zoneclearFeedServerAfterUpdFeed',$cur,$id);
68     }
69     
70     # Add record
71     public function addFeed($cur)
72     {
73          $this->con->writeLock($this->table);
74         
75          try
76          {
77               $cur->feed_id = $this->getNextId();
78               $cur->blog_id = $this->blog;
79               $cur->feed_creadt = date('Y-m-d H:i:s');
80               $cur->feed_upddt = date('Y-m-d H:i:s');
81
82               //add getFeedCursor here
83               
84               $cur->insert();
85               $this->con->unlock();
86               $this->trigger();
87          }
88          catch (Exception $e)
89          {
90               $this->con->unlock();
91               throw $e;
92          }
93         
94          # --BEHAVIOR-- zoneclearFeedServerAfterAddFeed
95          $this->core->callBehavior('zoneclearFeedServerAfterAddFeed',$cur);
96         
97          return $cur->feed_id;
98     }
99     
100     # Quick enable / disable feed
101     public function enableFeed($id,$enable=true,$time=null)
102     {
103          try
104          {
105               $id = (integer) $id;
106               
107               if ($id < 1)
108               {
109                    throw new Exception(__('No such ID'));
110               }
111               $cur = $this->openCursor();
112               $this->con->writeLock($this->table);
113               
114               $cur->feed_upddt = date('Y-m-d H:i:s');
115               
116               $cur->feed_status = (integer) $enable;
117               if (null !== $time)
118               {
119                    $cur->feed_upd_last = (integer) $time;
120               }
121               
122               $cur->update("WHERE feed_id = ".$id." AND blog_id = '".$this->blog."' ");
123               $this->con->unlock();
124               $this->trigger();
125          }
126          catch (Exception $e)
127          {
128               $this->con->unlock();
129               throw $e;
130          }
131         
132          # --BEHAVIOR-- zoneclearFeedServerAfterEnableFeed
133          $this->core->callBehavior('zoneclearFeedServerAfterEnableFeed',$id,$enable,$time);
134     }
135     
136     # Delete record (this not deletes post)
137     public function delFeed($id)
138     {
139          $id = (integer) $id;
140         
141          if ($id < 1)
142          {
143               throw new Exception(__('No such ID'));
144          }
145         
146          # --BEHAVIOR-- zoneclearFeedServerBeforeDelFeed
147          $this->core->callBehavior('zoneclearFeedServerBeforeDelFeed',$id);
148         
149          $this->con->execute(
150               'DELETE FROM '.$this->table.' '.
151               'WHERE feed_id = '.$id.' '.
152               "AND blog_id = '".$this->blog."' "
153          );
154          $this->trigger();
155     }
156     
157     # Get related posts
158     public function getPostsByFeed($params=array(),$count_only=false)
159     {
160          if (!isset($params['feed_id']))
161          {
162               return null;
163          }
164         
165          $params['from'] = 
166          'LEFT JOIN '.$this->core->prefix.'meta F '.
167          'ON P.post_id = F.post_id ';
168          $params['sql'] = 
169          "AND P.blog_id = '".$this->blog."' ".
170          "AND F.meta_type = 'zoneclearfeed_id' ".
171          "AND F.meta_id = '".$this->con->escape($params['feed_id'])."' ";
172         
173          unset($params['feed_id']);
174         
175          return $this->core->blog->getPosts($params,$count_only);
176     }
177
178     # get record
179     public function getFeeds($params=array(),$count_only=false)
180     {
181          if ($count_only)
182          {
183               $strReq = 'SELECT count(Z.feed_id) ';
184          }
185          else
186          {
187               $content_req = '';
188               if (!empty($params['columns']) && is_array($params['columns']))
189               {
190                    $content_req .= implode(', ',$params['columns']).', ';
191               }
192               
193               $strReq =
194               'SELECT Z.feed_id, Z.feed_creadt, Z.feed_upddt, Z.feed_type, '.
195               'Z.blog_id, Z.cat_id, '.
196               'Z.feed_upd_int, Z.feed_upd_last, Z.feed_status, '.
197               $content_req.
198               'LOWER(Z.feed_name) as lowername, Z.feed_name, Z.feed_desc, '.
199               'Z.feed_url, Z.feed_feed, Z.feed_get_tags, '.
200               'Z.feed_tags, Z.feed_owner, Z.feed_tweeter, Z.feed_lang, '.
201               'Z.feed_nb_out, Z.feed_nb_in, '.
202               'C.cat_title, C.cat_url, C.cat_desc ';
203          }
204         
205          $strReq .= 
206          'FROM '.$this->table.' Z '.
207          'LEFT OUTER JOIN '.$this->core->prefix.'category C ON Z.cat_id = C.cat_id ';
208         
209          if (!empty($params['from']))
210          {
211               $strReq .= $params['from'].' ';
212          }
213         
214          $strReq .= "WHERE Z.blog_id = '".$this->blog."' ";
215         
216          if (isset($params['feed_type']))
217          {
218               $strReq .= "AND Z.feed_type = '".$this->con->escape($params['type'])."' ";
219          }
220          else
221          {
222               $strReq .= "AND Z.feed_type = 'feed' ";
223          }
224
225          if (!empty($params['feed_id']))
226          {
227               if (is_array($params['feed_id']))
228               {
229                    array_walk($params['feed_id'],create_function('&$v,$k','if($v!==null){$v=(integer)$v;}'));
230               }
231               else
232               {
233                    $params['feed_id'] = array((integer) $params['feed_id']);
234               }
235               $strReq .= 'AND Z.feed_id '.$this->con->in($params['feed_id']);
236          }
237         
238          if (isset($params['feed_feed']))
239          {
240               $strReq .= "AND Z.feed_feed = '".$this->con->escape($params['feed_feed'])."' ";
241          }
242          if (isset($params['feed_url']))
243          {
244               $strReq .= "AND Z.feed_url = '".$this->con->escape($params['feed_url'])."' ";
245          }
246          if (isset($params['feed_status']))
247          {
248               $strReq .= "AND Z.feed_status = ".((integer) $params['feed_status'])." ";
249          }
250         
251          if (!empty($params['sql']))
252          {
253               $strReq .= $params['sql'].' ';
254          }
255         
256          if (!$count_only)
257          {
258               if (!empty($params['order']))
259               {
260                    $strReq .= 'ORDER BY '.$this->con->escape($params['order']).' ';
261               }
262               else
263               {
264                    $strReq .= 'ORDER BY Z.feed_upddt DESC ';
265               }
266          }
267         
268          if (!$count_only && !empty($params['limit']))
269          {
270               $strReq .= $this->con->limit($params['limit']);
271          }
272         
273          $rs = $this->con->select($strReq);
274          $rs->zc = $this;
275         
276          return $rs;
277     }
278     
279     # Get next table id
280     private function getNextId()
281     {
282          return $this->con->select('SELECT MAX(feed_id) FROM '.$this->table)->f(0) + 1;
283     }
284     
285     # Lock a file to see if an update is ongoing
286     public function lockUpdate()
287     {
288          try
289          {
290               # Need flock function
291               if (!function_exists('flock'))
292               {
293                    throw New Exception("Can't call php function named flock");
294               }
295               # Cache writable ?
296               if (!is_writable(DC_TPL_CACHE))
297               {
298                    throw new Exception("Can't write in cache fodler");
299               }
300               # Set file path
301               $f_md5 = md5($this->blog);
302               $cached_file = sprintf('%s/%s/%s/%s/%s.txt',
303                    DC_TPL_CACHE,
304                    'periodical',
305                    substr($f_md5,0,2),
306                    substr($f_md5,2,2),
307                    $f_md5
308               );
309               # Real path
310               $cached_file = path::real($cached_file,false);
311               # Make dir
312               if (!is_dir(dirname($cached_file)))
313               {
314                    files::makeDir(dirname($cached_file),true);
315               }
316               # Make file
317               if (!file_exists($cached_file))
318               {
319                    !$fp = @fopen($cached_file, 'w');
320                    if ($fp === false)
321                    {
322                         throw New Exception("Can't create file");
323                    }
324                    fwrite($fp,'1',strlen('1'));
325                    fclose($fp);
326               }
327               # Open file
328               if (!($fp = @fopen($cached_file, 'r+')))
329               {
330                    throw New Exception("Can't open file");
331               }
332               # Lock file
333               if (!flock($fp,LOCK_EX))
334               {
335                    throw New Exception("Can't lock file");
336               }
337               $this->lock = $fp;
338               return true;
339          }
340          catch (Exception $e)
341          {
342               throw $e;
343          }
344          return false;
345     }
346     
347     public function unlockUpdate()
348     {
349          @fclose($this->lock);
350          $this->lock = null;
351     }
352     
353     # Check and add/update post related to record if needed
354     public function checkFeedsUpdate($id=null,$throw=false)
355     {
356          # Limit to one update at a time
357          try
358          {
359               $this->lockUpdate();
360          }
361          catch (Exception $e)
362          {
363               if ($throw) throw $e;
364               return false;
365          }
366         
367          dt::setTZ($this->core->blog->settings->system->blog_timezone);
368          $time = time();
369          $s = $this->core->blog->settings->zoneclearFeedServer;
370         
371          # All feeds or only one (from admin)
372          $f = !$id ?
373               $this->getFeeds(array('feed_status'=>1,'order'=>'feed_upd_last ASC')) : 
374               $this->getFeeds(array('feed_id'=>$id));
375         
376          # No feed
377          if ($f->isEmpty()) return false;
378         
379          # Set feeds user
380          $this->enableUser($s->zoneclearFeedServer_user);
381         
382          $updates = false;
383          $loop_mem = array();
384         
385          $limit = abs((integer) $s->zoneclearFeedServer_update_limit);
386          if ($limit < 1) $limit = 10;
387          $i = 0;
388         
389          $cur_post = $this->con->openCursor($this->core->prefix.'post');
390          $cur_meta = $this->con->openCursor($this->core->prefix.'meta');
391         
392          while($f->fetch())
393          {
394               # Check if feed need update
395               if ($id || $i < $limit && $f->feed_status == 1 
396                && $time > $f->feed_upd_last + $f->feed_upd_int)
397               {         
398                    $i++;
399                    $feed = self::readFeed($f->feed_feed);
400                   
401                    # Nothing to parse
402                    if (!$feed)
403                    {
404                         # Disable feed
405                         $this->enableFeed($f->feed_id,false);
406                         $i++;
407                    }
408                    # Not updated since last visit
409                    elseif (!$id && '' != $feed->pubdate && strtotime($feed->pubdate) < $f->feed_upd_last)
410                    {
411                         # Set update time of this feed
412                         $this->enableFeed($f->feed_id,true,$time);
413                         $i++;
414                    }
415                    else
416                    {
417                         # Set update time of this feed
418                         $this->enableFeed($f->feed_id,$f->feed_status,$time);
419                         
420                         $this->con->begin();
421                         
422                         foreach ($feed->items as $item)
423                         {
424                              $item_TS = $item->TS ? $item->TS : $time;
425                              $item_link = $this->con->escape($item->link);
426                              $is_new_published_entry = false;
427                             
428                              # Not updated since last visit
429                              if (!$id && $item_TS < $f->feed_upd_last) continue;
430                             
431                              # Fix loop twin
432                              if (in_array($item_link,$loop_mem)) continue;
433                              $loop_mem[] = $item_link;
434                             
435                              # Check if entry exists
436                              $old_post = $this->con->select(
437                                   'SELECT P.post_id, P.post_status '.
438                                   'FROM '.$this->core->prefix.'post P '.
439                                   'INNER JOIN '.$this->core->prefix.'meta M '.
440                                   'ON P.post_id = M.post_id '.
441                                   "WHERE blog_id='".$this->blog."' ".
442                                   "AND meta_type = 'zoneclearfeed_url' ".
443                                   "AND meta_id = '".$item_link."' "
444                              );
445                             
446                              # Prepare entry cursor
447                              $cur_post->clean();
448                              $cur_post->post_dt = date('Y-m-d H:i:s',$item_TS);
449                              if ($f->cat_id) $cur_post->cat_id = $f->cat_id;
450                              $post_content = $item->content ? $item->content : $item->description;
451                              $cur_post->post_content = html::absoluteURLs($post_content,$feed->link);
452                              $cur_post->post_title = $item->title ? $item->title : text::cutString(html::clean($cur_post->post_content),60);
453                              $creator = $item->creator ? $item->creator : $f->feed_owner;
454                             
455                              try
456                              {
457                                   # Create entry
458                                   if ($old_post->isEmpty())
459                                   {
460                                        # Post
461                                        $cur_post->user_id = $this->core->auth->userID();
462                                        $cur_post->post_format = 'xhtml';
463                                        $cur_post->post_status = (integer) $s->zoneclearFeedServer_post_status_new;
464                                        $cur_post->post_open_comment = 0;
465                                        $cur_post->post_open_tb = 0;
466                                       
467                                        # --BEHAVIOR-- zoneclearFeedServerBeforePostCreate
468                                        $this->core->callBehavior('zoneclearFeedServerBeforePostCreate',$cur_post);
469                                       
470                                        $post_id = $this->core->auth->sudo(array($this->core->blog,'addPost'),$cur_post);
471                                       
472                                        # --BEHAVIOR-- zoneclearFeedServerAfterPostCreate
473                                        $this->core->callBehavior('zoneclearFeedServerAfterPostCreate',$cur_post,$post_id);
474                                       
475                                        # Auto tweet new post
476                                        if ($cur_post->post_status == 1)
477                                        {
478                                             $is_new_published_entry = true;
479                                        }
480                                   }
481                                   # Update entry
482                                   else
483                                   {
484                                        $post_id = $old_post->post_id;
485                                       
486                                        # --BEHAVIOR-- zoneclearFeedServerBeforePostUpdate
487                                        $this->core->callBehavior('zoneclearFeedServerBeforePostUpdate',$cur_post,$post_id);
488                                       
489                                        $this->core->auth->sudo(array($this->core->blog,'updPost'),$post_id,$cur_post);
490                                       
491                                        # Quick delete old meta
492                                        $this->con->execute(
493                                             'DELETE FROM '.$this->core->prefix.'meta '.
494                                             'WHERE post_id = '.$post_id.' '.
495                                             "AND meta_type LIKE 'zoneclearfeed_%' "
496                                        );
497                                        # Delete old tags
498                                        $this->core->auth->sudo(array($this->core->meta,'delPostMeta'),$post_id,'tag');
499                                       
500                                        # --BEHAVIOR-- zoneclearFeedServerAfterPostUpdate
501                                        $this->core->callBehavior('zoneclearFeedServerAfterPostUpdate',$cur_post,$post_id);
502                                   }
503                                   
504                                   # Quick add new meta
505                                   $meta = new ArrayObject();
506                                   $meta->tweeter = $f->feed_tweeter;
507                                   
508                                   $cur_meta->clean();
509                                   $cur_meta->post_id = $post_id;
510                                   $cur_meta->meta_type = 'zoneclearfeed_url';
511                                   $cur_meta->meta_id = $meta->url = $item->link;
512                                   $cur_meta->insert();
513                                   
514                                   $cur_meta->clean();
515                                   $cur_meta->post_id = $post_id;
516                                   $cur_meta->meta_type = 'zoneclearfeed_author';
517                                   $cur_meta->meta_id = $meta->author = $creator;
518                                   $cur_meta->insert();
519                                   
520                                   $cur_meta->clean();
521                                   $cur_meta->post_id = $post_id;
522                                   $cur_meta->meta_type = 'zoneclearfeed_site';
523                                   $cur_meta->meta_id = $meta->site = $f->feed_url;
524                                   $cur_meta->insert();
525                                   
526                                   $cur_meta->clean();
527                                   $cur_meta->post_id = $post_id;
528                                   $cur_meta->meta_type = 'zoneclearfeed_sitename';
529                                   $cur_meta->meta_id = $meta->sitename = $f->feed_name;
530                                   $cur_meta->insert();
531                                   
532                                   $cur_meta->clean();
533                                   $cur_meta->post_id = $post_id;
534                                   $cur_meta->meta_type = 'zoneclearfeed_id';
535                                   $cur_meta->meta_id = $meta->id = $f->feed_id;
536                                   $cur_meta->insert();
537                                   
538                                   # Add new tags
539                                   $tags = $this->core->meta->splitMetaValues($f->feed_tags);
540                                   if ($f->feed_get_tags)
541                                   {
542                                        $tags = array_merge($tags,$item->subject);
543                                        $tags = array_unique($tags);
544                                   }
545                                   foreach ($tags as $tag)
546                                   {
547                                        $this->core->auth->sudo(array($this->core->meta,'setPostMeta'),$post_id,'tag',dcMeta::sanitizeMetaID($tag));
548                                   }
549                                   $meta->tags = $tags;
550                                   
551                                   # --BEHAVIOR-- zoneclearFeedServerAfterFeedUpdate
552                                   $this->core->callBehavior('zoneclearFeedServerAfterFeedUpdate',$this->core,$is_new_published_entry,$cur_post,$meta);
553                                   
554                              }
555                              catch (Exception $e)
556                              {
557                                   $this->con->rollback();
558                                   $this->enableUser(false);
559                                   $this->unlockUpdate();
560                                   throw $e;
561                              }
562                              $updates = true;
563                         }
564                         $this->con->commit();
565                    }
566               }
567          }
568          $this->enableUser(false);
569          $this->unlockUpdate();
570          return true;
571     }
572
573     # Set permission to update post table
574     public function enableUser($enable=false)
575     {
576          # Enable
577          if ($enable)
578          {
579               if (!$this->core->auth->checkUser($enable))
580               {
581                    throw new Exception('Unable to set user');
582               }
583          }
584          # Disable
585          else
586          {
587               $this->core->auth = null;
588               $this->core->auth = new dcAuth($this->core);
589          }
590     }
591     
592     # Read and parse external feeds
593     public static function readFeed($f)
594     {
595          try
596          {
597               $feed_reader = new feedReader;
598               $feed_reader->setCacheDir(DC_TPL_CACHE);
599               $feed_reader->setTimeout(self::$nethttp_timeout);
600               $feed_reader->setMaxRedirects(self::$nethttp_maxredirect);
601               $feed_reader->setUserAgent(self::$nethttp_agent);
602               return $feed_reader->parse($f);
603          }
604          catch (Exception $e) {}
605         
606          return null;
607     }
608     
609     # Trigger blog
610     private function trigger()
611     {
612          $this->core->blog->triggerBlog();
613     }
614     
615     # Check if an URL is well formed
616     public static function validateURL($url)
617     {
618          if (
619           /*(function_exists('filter_var') && !filter_var($url, FILTER_VALIDATE_URL)) // pff http://my-url.com is not valid!
620           || */false !== strpos($url,'localhost')
621           || false === strpos($url,'http://')
622           || false !== strpos($url,'http://192.')
623          )
624          {
625               return false;
626          }
627          return true;
628     }
629     
630     public static function absoluteURL($root,$url)
631     {
632          $host = preg_replace('|^([a-z]{3,}://)(.*?)/(.*)$|','$1$2',$root);
633         
634          $parse = parse_url($url);
635          if (empty($parse['scheme']))
636          {
637               if (strpos($url,'/') === 0)
638               {
639                    $url = $host.$url;
640               }
641               elseif (strpos($url,'#') === 0)
642               {
643                    $url = $root.$url;
644               }
645               elseif (preg_match('|/$|',$root))
646               {
647                    $url = $root.$url;
648               }
649               else
650               {
651                    $url = dirname($root).'/'.$url;
652               }
653          }
654          return $url;
655     }
656     
657     public static function getAllStatus()
658     {
659          return array(
660               __('disabled') => '0',
661               __('enabled') => '1'
662          );
663     }
664     
665     public static function getAllUpdateInterval()
666     {
667          return array(
668               __('every hour') => 3600,
669               __('every two hours') => 7200,
670               __('two times per day') => 43200,
671               __('every day') => 86400,
672               __('every two days') => 172800,
673               __('every week') => 604800
674          );
675     }
676     
677     public function getAllBlogAdmins()
678     {
679          $admins = array();
680         
681          # Get super admins
682          $rs = $this->con->select(
683               'SELECT user_id, user_super, user_name, user_firstname, user_displayname '.
684               'FROM '.$this->con->escapeSystem($this->core->prefix.'user').' '.
685               'WHERE user_super = 1 AND user_status = 1 '
686          );
687         
688          if (!$rs->isEmpty())
689          {
690               while ($rs->fetch())
691               {
692                    $user_cn = dcUtils::getUserCN($rs->user_id, $rs->user_name, $rs->user_firstname, $rs->user_displayname);
693                    $admins[$user_cn.' (super admin)'] = $rs->user_id;
694               }
695          }
696         
697          # Get admins
698          $rs = $this->con->select(
699               'SELECT U.user_id, U.user_super, U.user_name, U.user_firstname, U.user_displayname '.
700               'FROM '.$this->con->escapeSystem($this->core->prefix.'user').' U '.
701               'LEFT JOIN '.$this->con->escapeSystem($this->core->prefix.'permissions').' P '.
702               'ON U.user_id=P.user_id '.
703               'WHERE U.user_status = 1 '.
704               "AND P.blog_id = '".$this->blog."' ".
705               "AND P.permissions LIKE '%|admin|%' "
706          );
707         
708          if (!$rs->isEmpty())
709          {
710               while ($rs->fetch())
711               {
712                    $user_cn = dcUtils::getUserCN($rs->user_id, $rs->user_name, $rs->user_firstname, $rs->user_displayname);
713                    $admins[$user_cn.' (admin)'] = $rs->user_id;
714               }
715          }
716         
717          return $admins;
718     }
719     
720     # Get list of urls where entries could be hacked
721     public static function getPublicUrlTypes($core)
722     {
723          $types = array();
724         
725          # --BEHAVIOR-- zoneclearFeedServerPublicUrlTypes
726          $core->callBehavior('zoneclearFeedServerPublicUrlTypes',$types);
727         
728          $types[__('home page')] = 'default';
729          $types[__('post pages')] = 'post';
730          $types[__('tags pages')] = 'tag';
731          $types[__('archives pages')] = 'archive';
732          $types[__('category pages')] = 'category';
733          $types[__('entries feed')] = 'feed';
734         
735          return $types;
736     }
737     
738     # Take care about tweakurls (thanks Mathieu M.)
739     // not a good way but job is done
740     public static function tweakurlsAfterPostCreate($cur,$id)
741     {
742          global $core;
743          $cur->post_url = tweakUrls::tweakBlogURL($cur->post_url);
744          $core->auth->sudo(array($core->blog,'updPost'),$id,$cur);
745     }
746}
747?>
Note: See TracBrowser for help on using the repository browser.

Sites map