OSDN Git Service

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