OSDN Git Service

CHANGE: NucleusPluginクラスの整理
[nucleus-jp/nucleus-next.git] / nucleus / libs / MANAGER.php
1 <?php
2 /*
3  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4  * Copyright (C) 2002-2009 The Nucleus Group
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  * (see nucleus/documentation/index.html#license for more info)
11  */
12 /**
13  * This class makes sure each item/weblog/comment object gets requested from
14  * the database only once, by keeping them in a cache. The class also acts as
15  * a dynamic classloader, loading classes _only_ when they are first needed,
16  * hoping to diminish execution time
17  *
18  * The class is a singleton, meaning that there will be only one object of it
19  * active at all times. The object can be requested using MANAGER::instance()
20  *
21  * @license http://nucleuscms.org/license.txt GNU General Public License
22  * @copyright Copyright (C) 2002-2009 The Nucleus Group
23  * @version $Id: MANAGER.php 1624 2012-01-09 11:36:20Z sakamocchi $
24  */
25 class MANAGER
26 {
27
28         /**
29          * Cached ITEM, BLOG, PLUGIN, KARMA and MEMBER objects. When these objects are requested
30          * through the global $manager object (getItem, getBlog, ...), only the first call
31          * will create an object. Subsequent calls will return the same object.
32          *
33          * The $items, $blogs, ... arrays map an id to an object (for plugins, the name is used
34          * rather than an ID)
35          */
36         var $items;
37         var $blogs;
38         var $plugins;
39         var $karma;
40         var $templates;
41         var $members;
42
43         /**
44          * cachedInfo to avoid repeated SQL queries (see pidInstalled/pluginInstalled/getPidFromName)
45          * e.g. which plugins exists?
46          *
47          * $cachedInfo['installedPlugins'] = array($pid -> $name)
48          */
49         var $cachedInfo;
50
51         /**
52          * The plugin subscriptionlist
53          *
54          * The subcription array has the following structure
55          *              $subscriptions[$EventName] = array containing names of plugin classes to be
56          *                                                                       notified when that event happens
57          */
58         var $subscriptions;
59
60         /**
61          * Returns the only instance of this class. Creates the instance if it
62          * does not yet exists. Users should use this function as
63          * $manager =& MANAGER::instance(); to get a reference to the object
64          * instead of a copy
65          */
66         function &instance()
67         {
68                 static $instance = array();
69                 if ( empty($instance) )
70                 {
71                         $instance[0] = new MANAGER();
72                 }
73                 return $instance[0];
74         }
75
76         /**
77          * The constructor of this class initializes the object caches
78          */
79         function MANAGER()
80         {
81                 $this->items = array();
82                 $this->blogs = array();
83                 $this->plugins = array();
84                 $this->karma = array();
85                 $this->parserPrefs = array();
86                 $this->cachedInfo = array();
87                 return;
88         }
89
90         /**
91          * Returns the requested item object. If it is not in the cache, it will
92          * first be loaded and then placed in the cache.
93          * Intended use: $item =& $manager->getItem(1234)
94          */
95         function &getItem($itemid, $allowdraft, $allowfuture)
96         {
97                 $item =& $this->items[$itemid];
98
99                 // check the draft and future rules if the item was already cached
100                 if ( $item )
101                 {
102                         if ((!$allowdraft) && ($item['draft']))
103                                 return 0;
104
105                         $blog =& $this->getBlog(getBlogIDFromItemID($itemid));
106                         if ( (!$allowfuture) && ($item['timestamp'] > $blog->getCorrectTime()) )
107                         {
108                                 return 0;
109                         }
110                 }
111                 if ( !$item )
112                 {
113                         // load class if needed
114                         $this->loadClass('ITEM');
115                         // load item object
116                         $item = ITEM::getitem($itemid, $allowdraft, $allowfuture);
117                         $this->items[$itemid] = $item;
118                 }
119                 return $item;
120         }
121
122         /**
123          * Loads a class if it has not yet been loaded
124          */
125         function loadClass($name)
126         {
127                 $this->_loadClass($name, $name . '.php');
128                 return;
129         }
130
131         /**
132          * Checks if an item exists
133          */
134         function existsItem($id,$future,$draft)
135         {
136                 $this->_loadClass('ITEM','ITEM.php');
137                 return ITEM::exists($id,$future,$draft);
138         }
139
140         /**
141          * Checks if a category exists
142          */
143         function existsCategory($id)
144         {
145                 return (quickQuery('SELECT COUNT(*) as result FROM '.sql_table('category').' WHERE catid='.intval($id)) > 0);
146         }
147
148         /**
149          * Returns the blog object for a given blogid
150          */
151         function &getBlog($blogid)
152         {
153                 $blog =& $this->blogs[$blogid];
154
155                 if ( !$blog )
156                 {
157                         // load class if needed
158                         $this->_loadClass('BLOG','BLOG.php');
159                         // load blog object
160                         $blog = new BLOG($blogid);
161                         $this->blogs[$blogid] =& $blog;
162                 }
163                 return $blog;
164         }
165
166         /**
167          * Checks if a blog exists
168          */
169         function existsBlog($name)
170         {
171                 $this->_loadClass('BLOG','BLOG.php');
172                 return BLOG::exists($name);
173         }
174
175         /**
176          * Checks if a blog id exists
177          */
178         function existsBlogID($id)
179         {
180                 $this->_loadClass('BLOG','BLOG.php');
181                 return BLOG::existsID($id);
182         }
183
184         /**
185          * Returns a previously read template
186          */
187         function &getTemplate($templateName)
188         {
189                 $template =& $this->templates[$templateName];
190
191                 if ( !$template )
192                 {
193                         $template = TEMPLATE::read($templateName);
194                         $this->templates[$templateName] =& $template;
195                 }
196                 return $template;
197         }
198
199         /**
200          * Returns a KARMA object (karma votes)
201          */
202         function &getKarma($itemid)
203         {
204                 $karma =& $this->karma[$itemid];
205
206                 if ( !$karma ) {
207                         // load class if needed
208                         $this->_loadClass('KARMA','KARMA.php');
209                         // create KARMA object
210                         $karma = new KARMA($itemid);
211                         $this->karma[$itemid] =& $karma;
212                 }
213                 return $karma;
214         }
215
216         /**
217          * Returns a MEMBER object
218          */
219         function &getMember($memberid)
220         {
221                 $mem =& $this->members[$memberid];
222
223                 if ( !$mem )
224                 {
225                         // load class if needed
226                         $this->_loadClass('MEMBER','MEMBER.php');
227                         // create MEMBER object
228                         $mem =& MEMBER::createFromID($memberid);
229                         $this->members[$memberid] =& $mem;
230                 }
231                 return $mem;
232         }
233
234         /**
235          * Set the global parser preferences
236          */
237         function setParserProperty($name, $value)
238         {
239                 $this->parserPrefs[$name] = $value;
240                 return;
241         }
242         
243         /**
244          * Get the global parser preferences
245          */
246         function getParserProperty($name)
247         {
248                 return $this->parserPrefs[$name];
249         }
250
251         /**
252           * A helper function to load a class
253           * 
254           *     private
255           */
256         function _loadClass($name, $filename)
257         {
258                 if ( !class_exists($name) )
259                 {
260                                 global $DIR_LIBS;
261                                 include($DIR_LIBS . $filename);
262                 }
263                 return;
264         }
265
266         /**
267          * MANAGER::_loadPlugin()
268          * loading a certain plugin
269          * 
270          * @param       string $name plugin name
271          * @return      void
272          */
273         function _loadPlugin($name)
274         {
275                 if ( !class_exists($name) )
276                 {
277                                 global $DIR_PLUGINS;
278                                 
279                                 $fileName = $DIR_PLUGINS . $name . '.php';
280                                 
281                                 if ( !file_exists($fileName) )
282                                 {
283                                         if ( !defined('_MANAGER_PLUGINFILE_NOTFOUND') )
284                                         {
285                                                 define('_MANAGER_PLUGINFILE_NOTFOUND', 'Plugin %s was not loaded (File not found)');
286                                         }
287                                         ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINFILE_NOTFOUND, $name)); 
288                                         return 0;
289                                 }
290                                 
291                                 // load plugin
292                                 include($fileName);
293                                 
294                                 // check if class exists (avoid errors in eval'd code)
295                                 if ( !class_exists($name) )
296                                 {
297                                         ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINFILE_NOCLASS, $name));
298                                         return 0;
299                                 }
300                                 
301                                 // add to plugin array
302                                 eval('$this->plugins[$name] = new ' . $name . '();');
303                                 
304                                 // get plugid
305                                 $this->plugins[$name]->setID($this->getPidFromName($name));
306                                 
307                                 // unload plugin if a prefix is used and the plugin cannot handle this^
308                                 global $MYSQL_PREFIX;
309                                 if ( ($MYSQL_PREFIX != '')
310                                   && !$this->plugins[$name]->supportsFeature('SqlTablePrefix') )
311                                 {
312                                         unset($this->plugins[$name]);
313                                         ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINTABLEPREFIX_NOTSUPPORT, $name));
314                                         return 0;
315                                 }
316                                 
317                                 // unload plugin if using non-mysql handler and plugin does not support it 
318                                 global $MYSQL_HANDLER;
319                                 if ( (!in_array('mysql',$MYSQL_HANDLER))
320                                   && !$this->plugins[$name]->supportsFeature('SqlApi') )
321                                 {
322                                         unset($this->plugins[$name]);
323                                         ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINSQLAPI_NOTSUPPORT, $name));
324                                         return 0;
325                                 }
326                                 
327                                 // call init method
328                                 $this->plugins[$name]->init();
329                 }
330                 return;
331         }
332
333         /**
334          * Returns a PLUGIN object
335          */
336         function &getPlugin($name)
337         {
338                 // retrieve the name of the plugin in the right capitalisation
339                 $name = $this->getUpperCaseName ($name);
340                 // get the plugin       
341                 $plugin =& $this->plugins[$name]; 
342
343                 if ( !$plugin )
344                 {
345                         // load class if needed
346                         $this->_loadPlugin($name);
347                         $plugin =& $this->plugins[$name];
348                 }
349                 return $plugin;
350         }
351
352         /**
353           * Checks if the given plugin IS loaded or not
354           */
355         function &pluginLoaded($name)
356         {
357                 $plugin =& $this->plugins[$name];
358                 return $plugin;
359         }
360                 
361         function &pidLoaded($pid)
362         {
363                 $plugin=false;
364                 reset($this->plugins);
365                 while ( list($name) = each($this->plugins) )
366                 {
367                         if ( $pid!=$this->plugins[$name]->getId() )
368                         {
369                                 continue;
370                         }
371                         $plugin= & $this->plugins[$name];
372                         break;
373                 }
374                 return $plugin;
375         }
376
377         /**
378           * checks if the given plugin IS installed or not
379           */
380         function pluginInstalled($name)
381         {
382                 $this->_initCacheInfo('installedPlugins');
383                 return ($this->getPidFromName($name) != -1);
384         }
385
386         function pidInstalled($pid)
387         {
388                 $this->_initCacheInfo('installedPlugins');
389                 return ($this->cachedInfo['installedPlugins'][$pid] != '');
390         }
391
392         function getPidFromName($name)
393         {
394                 $this->_initCacheInfo('installedPlugins');
395                 foreach ( $this->cachedInfo['installedPlugins'] as $pid => $pfile )
396                 {
397                         if (strtolower($pfile) == strtolower($name))
398                         {
399                                 return $pid;
400                         }
401                 }
402                 return -1;
403         }
404
405         /**
406           * Retrieve the name of a plugin in the right capitalisation
407           */
408         function getUpperCaseName ($name)
409         {
410                 $this->_initCacheInfo('installedPlugins');
411                 foreach ( $this->cachedInfo['installedPlugins'] as $pid => $pfile )
412                 {
413                         if ( strtolower($pfile) == strtolower($name) )
414                         {
415                                 return $pfile;
416                         }
417                 }
418                 return -1;
419         }
420         
421         function clearCachedInfo($what)
422         {
423                 unset($this->cachedInfo[$what]);
424                 return;
425         }
426
427         /**
428          * Loads some info on the first call only
429          */
430         function _initCacheInfo($what)
431         {
432                 if ( isset($this->cachedInfo[$what]) && is_array($this->cachedInfo[$what]) )
433                 {
434                         return;
435                 }
436                 switch ($what)
437                 {
438                         // 'installedPlugins' = array ($pid => $name)
439                         case 'installedPlugins':
440                                 $this->cachedInfo['installedPlugins'] = array();
441                                 $res = sql_query('SELECT pid, pfile FROM ' . sql_table('plugin'));
442                                 while ( $o = sql_fetch_object($res) )
443                                 {
444                                         $this->cachedInfo['installedPlugins'][$o->pid] = $o->pfile;
445                                 }
446                                 break;
447                 }
448                 return;
449         }
450
451         /**
452          * A function to notify plugins that something has happened. Only the plugins
453          * that are subscribed to the event will get notified.
454          * Upon the first call, the list of subscriptions will be fetched from the
455          * database. The plugins itsself will only get loaded when they are first needed
456          *
457          * @param $eventName
458          *              Name of the event (method to be called on plugins)
459          * @param $data
460          *              Can contain any type of data, depending on the event type. Usually this is
461          *              an itemid, blogid, ... but it can also be an array containing multiple values
462          */
463         function notify($eventName, $data)
464         {
465                 // load subscription list if needed
466                 if ( !is_array($this->subscriptions) )
467                 {
468                         $this->_loadSubscriptions();
469                 }
470                 
471                 // get listening objects
472                 $listeners = false;
473                 if ( isset($this->subscriptions[$eventName]) )
474                 {
475                         $listeners = $this->subscriptions[$eventName];
476                 }
477
478                 // notify all of them
479                 if ( is_array($listeners) )
480                 {
481                         foreach( $listeners as $listener )
482                         {
483                                 // load class if needed
484                                 $this->_loadPlugin($listener);
485                                 // do notify (if method exists)
486                                 if ( isset($this->plugins[$listener])
487                                   && method_exists($this->plugins[$listener], 'event_' . $eventName))
488                                 {
489                                         call_user_func(array(&$this->plugins[$listener],'event_' . $eventName), $data);
490                                 }
491                         }
492                 }
493                 return;
494         }
495
496         /**
497          * Loads plugin subscriptions
498          */
499         function _loadSubscriptions()
500         {
501                 // initialize as array
502                 $this->subscriptions = array();
503
504                 $res = sql_query('SELECT p.pfile as pfile, e.event as event FROM '.sql_table('plugin_event').' as e, '.sql_table('plugin').' as p WHERE e.pid=p.pid ORDER BY p.porder ASC');
505                 while ( $o = sql_fetch_object($res) )
506                 {
507                         $pluginName = $o->pfile;
508                         $eventName = $o->event;
509                         $this->subscriptions[$eventName][] = $pluginName;
510                 }
511                 return;
512         }
513
514         /*
515                 Ticket functions. These are uses by the admin area to make it impossible to simulate certain GET/POST
516                 requests. tickets are user specific
517         */
518
519         var $currentRequestTicket = '';
520
521         /**
522          * GET requests: Adds ticket to URL (URL should NOT be html-encoded!, ticket is added at the end)
523          */
524         function addTicketToUrl($url)
525         {
526                 $ticketCode = 'ticket=' . $this->_generateTicket();
527                 if ( strstr($url, '?') )
528                 {
529                         $ticketCode = "{$url}&{$ticketCode}";
530                 }
531                 else
532                 {
533                         $ticketCode = "{$url}?{$ticketCode}";
534                 }
535                 return $ticketCode;
536         }
537
538         /**
539          * POST requests: Adds ticket as hidden formvar
540          */
541         function addTicketHidden()
542         {
543                 $ticket = $this->_generateTicket();
544                 echo '<input type="hidden" name="ticket" value="', i18n::hsc($ticket), '" />';
545                 return;
546         }
547
548         /**
549          * Get a new ticket
550          * (xmlHTTPRequest AutoSaveDraft uses this to refresh the ticket)
551          */
552         function getNewTicket()
553         {
554                 $this->currentRequestTicket = '';
555                 return $this->_generateTicket();
556         }
557
558         /**
559          * Checks the ticket that was passed along with the current request
560          */
561         function checkTicket()
562         {
563                 global $member;
564
565                 // get ticket from request
566                 $ticket = requestVar('ticket');
567
568                 // no ticket -> don't allow
569                 if ( $ticket == '' )
570                 {
571                         return false;
572                 }
573
574                 // remove expired tickets first
575                 $this->_cleanUpExpiredTickets();
576
577                 // get member id
578                 if (!$member->isLoggedIn())
579                 {
580                         $memberId = -1;
581                 }
582                 else
583                 {
584                         $memberId = $member->getID();
585                 }
586
587                 // check if ticket is a valid one
588                 $query = 'SELECT COUNT(*) as result FROM ' . sql_table('tickets') . ' WHERE member=' . intval($memberId). ' and ticket=\''.sql_real_escape_string($ticket).'\'';
589
590                 /*
591                  * NOTE:
592                  * [in the original implementation, the checked ticket was deleted. This would lead to invalid
593                  * tickets when using the browsers back button and clicking another link/form
594                  * leaving the keys in the database is not a real problem, since they're member-specific and
595                  * only valid for a period of one hour]
596                  */
597                 if ( quickQuery($query) == 1 )
598                 {
599                         return true;
600                 }
601                 // not a valid ticket
602                 else
603                 {
604                         return false;
605                 }
606         }
607
608         /**
609          * (internal method) Removes the expired tickets
610          */
611         function _cleanUpExpiredTickets()
612         {
613                 // remove tickets older than 1 hour
614                 $oldTime = time() - 60 * 60;
615                 $query = 'DELETE FROM ' . sql_table('tickets'). ' WHERE ctime < \'' . date('Y-m-d H:i:s',$oldTime) .'\'';
616                 sql_query($query);
617                 return;
618         }
619
620         /**
621          * (internal method) Generates/returns a ticket (one ticket per page request)
622          */
623         function _generateTicket()
624         {
625                 if ( $this->currentRequestTicket == '' )
626                 {
627                         // generate new ticket (only one ticket will be generated per page request)
628                         // and store in database
629                         global $member;
630                         // get member id
631                         if ( !$member->isLoggedIn() )
632                         {
633                                 $memberId = -1;
634                         }
635                         else
636                         {
637                                 $memberId = $member->getID();
638                         }
639
640                         $ok = false;
641                         while ( !$ok )
642                         {
643                                 // generate a random token
644                                 srand((double)microtime()*1000000);
645                                 $ticket = md5(uniqid(rand(), true));
646
647                                 // add in database as non-active
648                                 $query = 'INSERT INTO ' . sql_table('tickets') . ' (ticket, member, ctime) ';
649                                 $query .= 'VALUES (\'' . sql_real_escape_string($ticket). '\', \'' . intval($memberId). '\', \'' . date('Y-m-d H:i:s',time()) . '\')';
650                                 if ( sql_query($query) )
651                                 {
652                                         $ok = true;
653                                 }
654                         }
655                         $this->currentRequestTicket = $ticket;
656                 }
657                 return $this->currentRequestTicket;
658         }
659 }
660