/[drupal]/contributions/modules/og_blueprints/og_blueprints.module
ViewVC logotype

Contents of /contributions/modules/og_blueprints/og_blueprints.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Thu Apr 17 21:17:25 2008 UTC (19 months, 1 week ago) by sdboyer
Branch: MAIN
CVS Tags: DRUPAL-5--1-0-ALPHA1, HEAD
Branch point for: DRUPAL-5
File MIME type: text/x-php
ready for alpha1 release
1 <?php
2 // $Id$
3
4 /**
5 * @file og_blueprints.module
6 * The og_blueprints.module file, which manages the creation and deployment of
7 * 'bundles' of 'blueprints' in conjunction with Organic Groups.
8 *
9 * Bundles are sets of panels (as in the Panels and OG Panels modules) that
10 * can be paired with group types. Once a bundle of panels has been paired
11 * with a group type, every new group of that type will automatically be granted
12 * their own duplicate set of the panels in the bundle.
13 *
14 * ABBREVIATIONS UTILIZED IN THIS MODULE:
15 * - bid: 'Bundle ID'. This is the primary key of the og_bundle table; one bid
16 * is created per group type, regardless of operating mode.
17 *
18 * - vid: 'Revision ID'. This is the autoincrementing primary key of the
19 * og_bundle_revision table, which is an intermediary join table used to keep
20 * track of release information. Note that although 'vid' appears in multiple
21 * tables, it ALWAYS refers to the bundle's revision number. Revision numbers
22 * for blueprints are not maintained.
23 *
24 * - bpid: 'Blueprint ID'. This is part of the joint primary key of the og_blueprint
25 * table. A unique bpid is assigned for every blueprint you create.
26 *
27 * - bpname: 'Blueprint Name'. This is the internal system name that has been
28 * assigned to a particular blueprint. Note that this value is NEVER visible to
29 * the end user. It has NOTHING to do with the page_title value, which is what
30 * the end user sees upon blueprint instantiation.
31 */
32
33 /**
34 * Definition of the base abstract class for og_bundles.
35 *
36 * Declaring as abstract allows us to programatically ensure that even empty
37 * og_bundle objects are still loaded with exactly the right parameters
38 *
39 */
40 abstract class og_bundle {
41 public $bid, $vid, $grouptype, $group_name, $release_id; // FIXME code the release ID pickup
42 protected $blueprints = array();
43
44 public function __construct() {
45 list($this->bid, $this->vid, $this->release_id) = array_values(db_fetch_array(db_query(<<<QUERY
46 SELECT b.bid, b.vid, r.release_id FROM {og_bundle} AS b
47 INNER JOIN {og_bundle_revision} AS r ON b.bid = r.bid
48 WHERE b.grouptype = '%s' ORDER BY r.release_id DESC LIMIT 1
49 QUERY
50 , $this->grouptype)));
51 $group_types = og_blueprints_group_types(TRUE);
52 $this->group_name = $group_types[$this->grouptype];
53 }
54
55 protected function load_all_blueprints() {
56 $blueprint_type = preg_replace('/bundle/', 'blueprint', get_class($this));
57 $blueprints = db_query("SELECT bpid FROM {og_blueprint} WHERE vid = %d", $this->vid);
58 while ($blueprint = db_fetch_array($blueprints)) {
59 $this->blueprints[$blueprint['bpid']] =& new $blueprint_type(array('bpid' => $blueprint['bpid'], 'vid' => $this->vid));
60 }
61 }
62
63 public function sort_for_display() {
64 uasort(&$this->blueprints, 'og_blueprints_sort_bundle');
65 }
66
67 public function increment_bid_version() {
68 db_query("INSERT INTO {og_bundle_revision} (bid) VALUES (%d)", $this->bid);
69 db_query("UPDATE {og_bundle} SET vid = (SELECT vid FROM {og_bundle_revision} WHERE bid = %d ORDER BY vid DESC LIMIT 1) , release_id = %d WHERE bid = %d", $this->bid, $this->release_id, $this->bid);
70 $this->vid = db_result(db_query("SELECT vid FROM {og_bundle} WHERE bid = %d", $this->bid)); // FIXME be consistent with make_release(), whichever way you go
71 }
72
73 function &__get($member) {
74 switch (TRUE) {
75 case (is_numeric($member) && in_array($member, array_keys(og_blueprints_list()))):
76 return $this->blueprints[$member];
77
78 case ($member == 'blueprints'):
79 return $this->blueprints;
80 }
81 }
82 }
83
84 class og_blueprint {
85 public $bid, $vid, $bpid, $bpname, $did, $linked, $enabled, $published, $default_page, $page_title, $path, $show_blocks, $weight;
86 private $grouptype, $display, $context, $content_types;
87
88 /**
89 * Load a blueprint. The constructor dynamically determines the method for loading the
90 * blueprint based on which args are passed in.
91 *
92 * @param array $args
93 * An associative array of known blueprint values keyed by the respective field names of those values.
94 */
95 public function __construct($args) {
96 foreach ($args as $field => $value) {
97 $this->$field = $value;
98 }
99 $this->load($args);
100 }
101
102 protected function load($args) {
103 $fields = og_blueprints_query_fields();
104 foreach ($args as $member => $value) {
105 $fq[] = $member ." = ". $fields[$member];
106 $v[] = $this->$member;
107 }
108 $string = preg_replace(array('/grouptype/', '/bid/', '/bpid/', '/vid/', '/did/'), array('b.grouptype', 'b.bid', 'bp.bpid', 'bp.vid', 'bp.did'), implode(' AND ', $fq));
109 $blueprint = db_fetch_array(db_query(<<<QUERY
110 SELECT
111 b.bid, b.grouptype,
112 r.vid,
113 bp.bpid, bp.bpname, bp.did, bp.linked, bp.enabled, bp.published, bp.default_page, bp.page_title, bp.path, bp.show_blocks, bp.weight
114 FROM {og_blueprint} AS bp
115 LEFT JOIN {og_bundle_revision} AS r ON bp.vid = r.vid
116 LEFT JOIN {og_bundle} AS b ON bp.bid = b.bid
117 WHERE $string
118 QUERY
119 , $v));
120 foreach ($blueprint as $field => $value) {
121 $this->$field = $value;
122 }
123 }
124
125 protected function export() {
126 $code = panels_export_display(panels_load_display($this->did));
127 eval($code);
128 panels_save_display($display);
129 $this->did = $display->did;
130 }
131
132 private function write_to_db($op, $args) {
133 $args = og_blueprints_query_fields($args);
134 switch ($op) {
135 case 'update':
136 // ONLY allow db manipulation if we're on the current version.
137 if ($this->bid == db_result(db_query("SELECT DISTINCT b.bid FROM {og_bundle} AS b LEFT JOIN {og_blueprint} AS bp ON b.vid = bp.vid WHERE bp.vid = %d", $this->vid))) {
138 foreach ($args as $member => $query_sub) {
139 if (in_array($member, array('bpid', 'bid', 'vid', 'bpname'))) {
140 list($f[], $q[], $vw[]) = array($member, $query_sub, $this->$member);
141 continue;
142 }
143 list($fsq[], $v[]) = array($member ." = ". $query_sub, $this->$member);
144 }
145 db_query("UPDATE {og_blueprint} SET ". implode(' , ', $fsq) ." WHERE (". implode(' , ', $f) .") = (". implode(' , ', $q) .")", array_merge($v, $vw));
146 }
147 break;
148
149 case 'insert':
150 foreach ($args as $member => $query_sub) {
151 if (isset($this->$member)) {
152 $f[] = $member;
153 $q[] = $query_sub;
154 $v[] = $this->$member;
155 }
156 }
157 db_query('INSERT INTO {og_blueprint} (' . implode(' , ', $f) . ') VALUES (' . implode(', ', $q) . ')', $v);
158 break;
159 }
160 }
161
162 public function __call($method, $args = NULL) {
163 switch ($method) {
164 case 'propagate':
165 case 'save':
166 $this->write_to_db('update', !empty($args) ? $args : array('bid', 'vid', 'bpid', 'did', 'linked', 'enabled', 'published', 'default_page', 'page_title', 'show_blocks', 'path', 'weight'));
167 break;
168
169 case 'propagate':
170 $result = db_query("SELECT vid, bid FROM {og_bundle}");
171 while ($row = db_fetch_array($result)) {
172 list ($this->vid, $this->bid) = array_values($row);
173 $this->write_to_db('update', $args);
174 }
175 break;
176
177 case 'delete':
178 $did_data = db_query("SELECT DISTINCT(bp.did) FROM {og_bundle} AS b INNER JOIN {og_bundle_revision} AS r ON b.vid = r.vid INNER JOIN {og_blueprint} AS bp ON b.vid = bp.vid WHERE bpid = %d");
179 while ($did = db_fetch_array($did_data)) {
180 panels_delete_display($did);
181 }
182 db_query("DELETE bp FROM {og_bundle} AS b INNER JOIN {og_bundle_revision} AS r ON b.vid = r.vid INNER JOIN {og_blueprint} AS bp ON b.vid = bp.vid WHERE bpid = %d", $this->bpid);
183 }
184 }
185
186 protected function insert($args = NULL) {
187 $this->write_to_db('insert', !is_null($args) ? $args : array('bid', 'vid', 'bpid', 'bpname', 'did', 'linked', 'enabled', 'published', 'default_page', 'page_title', 'show_blocks', 'path'));
188 }
189
190 // magic method for snagging certain requested members on demand
191 function __get($member) {
192 if (isset($this->$member)) {
193 return $this->$member;
194 }
195 switch ($member) {
196 case 'display':
197 case 'context':
198 panels_load_include('plugins');
199 $this->display = panels_load_display($this->did);
200 $this->context = $this->display->context = array('og_panels' => panels_context_create_empty('group'));
201 return $member == 'display' ? $this->display : $this->context;
202
203 case 'content_types':
204 panels_load_include('common');
205 return $this->content_types = $this->display->content_types = panels_common_get_allowed_types('og_panels', $this->context);
206
207 case 'grouptype':
208 return $this->grouptype = db_result(db_query("SELECT grouptype FROM {og_bundle} WHERE bid = %d", $this->bid));
209
210 }
211 }
212 }
213
214 class og_bundle_master extends og_bundle {
215
216 function __construct() {
217 $this->grouptype = 'master';
218 parent::__construct();
219 $this->load_all_blueprints();
220 $this->sort_for_display();
221 }
222
223 public function dashboard() {
224 if (og_blueprints_mode() == 2) {
225 $dashboard['version_text'] = array('#value' => t('Active Release: ') . '<strong>N/A</strong>');
226 $dashboard['new_release'] = array('#value' => '<em>' . t("Can't release Master bundle") . '</em>');
227 }
228 else {
229 $dashboard['new_release'] = array('#value' => l(t('Create new release'), "admin/og/og_blueprints/release/create/$this->grouptype"));
230 $dashboard['version_text'] = array('#value' => t('Active Release: ') . '<strong>' . (!empty($this->release_id) ? $this->release_id : 'NONE') . '</strong>');
231 }
232 foreach ($dashboard as $key => $item) {
233 $dashboard[$key] = drupal_render($item); // FIXME just do em directly above
234 }
235 return $dashboard;
236 }
237 }
238
239 class og_blueprint_master extends og_blueprint {
240 public $number_linked;
241
242 function __construct($args) {
243 parent::__construct(array_merge($args, array('bid' => 1)));
244 $this->number_linked = db_result(db_query("SELECT COUNT(*) FROM {og_blueprint} WHERE bid <> %d AND did = %d", $this->bid, $this->did));
245 }
246 }
247
248 class og_bundle_typed extends og_bundle {
249 public $defaults = array();
250
251 function __construct($grouptype) {
252 $this->grouptype = $grouptype;
253 parent::__construct();
254 $this->load_all_blueprints();
255 $this->sort_for_display();
256 }
257
258 public function dashboard() {
259 $dashboard['version_text'] = array('#value' => t('Active Release: ') . '<strong>' . (!empty($this->release_id) ? $this->release_id : 'NONE') . '</strong>');
260 $dashboard['new_release'] = array('#value' => l(t('Create new release'), "admin/og/og_blueprints/release/create/$this->grouptype"));
261 // $dashboard['new_blueprint'] = array('#value' => l(t('Add new blueprint'), 'admin/og/og_blueprints/blueprint/form', array()));
262 foreach ($dashboard as $key => $item) {
263 $dashboard[$key] = drupal_render($item);
264 }
265 return $dashboard;
266 }
267 }
268
269 class og_blueprint_typed extends og_blueprint {
270
271 public function relink() {
272 panels_delete_display($this->did);
273 $this->did = db_result(db_query("SELECT did FROM {og_blueprint} WHERE bid = 1 AND bpid = %d ORDER BY vid DESC LIMIT 1", $this->bpid));
274 $this->linked = 1;
275 $this->save();
276 }
277
278 public function delink() {
279 $this->export();
280 $this->linked = 0;
281 $this->save();
282 }
283 }
284
285 class og_bundle_new_release extends og_bundle {
286 public $linked_dids = array();
287
288 function __construct($grouptype, $release_name) {
289 $this->grouptype = $grouptype;
290 $this->release_name = $release_name;
291 parent::__construct();
292 $this->load_all_blueprints();
293 $this->make_release();
294 }
295
296 private function make_release() {
297 db_query("UPDATE {og_bundle_revision} SET release_id = %d , release_name = '%s' , timestamp = %d WHERE vid = %d", ++$this->release_id, $this->release_name, time(), $this->vid);
298 $this->increment_bid_version();
299 foreach ($this->blueprints as $bpid => &$blueprint) {
300 $blueprint->new_version($this->vid);
301 if ($this->bid != 1) {
302 $blueprint->update_dids();
303 }
304 }
305 if ($this->bid == 1) {
306 $this->master_release_updates();
307 }
308 }
309
310 private function master_release_updates() {
311 foreach (db_fetch_array(db_query("SELECT grouptype FROM {og_bundle} WHERE bid <> 1")) as $group_type) {
312 $bundle = new og_bundle_typed($group_type);
313 $bundle->load_all_blueprints();
314 db_query("UPDATE {og_bundle_revision} SET release_id = %d , release_name = '%s' , timestamp = %d WHERE vid = (SELECT vid FROM {og_bundle} WHERE bid = %d)", ++$bundle->release_id, $this->release_name, time(), $bundle->bid);
315 $this->increment_bid_version();
316 foreach ($bundle->blueprints as $bpid => &$blueprint) {
317 $blueprint->vid = $bundle->vid;
318 $blueprint->did = $this->blueprints[$bpid]->did;
319 $blueprint->save();
320 }
321 }
322 }
323 }
324
325 class og_blueprint_new_release extends og_blueprint {
326 private $old_did;
327
328 public function new_version($vid) { // very carefully, specifically ordered function. whew!
329 $this->old_did = $this->did;
330 $this->export();
331 $this->vid = $vid;
332 $this->insert();
333 }
334
335 public function update_dids() {
336 if ($this->linked) {
337 db_query("UPDATE {og_blueprint} SET did = %d WHERE did = %d AND vid <> %d", $this->did, $this->old_did, $this->vid);
338 }
339 }
340 }
341
342 /**
343 * Constructs an og_bundle object with data from a specific release version.
344 *
345 */
346 class og_bundle_release extends og_bundle {
347
348 function __construct($group_type, $release_id) {
349 $this->grouptype = $group_type;
350 $this->release_id = $release_id;
351 list($this->bid, $this->vid) = array_values(db_fetch_array(db_query("SELECT b.bid, r.vid FROM {og_bundle} AS b INNER JOIN {og_bundle_revision} AS r ON b.bid = r.bid WHERE b.grouptype = '%s' AND r.release_id = %d", $this->grouptype, $this->release_id)));
352 $this->load_all_blueprints();
353 $this->blueprints = array_filter($this->blueprints, array($this, 'filter_disabled')); // TODO should this be here? probably.
354 }
355
356 protected function filter_disabled($item) {
357 return $item->enabled == 1;
358 }
359 }
360
361 class og_blueprint_release extends og_blueprint {
362
363 public $release_id, $release_name;
364
365 public function load($args) {
366 parent::load($args);
367 list($this->release_id, $this->release_name) = array_values(db_fetch_array(db_query("SELECT release_id, release_name FROM {og_bundle_revision} WHERE vid = %d", $this->vid)));
368 }
369 }
370
371 /**
372 * Class simply creates a release bundle using the preferred release.
373 *
374 */
375 class og_bundle_instantiator extends og_bundle_release {
376 public $nid;
377
378 public function __construct($nid, $group_type) {
379 $this->nid = $nid;
380 parent::__construct($group_type, db_result(db_query("SELECT release_id FROM {og_bundle} WHERE grouptype = '%s'", $group_type)));
381 }
382
383
384 /**
385 * Instantiate a bundle's blueprints as og_panels for a new group node. AKA, the meat and potatoes.
386 *
387 * @param int $nid
388 * The nid of the new group node being created.
389 * @param string $grouptype
390 * The internal system name of the group node's content type
391 */
392 public function instantiate() {
393 foreach ($this->blueprints as $bpid => $blueprint) {
394 $blueprint->build_display($this->nid);
395 }
396 }
397 }
398
399 class og_blueprint_instantiator extends og_blueprint_release {
400 public $nid;
401
402 public function build_display ($nid) {
403 $fields = og_blueprints_query_fields(array('nid', 'did', 'published', 'page_title', 'path', 'default_page', 'show_blocks', 'weight'));
404 $this->nid = $nid;
405 $this->export();
406 foreach ($fields as $field => $value) {
407 $f[] = $field;
408 $q[] = $value;
409 $v[] = $this->$field;
410 }
411 // FIXME naughty naughty. Should do this via drupal_process_form()
412 db_query("INSERT INTO {og_panels} (". implode(' , ', $f) .") VALUES (". implode(' , ', $q) .")", $v);
413 db_query("INSERT INTO {og_blueprint_control} (bid, vid, bpid, nid, did_link) VALUES (%d, %d, %d, %d, %d)", $this->bid, $this->vid, $this->bpid, $this->nid, 0); // TODO if we can get did-linking going in og_panels, then just change this to 1
414 }
415 }
416
417 class og_blueprint_new extends og_blueprint {
418
419 function __construct() {
420 $this->bpid = 'new';
421 $this->linked = TRUE;
422 }
423
424 function save() {
425 $this->bpid = db_next_id('{og_blueprint}_bpid');
426
427 $display = panels_save_display(panels_new_display());
428 $this->did = $display->did;
429
430 $result = db_query("SELECT bid, vid FROM {og_bundle}");
431 while ($row = db_fetch_array($result)) {
432 list($this->bid, $this->vid) = array_values($row);
433 $this->insert();
434 }
435 }
436 }
437
438 class og_bundle_type_creator extends og_bundle {
439
440 function __construct($group_type) {
441 $this->grouptype = 'master';
442 parent::__construct();
443 $this->load_all_blueprints();
444 $this->grouptype = $group_type;
445 $this->bid = db_next_id("{og_bundle}_bid");
446 $this->release_id = NULL;
447 db_query("INSERT INTO {og_bundle} (bid, grouptype) VALUES (%d, '%s')", $this->bid, $this->grouptype);
448 $this->increment_bid_version();
449 foreach ($this->blueprints as &$blueprint) {
450 $blueprint->split_from_master($this->bid, $this->vid);
451 }
452 }
453 }
454
455 class og_blueprint_type_creator extends og_blueprint {
456
457 function split_from_master($bid, $vid) {
458 list($this->bid, $this->vid) = array($bid, $vid);
459 $this->insert();
460 }
461 }
462
463 class og_bundle_type_remover extends og_bundle {
464
465 function __construct($group_type) {
466 $this->grouptype = $group_type;
467 parent::__construct();
468 if (!empty($this->release_id)) { // TODO make this check more elaborate. ensure that NO changes have been made
469 $bid = array((string) $this->bid => 'delete me!');
470 if ($to_delete = variable_get("og_blueprints_deleted_bid", FALSE)) {
471 $to_delete = array_merge($to_delete, $bid);
472 }
473 variable_set("og_blueprints_deleted_bid", $this->bid);
474 }
475 else $this->purge();
476 }
477
478 public function paint_over_it() {
479 db_query("DELETE FROM {og_bundle} WHERE grouptype = '%s'", $this->grouptype);
480 }
481
482 public function purge() {
483 $this->paint_over_it();
484 db_query("DELETE FROM {og_bundle_revision} WHERE bid = %d", $this->bid);
485 db_query("DELETE FROM {og_blueprint} WHERE bid = %d", $this->bid);
486 db_query("DELETE FROM {og_blueprint_control} WHERE bid = %d", $this->bid);
487 }
488 }
489
490 /**
491 * Object interface for pulling relevant help & informational text out of the .inc file.
492 * Keeps the main module sleek and clean!
493 *
494 */
495 class og_blueprints_help {
496 public $section = '';
497
498 /**
499 * @param string $section
500 * Indicates the primary helptext section area to be looking in
501 */
502 function __construct($section) {
503 include_once drupal_get_path('module', 'og_blueprints') . '/helptext.inc';
504 $this->section = $section;
505 }
506
507 /**
508 * Handles calls to various help text functions using
509 * __call() magic method
510 *
511 * @param string $function
512 * The actual function request entered into the object. Constitutes the last bit
513 * of the function that is eventually called.
514 * @param array $args
515 * A variable array of additional arguments passed in by the caller.
516 * @return string
517 * A helptext string, already run through t() and ready to be displayed
518 */
519 public function help($function, $location, $bundle = FALSE) {
520 if (function_exists($function = "ogbp_help_" . $this->section . "_" . $function)) {
521 $op = og_blueprints_mode() - ($bundle instanceof og_bundle_typed ? 0 : 1);
522 $args = func_get_args();
523 $t_args = array();
524 $help_text = $function($t_args, $location, $bundle, array_splice($args, 3));
525 return t($help_text[$op], $t_args[$op]);
526 }
527 else return FALSE;
528 }
529 }
530
531 /**
532 * Implementation of hook_help()
533 */
534 function og_blueprints_help($section) {
535 switch ($section) {
536 case 'admin/help#og_blueprints': // TODO write up quite a
537 $output = t('<em>NOTE: This section was slapped together quickly, and will be improved later. It works for now, and there needed to be a release of the module.</em>'); // FIXME move into help text
538 }
539 }
540
541 /**
542 * Implementation of hook_menu()
543 */
544 function og_blueprints_menu($may_cache) {
545 $group_types = og_blueprints_group_types();
546 $bundle_mode = og_blueprints_mode();
547 if ($may_cache) {
548 $items[] = array(
549 'path' => 'admin/og/og_blueprints',
550 'title' => t('OG Blueprints'),
551 'description' => t("Configure OG Blueprints' operating mode regarding bundles and group types."),
552 'callback' => 'drupal_get_form',
553 'callback arguments' => array('og_blueprints_admin'),
554 'access' => user_access('manage og blueprints'),
555 'weight' => 0,
556 );
557 $items[] = array(
558 'title' => t("Operating Mode"),
559 'description' => t("Configure OG Blueprints' operating mode regarding bundles and group types."),
560 'path' => 'admin/og/og_blueprints/settings',
561 'type' => MENU_DEFAULT_LOCAL_TASK,
562 'weight' => -10,
563 );
564 if ($group_types && $bundle_mode) { // at least one group type must have already been set, and bundles must be enabled
565 $multi = $bundle_mode == 1 ? FALSE : TRUE;
566 $items[] = array(
567 'path' => 'admin/og/og_blueprints/release',
568 'title' => t('Releases'),
569 'description' => t("Manage your bundle releases"),
570 'callback' => 'og_blueprints_release_overview',
571 'type' => MENU_LOCAL_TASK,
572 'access' => user_access('manage og blueprints'),
573 'weight' => -4,
574 );
575 $items[] = array(
576 'path' => 'admin/og/og_blueprints/release/overview',
577 'title' => t('Releases Overview'),
578 'type' => MENU_DEFAULT_LOCAL_TASK,
579 'access' => user_access('manage og blueprints'),
580 'weight' => -10,
581 );
582 $items[] = array(
583 'path' => 'admin/og/og_blueprints/release/create',
584 'callback' => 'drupal_get_form',
585 'callback arguments' => array('og_blueprints_create_release'),
586 'type' => MENU_CALLBACK,
587 );
588 $items[] = array(
589 'path' => 'admin/og/og_blueprints/release/push',
590 'title' => t('Push Blueprint Content'),
591 'description' => t('Selectively push content from your blueprints into existing groups.'),
592 'callback' => 'og_blueprints_content_push',
593 'type' => MENU_LOCAL_TASK,
594 'weight' => -8,
595 );
596 $items[] = array(
597 'path' => 'admin/og/og_blueprints/newbp',
598 'title' => t('New Blueprint'),
599 'description' => t('Add a new OG Blueprint to '. $multi ? 'your group bundle' : 'each of your group bundles'),
600 'callback' => 'drupal_get_form',
601 'callback arguments' => array('og_blueprints_blueprint_form', 0),
602 'type' => MENU_LOCAL_TASK,
603 'access' => user_access('manage og blueprints'),
604 'weight' => 0,
605 );
606 $items[] = array(
607 'path' => 'admin/og/og_blueprints/bundlecfg',
608 'title' => t('Bundles'),
609 'description' => t('Select which blueprints are to be used ' . $multi ? 'for each of your group type bundles.' : 'in your group bundle.'),
610 'callback' => 'og_blueprints_admin_bundlecfg',
611 'type' => MENU_LOCAL_TASK,
612 'access' => user_access('manage og blueprints'),
613 'weight' => -8,
614 );
615 $items[] = array(
616 'path' => 'admin/og/og_blueprints/blueprintsetup',
617 'title' => t('Blueprints'),
618 'description' => t('Configure the panel blueprints that make up your bundle.'),
619 'callback' => 'og_blueprints_admin_blueprintsetup',
620 'callback arguments' => array('master'),
621 'type' => MENU_LOCAL_TASK,
622 'access' => user_access('manage og blueprints'),
623 'weight' => -6,
624 );
625 if ($bundle_mode == 2) {
626 $items[] = array(
627 'path' => 'admin/og/og_blueprints/bundlecfg/master',
628 'title' => t('(Master Bundle)'),
629 'type' => MENU_DEFAULT_LOCAL_TASK,
630 'access' => user_access('manage og blueprints'),
631 );
632 $items[] = array(
633 'path' => 'admin/og/og_blueprints/blueprintsetup/master',
634 'title' => t('(Master Bundle)'),
635 'description' => t('Configure the panels that make up your bundle.'),
636 'type' => MENU_DEFAULT_LOCAL_TASK,
637 'access' => user_access('manage og blueprints'),
638 );
639 $i = 1;
640 foreach ($group_types as $type => $name) {
641 $items[] = array(
642 'path' => 'admin/og/og_blueprints/bundlecfg/'. $type,
643 'title' => t($name),
644 'callback' => 'og_blueprints_admin_bundlecfg',
645 'callback arguments' => array($type),
646 'type' => MENU_LOCAL_TASK,
647 'access' => user_access('manage og blueprints'),
648 'weight' => $i,
649 );
650 $items[] = array(
651 'path' => 'admin/og/og_blueprints/blueprintsetup/'. $type,
652 'title' => t($name),
653 'callback' => 'og_blueprints_admin_blueprintsetup',
654 'callback arguments' => array($type),
655 'type' => MENU_LOCAL_TASK,
656 'access' => user_access('manage og blueprints'),
657 'weight' => $i,
658 );
659 $i++;
660 }
661 }
662 $items[] = array(
663 'path' => 'admin/og/og_blueprints/blueprint/form',
664 'callback' => 'drupal_get_form',
665 'callback arguments' => array('og_blueprints_blueprint_form'),
666 'type' => MENU_CALLBACK,
667 'access' => user_access('manage og blueprints'),
668 );
669 $items[] = array(
670 'path' => 'admin/og/og_blueprints/blueprint/edit',
671 'callback' => 'og_blueprints_blueprint_edit',
672 'type' => MENU_CALLBACK,
673 'access' => user_access('manage og blueprints'),
674 );
675 $items[] = array(
676 'path' => 'admin/og/og_blueprints/blueprint/delete',
677 'callback' => 'drupal_get_form',
678 'callback arguments' => array('og_blueprints_blueprint_delete_confirm'),
679 'type' => MENU_CALLBACK,
680 'access' => user_access('manage og blueprints'),
681 );
682 $items[] = array(
683 'path' => 'admin/og/og_blueprints/blueprint/relink',
684 'callback' => 'drupal_get_form',
685 'callback arguments' => array('og_blueprints_blueprint_relink_confirm'),
686 'type' => MENU_CALLBACK,
687 'access' => user_access('manage og blueprints'),
688 );
689 }
690 }
691 return $items;
692 }
693
694 /**
695 * Implementation of hook_perm()
696 *
697 */
698 function og_blueprints_perm() {
699 return array('manage og blueprints');
700 }
701
702 /**
703 * Return the list of node types that have been classified by og as groups.
704 *
705 * Because of the omnipresent 'Master' bundle, it's sometimes helpful to add a 'Master'
706 * value to the beginning of this array. That's what the first parameter is for.
707 *
708 * @param boolean $master = FALSE
709 * indicates whether a 'master' group type should be added at the beginning of the array.
710 * @return array $group_types
711 * An associative array of the form [system_type_name] => [public-facing_type_name] for all group types.
712 * Also can return FALSE if no groups have been defined.
713 */
714 function og_blueprints_group_types($master = FALSE) {
715 if ($group_types = variable_get('og_node_types', FALSE)) {
716 asort($group_types);
717 foreach ($group_types as $type) {
718 $group_types[$type] = node_get_types('name', $type);
719 }
720 if ($master) {
721 $group_types = array_merge(array('master' => 'Master'), $group_types);
722 }
723 }
724 return $group_types;
725 }
726
727 /**
728 * Return an associative array of bpnames, keyed by their corresponding bpid.
729 *
730 * @param bool $refresh
731 * The list is cached using a static var, and will only be refreshed from the variables table if explicitly told to do so
732 * @return array $blueprints
733 * Returns an empty array if no blueprints have been defined
734 */
735 function og_blueprints_list($refresh = FALSE) {
736 static $blueprints;
737 if (empty($blueprints) || $refresh) {
738 if ($refresh || !$blueprints = variable_get('og_blueprints_list', FALSE)) {
739 $blueprints = array();
740 $result = db_query("SELECT DISTINCT bpid, bpname FROM {og_blueprint}");
741 while ($row = db_fetch_array($result)) {
742 $blueprints[$row['bpid']] = $row['bpname'];
743 }
744 variable_set('og_blueprints_list', $blueprints);
745 }
746 }
747 return $blueprints;
748 }
749
750 /**
751 * Render the form for bundle configuration.
752 */
753 function og_blueprints_admin_bundlecfg($type = 'master') {
754 if (count(og_blueprints_list()) == 0) {
755 drupal_set_message("You need to create at least one blueprint before this module can operate. Use this form to create your first one.");
756 drupal_goto('admin/og/og_blueprints/blueprint/form');
757 }
758 $group_types = og_blueprints_group_types(TRUE);
759 $ismaster = $type == 'master' ? TRUE : FALSE;
760 drupal_set_title(og_blueprints_mode == 2 ? "'". $group_types[$type] ."' Bundle" : "Bundle Setup");
761 $bundle = $ismaster ? new og_bundle_master() : new og_bundle_typed($type);
762 $output .= theme('og_blueprints_admin_dashboard', $bundle);
763 return $output . drupal_get_form('og_blueprints_bundlecfg_table', $type, $ismaster, $bundle);
764 }
765
766 /**
767 * FAPI definition for the configure overall bundle settings form.
768 *
769 * @ingroup forms
770 * @param string $type
771 * The group type for which the bundle settings are being edited.
772 *
773 */
774 function og_blueprints_bundlecfg_table($type, $ismaster, $bundle) {
775 $form['typevar'] = array('#type' => 'hidden', '#value' => $type);
776 $form['#tree'] = TRUE;
777 // TODO add a default tab as a means of allowing people to add/remove panel blueprints?
778 foreach ($bundle->blueprints as $bpid => $blueprint) {
779 $form[$bpid]['page_title'] = array('#value' => $blueprint->page_title .' (<em>'. $blueprint->bpname .'</em>)'); // TODO switch to 'description' font?
780 $form[$bpid]['weight'] = array('#type' => 'weight', '#default_value' => $blueprint->weight ? $blueprint->weight : 0);
781 $form[$bpid]['edit'] = array('#value' => l(t('Edit'), "admin/og/og_blueprints/blueprint/form/$bpid/$type", array(), drupal_get_destination()));
782 $form[$bpid]['enabled'] = array('#type' => 'checkbox', '#default_value' => $blueprint->enabled);
783 $form[$bpid]['show_blocks'] = array('#type' => 'checkbox', '#default_value' => $blueprint->show_blocks);
784 $form[$bpid]['published'] = array('#type' => 'checkbox', '#default_value' => $blueprint->published);
785 if ($ismaster) {
786 $form[$bpid]['delete'] = array('#value' => l(t('Delete'), "admin/og/og_blueprints/blueprint/delete/$bpid", array(), drupal_get_destination()));
787 if ($all_disabled = og_blueprints_mode() == 2) {
788 $form[$bpid]['weight']['#disabled'] = TRUE;
789 $form[$bpid]['enabled']['#disabled'] = TRUE;
790 $form[$bpid]['published']['#disabled'] = TRUE;
791 $form[$bpid]['show_blocks']['#disabled'] = TRUE;
792 }
793 }
794
795 if ($blueprint->default_page == TRUE) {
796 $default_page = $bpid;
797 }
798 $options[$bpid] = '';
799 }
800
801 // $options = $optionshome;
802 // $optionshome[0] = t('<em>(Do not replace the group homepage with a blueprint)</em>');
803 $form['default_page'] = array(
804 '#type' => 'radios',
805 '#options' => $options,
806 '#default_value' => $default_page ? $default_page : 0,
807 );
808 $form['submit'] = array(
809 '#type' => 'submit',
810 '#value' => t('Save Bundle'),
811 );
812 if ($all_disabled) {
813 $form['default_page']['#disabled'] = TRUE;
814 $form['submit']['#disabled'] = TRUE;
815 }
816 $form['bundle_data'] = array('#type' => 'hidden', '#value' => serialize($bundle));
817 return $form;
818 }
819
820 /**
821 * Theme the bundle settings form into a table.
822 *
823 * @ingroup themeable
824 */
825 function theme_og_blueprints_bundlecfg_table($form) {
826 $ismaster = $form['typevar']['#value'] == 'master' ? TRUE : FALSE;
827 $bundle = unserialize($form['bundle_data']['#value']);
828 foreach (array_intersect(element_children($form), array_keys($bundle->blueprints)) as $bpid) {
829 $row = array(
830 drupal_render($form[$bpid]['page_title']),
831 drupal_render($form['default_page'][$bpid]),
832 drupal_render($form[$bpid]['enabled']),
833 drupal_render($form[$bpid]['published']),
834 drupal_render($form[$bpid]['show_blocks']),
835 drupal_render($form[$bpid]['weight']),
836 drupal_render($form[$bpid]['edit']),
837 );
838 if ($ismaster) {
839 $row[] = drupal_render($form[$bpid]['delete']);
840 }
841 $rows[] = $row;
842 unset($row);
843 }
844
845
846 $help = new og_blueprints_help('mainform');
847 $header = array(t('Blueprint'), t('Home Page'), t('Enabled'), t('Published'), t('Show Blocks'), t('Weight'), array('data' => t('Operations'), 'align' => 'center', 'colspan' => $ismaster ? 2 : 1));
848 $output .= theme('table', $header, $rows, array('class' => 'og-blueprint-admin-table'), $help->help('caption', arg(3), $bundle));
849 $output .= drupal_render($form);
850 return $output;
851 }
852
853 /**
854 * Theme the instructional/informational upper portion of several og_blueprints admin pages.
855 *
856 * @ingroup themeable
857 *
858 * @param $bundle instanceof og_bundle subclass
859 * A fully-loaded
860 * @return string
861 */
862 function theme_og_blueprints_admin_dashboard($bundle) {
863 drupal_add_css(drupal_get_path('module', 'og_blueprints') . '/og_blueprints_admin.css');
864 $help = new og_blueprints_help('dashboard');
865 $output = '<div class="clear-block"><div id="og-blueprints-admin-helptext">' . $help->help('inline', arg(3), $bundle) . '</div>';
866 return $output . '<div id="og-blueprint-dashboard">' . theme('table', array(array('data' => t('Bundle Releases'), 'colspan' => 4)), array($bundle->dashboard()), array('class' => 'og-blueprint-admin-table'), $help->help('caption', arg(3), $bundle)) . "</div></div>";
867 }
868
869 /**
870 * Specialized sort function. Sorts a given bundle's blueprints by the following criteria:
871 * 1. Default Page status
872 * 2. Whether the blueprint is enabled or disabled in the bundle
873 * 3. Finally, by weight
874 */
875 function og_blueprints_sort_bundle($a, $b) {
876 if ($b->default_page) {
877 return TRUE;
878 }
879 elseif ($a->default_page) {
880 return FALSE;
881 }
882
883 $active = $b->enabled - $a->enabled;
884 if ($active) {
885 return $active;
886 }
887
888 // if ($a['active']) return ($a['weight'] - $b['weight']);
889 return ($a->weight - $b->weight);
890 }
891
892 /**
893 * Submit form changes to class methods for saving to the db.
894 *
895 */
896 function og_blueprints_bundlecfg_table_submit($form_id, $form_values) {
897 $bundle = unserialize($form_values['bundle_data']);
898 foreach ($bundle->blueprints as $bpid => $blueprint) {
899 $blueprint->default_page = $form_values['default_page'] == $bpid ? 1 : 0;
900 list ($blueprint->weight, $blueprint->enabled, $blueprint->show_blocks, $blueprint->published) = array_values($form_values[$bpid]);
901 $blueprint->save();
902 }
903 drupal_set_message(t("Successfully updated the '!type' bundle.", array('!type' => $form_values['typevar'])));
904 }
905
906 /**
907 * Render the page containing blueprint-specific configuration links.
908 *
909
910 * @param string $type
911 * The group type for which the bundle settings are being edited.
912 * @return string $output
913 * String of HTML that has (already) been rendered by the theme('page') function... TODO split this into a separate function so it can be themed
914 */
915 function og_blueprints_admin_blueprintsetup($type = 'master') {
916 if (count(og_blueprints_list()) == 0) {
917 drupal_set_message(t("You need to HAVE a blueprint before you can edit one! So, create one here."));
918 drupal_goto('admin/og/og_blueprints/blueprint/form');
919 }
920 $group_types = og_blueprints_group_types(TRUE);
921 $ismaster = $type == 'master' ? TRUE : FALSE;
922 $bundle = $ismaster ? new og_bundle_master() : new og_bundle_typed($type);
923 $multi = ($bundle_mode = og_blueprints_mode()) == 2 ? TRUE : FALSE;
924 drupal_set_title($multi ? "'". $group_types[$type] ."' Blueprints" : 'Master Blueprints');
925
926 $rows = array();
927 $last_status = 'none';
928 $colspan = $bundle_mode == 1 ? 4 : $bundle instanceof og_bundle_typed ? 6 : 5;
929 foreach ($bundle->blueprints as $bpid => $blueprint) {
930 if ($blueprint->default_page) {
931 $rows[] = array(array('data' => t('Home Page Blueprint'), 'class' => 'region', 'colspan' => $colspan));
932 $last_status = 'home';
933 }
934 elseif ($blueprint->enabled) {
935 if (in_array($last_status, array('home', 'none'))) {
936 $rows[] = array(array('data' => t('Enabled Blueprints'), 'class' => 'region', 'colspan' => $colspan));
937 }
938 $last_status = 'enabled';
939 }
940 elseif (!$blueprint->enabled) {
941 if ($last_status != 'disabled') {
942 $rows[] = array(array('data' => t('Disabled Blueprints'), 'class' => 'region', 'colspan' => $colspan));
943 }
944 $last_status = 'disabled';
945 }
946 $bpid = $blueprint->bpid;
947 $row['page_title'] = $blueprint->page_title;
948 // $row['default_page'] = $blueprint->default_page ? 'Yes' : 'No';
949 if (!$ismaster) {
950 $row['linked'] = $blueprint->linked ? 'Yes' : 'No';
951 $row['relink'] = $blueprint->linked ? 'Already Linked' : l(t('Restore Link'), "admin/og/og_blueprints/blueprint/relink/$blueprint->did", array(), drupal_get_destination());
952 // $row['import'] = 'Coming Soon'; TODO do it or don't, but don't let it sit
953 }
954 elseif ($multi) {
955 $row['number_linked'] = $blueprint->number_linked == 0 ? $none_linked = t('Delinked in all bundles') : $blueprint->number_linked;
956 }
957 if ($blueprint->enabled) {
958 $edit_url = $bpid . ($ismaster ? '' : "/". $type);
959 $row['content'] = l(t('Panel Content'), "admin/og/og_blueprints/blueprint/edit/content/$edit_url", array(), drupal_get_destination());
960 $row['layout'] = l(t('Panel Layout'), "admin/og/og_blueprints/blueprint/edit/layout/$edit_url", array(), drupal_get_destination());
961 $row['layset'] = l(t('Panel Layout Settings'), "admin/og/og_blueprints/blueprint/edit/layset/$edit_url", array(), drupal_get_destination());
962 }
963 else {
964 $row['filler'] = array('data'=> t('<em>This blueprint is currently disabled in this bundle.</em>'), 'align' => 'center', 'colspan' => 3);
965 }
966 $rows[] = $row;
967 unset ($row);
968 }
969
970 $header = array(t('Panel Name'));
971 if (!$ismaster) {
972 $header[] = t('Tied to Master?');
973 $header[] = t('Relink to Master');
974 // $header[] = t('Import');
975 }
976 elseif ($multi) {
977 $header[] = t('Linked Blueprints');
978 }
979 $header[] = array('data' => t('Panels Editing Operations'), 'align' => 'center', 'colspan' => 3);
980
981 $output = theme('og_blueprints_admin_dashboard', $bundle);
982 $help = new og_blueprints_help('mainform');
983 return $output . theme('table', $header, $rows, array('class' => 'og-blueprint-admin-table'), $help->help('caption', arg(3), $bundle));
984 }
985
986 /**
987 * Form to add/edit a blueprint. if a bpid & type are passed in, it edits that blueprint. if there's not, it creates a new blueprint.
988 */
989 function og_blueprints_blueprint_form($bpid, $type = 'master') {
990 $blueprint = $bpid === 0 ? new og_blueprint_new() : new og_blueprint(array('bpid' => $bpid, 'grouptype' => $type));
991 $form['bpid'] = array('#type' => 'value', '#value' => $bpid);
992 $form['#redirect'] = !$bpid ? FALSE : TRUE;
993 $form['blueprint_data'] = array('#type' => 'value', '#value' => serialize($blueprint));
994 drupal_set_title($bpid ? "Configure '$blueprint->page_title' Blueprint" : "New Blueprint");
995
996 $form['#tree'] = TRUE;
997 if (!$bpid) {
998 $form['bpvals']['bpname'] = array(
999 '#title' => t('Blueprint System Name'),
1000 '#type' => 'textfield',
1001 '#required' => TRUE,
1002 '#default_value' => $blueprint->bpname,
1003 '#description' => t('The internal system name for this blueprint. Only sitewide OG Blueprints administrators will ever see this value. Alphanumerics, dashes, and underscores only. Note that you will NOT be able to change this later.'),
1004 '#size' => 32,
1005 );
1006 }
1007 $form['bpvals']['page_title'] = array(
1008 '#title' => t('Blueprint Title'),
1009 '#type' => 'textfield',
1010 '#required' => TRUE,
1011 '#default_value' => $blueprint->page_title,
1012 '#description' => t('The default title for og panels (and corresponding navigation tabs) that are instantiated from this blueprint.'),
1013 '#size' => 32,
1014 );
1015 $form['bpvals']['path'] = array(
1016 '#title' => t('Default Path'),
1017 '#type' => 'textfield',
1018 '#default_value' => $blueprint->default_page ? '' : $blueprint->path,
1019 '#required' => $blueprint->default_page ? FALSE : TRUE,
1020 '#description' => $blueprint->default_page ? t('This blueprint is currently set to act as the group home page and therefore cannot be assigned a default path.') : t('The default path where og panels instantiated by this blueprint will reside.'),
1021 '#disabled' => $blueprint->default_page ? TRUE : FALSE,
1022 '#size' => 32,
1023 );
1024 $form['bpvals']['show_blocks'] = array(
1025 '#title' => t('Show blocks'),
1026 '#type' => 'checkbox',
1027 '#default_value' => $blueprint->show_blocks,
1028 '#description' => t('If unchecked, OG Panels instantiated by this blueprint will hide group blocks by default.'),
1029 );
1030 $form['bpvals']['published'] = array(
1031 '#type' => 'checkbox',
1032 '#title' => t('Published'),
1033 '#default_value' => $blueprint->published,
1034 '#description' => t('If unchecked, OG Panels instantiated by this blueprint will initially be unpublished and viewable only by site or group administrators. Keeping this unchecked is wise if the page contains information that is not automatically present on group creation, and having this page accessible but empty would reflect poorly on the group.'),
1035 );
1036 $form['propagate'] = array('#type' => 'hidden', '#value' => 1); // we propagate under most circumstances, so set & hide it by default
1037 $form['bpvals'