Merge branch '4.3' of github.com:civicrm/civicrm-core
[civicrm-core.git] / CRM / Core / Controller.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26*/
27
28/**
29 * This class acts as our base controller class and adds additional
30 * functionality and smarts to the base QFC. Specifically we create
31 * our own action classes and handle the transitions ourselves by
32 * simulating a state machine. We also create direct jump links to any
33 * page that can be used universally.
34 *
35 * This concept has been discussed on the PEAR list and the QFC FAQ
36 * goes into a few details. Please check
37 * http://pear.php.net/manual/en/package.html.html-quickform-controller.faq.php
38 * for other useful tips and suggestions
39 *
40 * @package CRM
41 * @copyright CiviCRM LLC (c) 2004-2013
42 * $Id$
43 *
44 */
45
46require_once 'HTML/QuickForm/Controller.php';
47require_once 'HTML/QuickForm/Action/Direct.php';
48
49class CRM_Core_Controller extends HTML_QuickForm_Controller {
50
51 /**
52 * the title associated with this controller
53 *
54 * @var string
55 */
56 protected $_title;
57
58 /**
59 * The key associated with this controller
60 *
61 * @var string
62 */
63 public $_key;
64
65 /**
66 * the name of the session scope where values are stored
67 *
68 * @var object
69 */
70 protected $_scope;
71
72 /**
73 * the state machine associated with this controller
74 *
75 * @var object
76 */
77 protected $_stateMachine;
78
79 /**
80 * Is this object being embedded in another object. If
81 * so the display routine needs to not do any work. (The
82 * parent object takes care of the display)
83 *
84 * @var boolean
85 */
86 protected $_embedded = FALSE;
87
88 /**
89 * After entire form execution complete,
90 * do we want to skip control redirection.
91 * Default - It get redirect to user context.
92 *
93 * Useful when we run form in non civicrm context
94 * and we need to transfer control back.(eg. drupal)
95 *
96 * @var boolean
97 */
98 protected $_skipRedirection = FALSE;
99
100 /**
101 * Are we in print mode? if so we need to modify the display
102 * functionality to do a minimal display :)
103 *
104 * @var boolean
105 */
106 public $_print = 0;
107
108 /**
109 * Should we generate a qfKey, true by default
110 *
111 * @var boolean
112 */
113 public $_generateQFKey = TRUE;
114
115 /**
116 * QF response type
117 */
118 public $_QFResponseType = 'html';
119
120 /**
121 * cache the smarty template for efficiency reasons
122 *
123 * @var CRM_Core_Smarty
124 */
125 static protected $_template;
126
127 /**
128 * cache the session for efficiency reasons
129 *
130 * @var CRM_Core_Session
131 */
132 static protected $_session;
133
134 /**
135 * The parent of this form if embedded
136 *
137 * @var object
138 */
139 protected $_parent = NULL;
140
141 /**
142 * The destination if set will override the destination the code wants to send it to
143 *
144 * @var string;
145 */
146 public $_destination = NULL;
147
148 /**
149 * All CRM single or multi page pages should inherit from this class.
150 *
151 * @param string title descriptive title of the controller
152 * @param boolean whether controller is modal
153 * @param string scope name of session if we want unique scope, used only by Controller_Simple
154 * @param boolean addSequence should we add a unique sequence number to the end of the key
155 * @param boolean ignoreKey should we not set a qfKey for this controller (for standalone forms)
156 *
157 * @access public
158 *
159 * @return void
160 *
161 */
162 function __construct(
163 $title = NULL,
164 $modal = TRUE,
165 $mode = NULL,
166 $scope = NULL,
167 $addSequence = FALSE,
168 $ignoreKey = FALSE
169 ) {
170 // this has to true for multiple tab session fix
171 $addSequence = TRUE;
172
173 // let the constructor initialize this, should happen only once
174 if (!isset(self::$_template)) {
175 self::$_template = CRM_Core_Smarty::singleton();
176 self::$_session = CRM_Core_Session::singleton();
177 }
178
179 // add a unique validable key to the name
180 $name = CRM_Utils_System::getClassName($this);
181 if ($name == 'CRM_Core_Controller_Simple' && !empty($scope)) {
182 // use form name if we have, since its a lot better and
183 // definitely different for different forms
184 $name = $scope;
185 }
186 $name = $name . '_' . $this->key($name, $addSequence, $ignoreKey);
187 $this->_title = $title;
188 if ($scope) {
189 $this->_scope = $scope;
190 }
191 else {
192 $this->_scope = CRM_Utils_System::getClassName($this);
193 }
194 $this->_scope = $this->_scope . '_' . $this->_key;
195
196 // only use the civicrm cache if we have a valid key
197 // else we clash with other users CRM-7059
198 if (!empty($this->_key)) {
199 CRM_Core_Session::registerAndRetrieveSessionObjects(array(
200 "_{$name}_container",
201 array('CiviCRM', $this->_scope),
202 ));
203 }
204
205 $this->HTML_QuickForm_Controller($name, $modal);
206
207 $snippet = CRM_Utils_Array::value('snippet', $_REQUEST);
208 if ($snippet) {
209 if ($snippet == 3) {
210 $this->_print = CRM_Core_Smarty::PRINT_PDF;
211 }
212 elseif ($snippet == 4) {
213 // this is used to embed fragments of a form
214 $this->_print = CRM_Core_Smarty::PRINT_NOFORM;
215 self::$_template->assign('suppressForm', TRUE);
216 $this->_generateQFKey = FALSE;
217 }
218 elseif ($snippet == 5) {
219 // this is used for popups and inlined ajax forms
220 // also used for the various tabs via TabHeader
221 $this->_print = CRM_Core_Smarty::PRINT_NOFORM;
222 }
223 elseif ($snippet == 6) {
224 $this->_print = CRM_Core_Smarty::PRINT_NOFORM;
225 $this->_QFResponseType = 'json';
226 }
227 else {
228 $this->_print = CRM_Core_Smarty::PRINT_SNIPPET;
229 }
230 }
231
232 // if the request has a reset value, initialize the controller session
233 if (CRM_Utils_Array::value('reset', $_GET)) {
234 $this->reset();
235 }
236
237 // set the key in the session
238 // do this at the end so we have initialized the object
239 // and created the scope etc
240 $this->set('qfKey', $this->_key);
241
242
243 // also retrieve and store destination in session
244 $this->_destination = CRM_Utils_Request::retrieve('civicrmDestination', 'String', $this,
245 FALSE, NULL, $_REQUEST
246 );
247 }
248
249 function fini() {
250 CRM_Core_BAO_Cache::storeSessionToCache(array(
251 "_{$this->_name}_container",
252 array('CiviCRM', $this->_scope),
253 ),
254 TRUE
255 );
256 }
257
258 function key($name, $addSequence = FALSE, $ignoreKey = FALSE) {
259 $config = CRM_Core_Config::singleton();
260
261 if (
262 $ignoreKey ||
263 (isset($config->keyDisable) && $config->keyDisable)
264 ) {
265 return NULL;
266 }
267
268 $key = CRM_Utils_Array::value('qfKey', $_REQUEST, NULL);
269 if (!$key && $_SERVER['REQUEST_METHOD'] === 'GET') {
270 $key = CRM_Core_Key::get($name, $addSequence);
271 }
272 else {
273 $key = CRM_Core_Key::validate($key, $name, $addSequence);
274 }
275
276 if (!$key) {
277 $msg = ts('We can\'t load the requested web page. This page requires cookies to be enabled in your browser settings. Please check this setting and enable cookies (if they are not enabled). Then try again. If this error persists, contact the site adminstrator for assistance.') . '<br /><br />' . ts('Site Administrators: This error may indicate that users are accessing this page using a domain or URL other than the configured Base URL. EXAMPLE: Base URL is http://example.org, but some users are accessing the page via http://www.example.org or a domain alias like http://myotherexample.org.') . '<br /><br />' . ts('Error type: Could not find a valid session key.');
278 CRM_Core_Error::fatal($msg);
279 }
280
281 $this->_key = $key;
282
283 return $key;
284 }
285
286 /**
287 * Process the request, overrides the default QFC run method
288 * This routine actually checks if the QFC is modal and if it
289 * is the first invalid page, if so it call the requested action
290 * if not, it calls the display action on the first invalid page
291 * avoids the issue of users hitting the back button and getting
292 * a broken page
293 *
294 * This run is basically a composition of the original run and the
295 * jump action
296 *
297 */
298 function run() {
299 // the names of the action and page should be saved
300 // note that this is split into two, because some versions of
301 // php 5.x core dump on the triple assignment :)
302 $this->_actionName = $this->getActionName();
303 list($pageName, $action) = $this->_actionName;
304
305 if ($this->isModal()) {
306 if (!$this->isValid($pageName)) {
307 $pageName = $this->findInvalid();
308 $action = 'display';
309 }
310 }
311
312 // note that based on action, control might not come back!!
313 // e.g. if action is a valid JUMP, u basically do a redirect
314 // to the appropriate place
315 $this->wizardHeader($pageName);
316 return $this->_pages[$pageName]->handle($action);
317 }
318
319 function validate() {
320 $this->_actionName = $this->getActionName();
321 list($pageName, $action) = $this->_actionName;
322
323 $page = &$this->_pages[$pageName];
324
325 $data = &$this->container();
326 $this->applyDefaults($pageName);
327 $page->isFormBuilt() or $page->buildForm();
328 // We use defaults and constants as if they were submitted
329 $data['values'][$pageName] = $page->exportValues();
330 $page->loadValues($data['values'][$pageName]);
331 // Is the page now valid?
332 if (TRUE === ($data['valid'][$pageName] = $page->validate())) {
333 return TRUE;
334 }
335 return $page->_errors;
336 }
337
338 /**
339 * Helper function to add all the needed default actions. Note that the framework
340 * redefines all of the default QFC actions
341 *
342 * @param string directory to store all the uploaded files
343 * @param array names for the various upload buttons (note u can have more than 1 upload)
344 *
345 * @access private
346 *
347 * @return void
348 *
349 */
350 function addActions($uploadDirectory = NULL, $uploadNames = NULL) {
351 $names = array(
352 'display' => 'CRM_Core_QuickForm_Action_Display',
353 'next' => 'CRM_Core_QuickForm_Action_Next',
354 'back' => 'CRM_Core_QuickForm_Action_Back',
355 'process' => 'CRM_Core_QuickForm_Action_Process',
356 'cancel' => 'CRM_Core_QuickForm_Action_Cancel',
357 'refresh' => 'CRM_Core_QuickForm_Action_Refresh',
358 'reload' => 'CRM_Core_QuickForm_Action_Reload',
359 'done' => 'CRM_Core_QuickForm_Action_Done',
360 'jump' => 'CRM_Core_QuickForm_Action_Jump',
361 'submit' => 'CRM_Core_QuickForm_Action_Submit',
362 );
363
364 foreach ($names as $name => $classPath) {
365 $action = new $classPath($this->_stateMachine);
366 $this->addAction($name, $action);
367 }
368
369 $this->addUploadAction($uploadDirectory, $uploadNames);
370 }
371
372 /**
373 * getter method for stateMachine
374 *
375 * @return object
376 * @access public
377 */
378 function getStateMachine() {
379 return $this->_stateMachine;
380 }
381
382 /**
383 * setter method for stateMachine
384 *
385 * @param object a stateMachineObject
386 *
387 * @return void
388 * @access public
389 */
390 function setStateMachine($stateMachine) {
391 $this->_stateMachine = $stateMachine;
392 }
393
394 /**
395 * add pages to the controller. Note that the controller does not really care
396 * the order in which the pages are added
397 *
398 * @param object $stateMachine the state machine object
399 * @param int $action the mode in which the state machine is operating
400 * typicaly this will be add/view/edit
401 *
402 * @return void
403 * @access public
404 *
405 */
406 function addPages(&$stateMachine, $action = CRM_Core_Action::NONE) {
407 $pages = $stateMachine->getPages();
408 foreach ($pages as $name => $value) {
409 $className = CRM_Utils_Array::value('className', $value, $name);
410 $title = CRM_Utils_Array::value('title', $value);
411 $options = CRM_Utils_Array::value('options', $value);
412 $stateName = CRM_Utils_String::getClassName($className);
413 if (CRM_Utils_Array::value('className', $value)) {
414 $formName = $name;
415 }
416 else {
417 $formName = CRM_Utils_String::getClassName($name);
418 }
419
420 $ext = CRM_Extension_System::singleton()->getMapper();
421 if ($ext->isExtensionClass($className)) {
422 require_once ($ext->classToPath($className));
423 }
424 else {
425 require_once (str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php');
426 }
427 $$stateName = new $className($stateMachine->find($className), $action, 'post', $formName);
428 if ($title) {
429 $$stateName->setTitle($title);
430 }
431 if ($options) {
432 $$stateName->setOptions($options);
433 }
434 $this->addPage($$stateName);
435 $this->addAction($stateName, new HTML_QuickForm_Action_Direct());
436
437 //CRM-6342 -we need kill the reference here,
438 //as we have deprecated reference object creation.
439 unset($$stateName);
440 }
441 }
442
443 /**
444 * QFC does not provide native support to have different 'submit' buttons.
445 * We introduce this notion to QFC by using button specific data. Thus if
446 * we have two submit buttons, we could have one displayed as a button and
447 * the other as an image, both are of type 'submit'.
448 *
449 * @return string the name of the button that has been pressed by the user
450 * @access public
451 */
452 function getButtonName() {
453 $data = &$this->container();
454 return CRM_Utils_Array::value('_qf_button_name', $data);
455 }
456
457 /**
458 * function to destroy all the session state of the controller.
459 *
460 * @access public
461 *
462 * @return void
463 */
464 function reset() {
465 $this->container(TRUE);
466 self::$_session->resetScope($this->_scope);
467 }
468
469 /**
470 * virtual function to do any processing of data.
471 * Sometimes it is useful for the controller to actually process data.
472 * This is typically used when we need the controller to figure out
473 * what pages are potentially involved in this wizard. (this is dynamic
474 * and can change based on the arguments
475 *
476 * @return void
477 * @access public
478 */
479 function process() {}
480
481 /**
482 * Store the variable with the value in the form scope
483 *
484 * @param string|array $name name of the variable or an assoc array of name/value pairs
485 * @param mixed $value value of the variable if string
486 *
487 * @access public
488 *
489 * @return void
490 *
491 */
492 function set($name, $value = NULL) {
493 self::$_session->set($name, $value, $this->_scope);
494 }
495
496 /**
497 * Get the variable from the form scope
498 *
499 * @param string name : name of the variable
500 *
501 * @access public
502
503 *
504 * @return mixed
505 *
506 */
507 function get($name) {
508 return self::$_session->get($name, $this->_scope);
509 }
510
511 /**
512 * Create the header for the wizard from the list of pages
513 * Store the created header in smarty
514 *
515 * @param string $currentPageName name of the page being displayed
516 *
517 * @return array
518 * @access public
519 */
520 function wizardHeader($currentPageName) {
521 $wizard = array();
522 $wizard['steps'] = array();
523 $count = 0;
524 foreach ($this->_pages as $name => $page) {
525 $count++;
526 $wizard['steps'][] = array(
527 'name' => $name,
528 'title' => $page->getTitle(),
529 //'link' => $page->getLink ( ),
530 'link' => NULL,
531 'step' => TRUE,
532 'valid' => TRUE,
533 'stepNumber' => $count,
534 'collapsed' => FALSE,
535 );
536
537 if ($name == $currentPageName) {
538 $wizard['currentStepNumber'] = $count;
539 $wizard['currentStepName'] = $name;
540 $wizard['currentStepTitle'] = $page->getTitle();
541 }
542 }
543
544 $wizard['stepCount'] = $count;
545
546 $this->addWizardStyle($wizard);
547
548 $this->assign('wizard', $wizard);
549 return $wizard;
550 }
551
552 function addWizardStyle(&$wizard) {
553 $wizard['style'] = array(
554 'barClass' => '',
555 'stepPrefixCurrent' => '&raquo;',
556 'stepPrefixPast' => '&radic;',
557 'stepPrefixFuture' => ' ',
558 'subStepPrefixCurrent' => '&nbsp;&nbsp;',
559 'subStepPrefixPast' => '&nbsp;&nbsp;',
560 'subStepPrefixFuture' => '&nbsp;&nbsp;',
561 'showTitle' => 1,
562 );
563 }
564
565 /**
566 * assign value to name in template
567 *
568 * @param array|string $name name of variable
569 * @param mixed $value value of varaible
570 *
571 * @return void
572 * @access public
573 */
574 function assign($var, $value = NULL) {
575 self::$_template->assign($var, $value);
576 }
577
578 function assign_by_ref($var, &$value) {
579 self::$_template->assign_by_ref($var, $value);
580 }
581
582 /**
583 * setter for embedded
584 *
585 * @param boolean $embedded
586 *
587 * @return void
588 * @access public
589 */
590 function setEmbedded($embedded) {
591 $this->_embedded = $embedded;
592 }
593
594 /**
595 * getter for embedded
596 *
597 * @return boolean return the embedded value
598 * @access public
599 */
600 function getEmbedded() {
601 return $this->_embedded;
602 }
603
604 /**
605 * setter for skipRedirection
606 *
607 * @param boolean $skipRedirection
608 *
609 * @return void
610 * @access public
611 */
612 function setSkipRedirection($skipRedirection) {
613 $this->_skipRedirection = $skipRedirection;
614 }
615
616 /**
617 * getter for skipRedirection
618 *
619 * @return boolean return the skipRedirection value
620 * @access public
621 */
622 function getSkipRedirection() {
623 return $this->_skipRedirection;
624 }
625
626 function setWord($fileName = NULL) {
627 //Mark as a CSV file.
628 header('Content-Type: application/vnd.ms-word');
629
630 //Force a download and name the file using the current timestamp.
631 if (!$fileName) {
632 $fileName = 'Contacts_' . $_SERVER['REQUEST_TIME'] . '.doc';
633 }
634 header("Content-Disposition: attachment; filename=Contacts_$fileName");
635 }
636
637 function setExcel($fileName = NULL) {
638 //Mark as an excel file.
639 header('Content-Type: application/vnd.ms-excel');
640
641 //Force a download and name the file using the current timestamp.
642 if (!$fileName) {
643 $fileName = 'Contacts_' . $_SERVER['REQUEST_TIME'] . '.xls';
644 }
645
646 header("Content-Disposition: attachment; filename=Contacts_$fileName");
647 }
648
649 /**
650 * setter for print
651 *
652 * @param boolean $print
653 *
654 * @return void
655 * @access public
656 */
657 function setPrint($print) {
658 if ($print == "xls") {
659 $this->setExcel();
660 }
661 elseif ($print == "doc") {
662 $this->setWord();
663 }
664 $this->_print = $print;
665 }
666
667 /**
668 * getter for print
669 *
670 * @return boolean return the print value
671 * @access public
672 */
673 function getPrint() {
674 return $this->_print;
675 }
676
677 function getTemplateFile() {
678 if ($this->_print) {
679 if ($this->_print == CRM_Core_Smarty::PRINT_PAGE) {
680 return 'CRM/common/print.tpl';
681 }
682 elseif ($this->_print == 'xls' || $this->_print == 'doc') {
683 return 'CRM/Contact/Form/Task/Excel.tpl';
684 }
685 else {
686 return 'CRM/common/snippet.tpl';
687 }
688 }
689 else {
690 $config = CRM_Core_Config::singleton();
691 return 'CRM/common/' . strtolower($config->userFramework) . '.tpl';
692 }
693 }
694
695 public function addUploadAction($uploadDir, $uploadNames) {
696 if (empty($uploadDir)) {
697 $config = CRM_Core_Config::singleton();
698 $uploadDir = $config->uploadDir;
699 }
700
701 if (empty($uploadNames)) {
702 $uploadNames = $this->get('uploadNames');
703 if (!empty($uploadNames)) {
704 $uploadNames = array_merge($uploadNames,
705 CRM_Core_BAO_File::uploadNames()
706 );
707 }
708 else {
709 $uploadNames = CRM_Core_BAO_File::uploadNames();
710 }
711 }
712
713 $action = new CRM_Core_QuickForm_Action_Upload($this->_stateMachine,
714 $uploadDir,
715 $uploadNames
716 );
717 $this->addAction('upload', $action);
718 }
719
720 public function setParent($parent) {
721 $this->_parent = $parent;
722 }
723
724 public function getParent() {
725 return $this->_parent;
726 }
727
728 public function getDestination() {
729 return $this->_destination;
730 }
731
732 public function setDestination($url = NULL, $setToReferer = FALSE) {
733 if (empty($url)) {
734 if ($setToReferer) {
735 $url = $_SERVER['HTTP_REFERER'];
736 }
737 else {
738 $config = CRM_Core_Config::singleton();
739 $url = $config->userFrameworkBaseURL;
740 }
741 }
742
743 $this->_destination = $url;
744 $this->set('civicrmDestination', $this->_destination);
745 }
746
747 public function cancelAction() {
748 $actionName = $this->getActionName();
749 list($pageName, $action) = $actionName;
750 return $this->_pages[$pageName]->cancelAction();
751 }
752}
753