/src/model/Model_PDF.php

  1. <?php 
  2.  
  3. namespace GFPDF\Model; 
  4.  
  5. use GFPDF\Helper\Helper_Abstract_Model; 
  6. use GFPDF\Helper\Helper_PDF; 
  7.  
  8. use GFPDF\Helper\Helper_Abstract_Fields; 
  9. use GFPDF\Helper\Fields\Field_Product; 
  10. use GFPDF\Helper\Fields\Field_Default; 
  11. use GFPDF\Helper\Fields\Field_Products; 
  12.  
  13. use GFPDF\Helper\Helper_Abstract_Form; 
  14. use GFPDF\Helper\Helper_Abstract_Options; 
  15. use GFPDF\Helper\Helper_Data; 
  16. use GFPDF\Helper\Helper_Misc; 
  17. use GFPDF\Helper\Helper_Notices; 
  18. use GFPDF\Helper\Helper_Templates; 
  19.  
  20. use Psr\Log\LoggerInterface; 
  21.  
  22. use GFFormsModel; 
  23. use GFCommon; 
  24. use GF_Field; 
  25. use GFQuiz; 
  26. use GFSurvey; 
  27. use GFPolls; 
  28. use GFResults; 
  29.  
  30. use WP_Error; 
  31.  
  32. use Exception; 
  33.  
  34. /** 
  35. * PDF Display Model, including the $form_data array 
  36. * 
  37. * @package Gravity PDF 
  38. * @copyright Copyright (c) 2016, Blue Liquid Designs 
  39. * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License 
  40. * @since 4.0 
  41. */ 
  42.  
  43. /** Exit if accessed directly */ 
  44. if ( ! defined( 'ABSPATH' ) ) { 
  45. exit; 
  46.  
  47. /** 
  48. This file is part of Gravity PDF. 
  49.   
  50. Gravity PDF * Copyright (C) 2016, Blue Liquid Designs 
  51.   
  52. This program is free software; you can redistribute it and/or modify 
  53. it under the terms of the GNU General Public License as published by 
  54. the Free Software Foundation; either version 2 of the License, or 
  55. (at your option) any later version. 
  56.   
  57. This program is distributed in the hope that it will be useful,  
  58. but WITHOUT ANY WARRANTY; without even the implied warranty of 
  59. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  60. GNU General Public License for more details. 
  61.   
  62. You should have received a copy of the GNU General Public License 
  63. along with this program; if not, write to the Free Software 
  64. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
  65. */ 
  66.  
  67. /** 
  68. * Model_PDF 
  69. * 
  70. * Handles all the PDF display logic 
  71. * 
  72. * @since 4.0 
  73. */ 
  74. class Model_PDF extends Helper_Abstract_Model { 
  75.  
  76. /** 
  77. * Holds the abstracted Gravity Forms API specific to Gravity PDF 
  78. * 
  79. * @var \GFPDF\Helper\Helper_Form 
  80. * 
  81. * @since 4.0 
  82. */ 
  83. protected $gform; 
  84.  
  85. /** 
  86. * Holds our log class 
  87. * 
  88. * @var \Monolog\Logger|LoggerInterface 
  89. * 
  90. * @since 4.0 
  91. */ 
  92. protected $log; 
  93.  
  94. /** 
  95. * Holds our Helper_Abstract_Options / Helper_Options_Fields object 
  96. * Makes it easy to access global PDF settings and individual form PDF settings 
  97. * 
  98. * @var \GFPDF\Helper\Helper_Options_Fields 
  99. * 
  100. * @since 4.0 
  101. */ 
  102. protected $options; 
  103.  
  104. /** 
  105. * Holds our Helper_Data object 
  106. * which we can autoload with any data needed 
  107. * 
  108. * @var \GFPDF\Helper\Helper_Data 
  109. * 
  110. * @since 4.0 
  111. */ 
  112. protected $data; 
  113.  
  114. /** 
  115. * Holds our Helper_Misc object 
  116. * Makes it easy to access common methods throughout the plugin 
  117. * 
  118. * @var \GFPDF\Helper\Helper_Misc 
  119. * 
  120. * @since 4.0 
  121. */ 
  122. protected $misc; 
  123.  
  124. /** 
  125. * Holds our Helper_Notices object 
  126. * which we can use to queue up admin messages for the user 
  127. * 
  128. * @var \GFPDF\Helper\Helper_Notices 
  129. * 
  130. * @since 4.0 
  131. */ 
  132. protected $notices; 
  133.  
  134. /** 
  135. * Holds our Helper_Templates object 
  136. * used to ease access to our PDF templates 
  137. * 
  138. * @var \GFPDF\Helper\Helper_Templates 
  139. * 
  140. * @since 4.0 
  141. */ 
  142. protected $templates; 
  143.  
  144. /** 
  145. * Setup our view with the needed data and classes 
  146. * 
  147. * @param \GFPDF\Helper\Helper_Abstract_Form $gform Our abstracted Gravity Forms helper functions 
  148. * @param \Monolog\Logger|LoggerInterface $log Our logger class 
  149. * @param \GFPDF\Helper\Helper_Abstract_Options $options Our options class which allows us to access any settings 
  150. * @param \GFPDF\Helper\Helper_Data $data Our plugin data store 
  151. * @param \GFPDF\Helper\Helper_Misc $misc Our miscellaneous class 
  152. * @param \GFPDF\Helper\Helper_Notices $notices Our notice class used to queue admin messages and errors 
  153. * @param \GFPDF\Helper\Helper_Templates $templates 
  154. * 
  155. * @since 4.0 
  156. */ 
  157. public function __construct( Helper_Abstract_Form $gform, LoggerInterface $log, Helper_Abstract_Options $options, Helper_Data $data, Helper_Misc $misc, Helper_Notices $notices, Helper_Templates $templates ) { 
  158.  
  159. /** Assign our internal variables */ 
  160. $this->gform = $gform; 
  161. $this->log = $log; 
  162. $this->options = $options; 
  163. $this->data = $data; 
  164. $this->misc = $misc; 
  165. $this->notices = $notices; 
  166. $this->templates = $templates; 
  167.  
  168. /** 
  169. * Our Middleware used to handle the authentication process 
  170. * 
  171. * @param string $pid The Gravity Form PDF Settings ID 
  172. * @param integer $lid The Gravity Form Entry ID 
  173. * @param string $action Whether the PDF should be viewed or downloaded 
  174. * 
  175. * @since 4.0 
  176. * 
  177. * @return WP_Error 
  178. */ 
  179. public function process_pdf( $pid, $lid, $action = 'view' ) { 
  180.  
  181. /** 
  182. * Check if we have a valid Gravity Form Entry and PDF Settings ID 
  183. */ 
  184. $entry = $this->gform->get_entry( $lid ); 
  185.  
  186. /** not a valid entry */ 
  187. if ( is_wp_error( $entry ) ) { 
  188. $this->log->addError( 'Invalid Entry.', [ 
  189. 'entry' => $entry,  
  190. ] ); 
  191.  
  192. return $entry; /** return error */ 
  193.  
  194. $settings = $this->options->get_pdf( $entry['form_id'], $pid ); 
  195.  
  196. /** Not valid settings */ 
  197. if ( is_wp_error( $settings ) ) { 
  198.  
  199. $this->log->addError( 'Invalid PDF Settings.', [ 
  200. 'entry' => $entry,  
  201. 'WP_Error_Message' => $settings->get_error_message(),  
  202. 'WP_Error_Code' => $settings->get_error_code(),  
  203. ] ); 
  204.  
  205. return $settings; /** return error */ 
  206.  
  207. /** Add our download setting */ 
  208. $settings['pdf_action'] = $action; 
  209.  
  210. /** 
  211. * Our middleware authenticator 
  212. * Allow users to tap into our middleware and add or remove additional authentication layers 
  213. * 
  214. * Default middleware includes 'middle_public_access', 'middle_active', 'middle_conditional', 'middle_owner_restriction', 'middle_logged_out_timeout', 'middle_auth_logged_out_user', 'middle_user_capability' 
  215. * If WP_Error is returned the PDF won't be parsed 
  216. * 
  217. * See https://gravitypdf.com/documentation/v4/gfpdf_pdf_middleware/ for more details about this filter 
  218. */ 
  219. $middleware = apply_filters( 'gfpdf_pdf_middleware', false, $entry, $settings ); 
  220.  
  221. /** Throw error */ 
  222. if ( is_wp_error( $middleware ) ) { 
  223.  
  224. $this->log->addError( 'PDF Authentication Failure.', [ 
  225. 'entry' => $entry,  
  226. 'settings' => $settings,  
  227. 'WP_Error_Message' => $middleware->get_error_message(),  
  228. 'WP_Error_Code' => $middleware->get_error_code(),  
  229. ] ); 
  230.  
  231. return $middleware; 
  232.  
  233. /** Add backwards compatibility support for certain settings */ 
  234. $settings = $this->apply_backwards_compatibility_filters( $settings, $entry ); 
  235.  
  236. /** Ensure Gravity Forms depedancy loaded */ 
  237. $this->misc->maybe_load_gf_entry_detail_class(); 
  238.  
  239. /** If we are here we can generate our PDF */ 
  240. $controller = $this->getController(); 
  241. $controller->view->generate_pdf( $entry, $settings ); 
  242.  
  243. return null; 
  244.  
  245. /** 
  246. * Apply filters to particular settings to maintain backwards compatibility 
  247. * Note: If you want to modify the $settings array you should use the new "gfpdf_pdf_config" filter instead 
  248. * 
  249. * @param array $settings The PDF settings array 
  250. * @param array $entry 
  251. * 
  252. * @return array The $settings array 
  253. * 
  254. * @since 4.0 
  255. */ 
  256. public function apply_backwards_compatibility_filters( $settings, $entry ) { 
  257.  
  258. $form = $this->gform->get_form( $entry['form_id'] ); 
  259.  
  260. $settings['filename'] = $this->misc->remove_extension_from_string( apply_filters( 'gfpdfe_pdf_name', $settings['filename'], $form, $entry ) ); 
  261. $settings['template'] = $this->misc->remove_extension_from_string( apply_filters( 'gfpdfe_template', $settings['template'], $form, $entry ), '.php' ); 
  262.  
  263. if ( isset( $settings['orientation'] ) ) { 
  264. $settings['orientation'] = apply_filters( 'gfpdf_orientation', $settings['orientation'], $form, $entry ); 
  265.  
  266. if ( isset( $settings['security'] ) ) { 
  267. $settings['security'] = $this->misc->update_deprecated_config( apply_filters( 'gfpdf_security', $settings['security'], $form, $entry ) ); 
  268.  
  269. if ( isset( $settings['privileges'] ) ) { 
  270. $settings['privileges'] = apply_filters( 'gfpdf_privilages', $settings['privileges'], $form, $entry ); 
  271.  
  272. if ( isset( $settings['password'] ) ) { 
  273. $settings['password'] = apply_filters( 'gfpdf_password', $settings['password'], $form, $entry ); 
  274.  
  275. if ( isset( $settings['master_password'] ) ) { 
  276. $settings['master_password'] = apply_filters( 'gfpdf_master_password', $settings['master_password'], $form, $entry ); 
  277.  
  278. if ( isset( $settings['rtl'] ) ) { 
  279. $settings['rtl'] = $this->misc->update_deprecated_config( apply_filters( 'gfpdf_rtl', $settings['rtl'], $form, $entry ) ); 
  280.  
  281. return $settings; 
  282.  
  283. /** 
  284. * Check if the current PDF trying to be viewed has public access enabled 
  285. * If it does, we'll remove some of our middleware filters to allow this feature 
  286. * 
  287. * @param boolean|object $action 
  288. * @param array $entry The Gravity Forms Entry 
  289. * @param array $settings The Gravity Form PDF Settings 
  290. * 
  291. * @return boolean|object 
  292. * 
  293. * @since 4.0 
  294. */ 
  295. public function middle_public_access( $action, $entry, $settings ) { 
  296.  
  297. if ( isset( $settings['public_access'] ) && 'Yes' === $settings['public_access'] ) { 
  298. remove_filter( 'gfpdf_pdf_middleware', [ $this, 'middle_owner_restriction' ], 40 ); 
  299. remove_filter( 'gfpdf_pdf_middleware', [ $this, 'middle_logged_out_timeout' ], 50 ); 
  300. remove_filter( 'gfpdf_pdf_middleware', [ $this, 'middle_auth_logged_out_user' ], 60 ); 
  301. remove_filter( 'gfpdf_pdf_middleware', [ $this, 'middle_user_capability' ], 70 ); 
  302.  
  303. return $action; 
  304.  
  305. /** 
  306. * Check if the current PDF trying to be viewed is active 
  307. * 
  308. * @param boolean|object $action 
  309. * @param array $entry The Gravity Forms Entry 
  310. * @param array $settings The Gravity Form PDF Settings 
  311. * 
  312. * @return boolean|object 
  313. * 
  314. * @since 4.0 
  315. */ 
  316. public function middle_active( $action, $entry, $settings ) { 
  317.  
  318. if ( ! is_wp_error( $action ) ) { 
  319. if ( $settings['active'] !== true ) { 
  320. return new WP_Error( 'inactive', esc_html__( 'The PDF configuration is not currently active.', 'gravity-forms-pdf-extended' ) ); 
  321.  
  322. return $action; 
  323.  
  324. /** 
  325. * Check if the current PDF trying to be viewed has conditional logic which passes 
  326. * 
  327. * @param boolean|object $action 
  328. * @param array $entry The Gravity Forms Entry 
  329. * @param array $settings The Gravity Form PDF Settings 
  330. * 
  331. * @return boolean|object 
  332. * 
  333. * @since 4.0 
  334. */ 
  335. public function middle_conditional( $action, $entry, $settings ) { 
  336.  
  337. if ( ! is_wp_error( $action ) ) { 
  338. if ( isset( $settings['conditionalLogic'] ) && ! $this->misc->evaluate_conditional_logic( $settings['conditionalLogic'], $entry ) ) { 
  339. return new WP_Error( 'conditional_logic', esc_html__( 'PDF conditional logic requirements have not been met.', 'gravity-forms-pdf-extended' ) ); 
  340.  
  341. return $action; 
  342.  
  343. /** 
  344. * Check if the current user attempting to access is the PDF owner 
  345. * 
  346. * @param array $entry The Gravity Forms Entry 
  347. * @param string $type The authentication type we should use 
  348. * 
  349. * @return boolean 
  350. * 
  351. * @since 4.0 
  352. */ 
  353. public function is_current_pdf_owner( $entry, $type = 'all' ) { 
  354. $owner = false; 
  355. /** check if the user is logged in and the entry is assigned to them */ 
  356. if ( $type === 'all' || $type === 'logged_in' ) { 
  357. if ( is_user_logged_in() && (int) $entry['created_by'] === get_current_user_id() ) { 
  358. $owner = true; 
  359.  
  360. if ( $type === 'all' || $type === 'logged_out' ) { 
  361. $user_ip = trim( GFFormsModel::get_ip() ); 
  362. if ( $entry['ip'] == $user_ip && $entry['ip'] !== '127.0.0.1' && strlen( $user_ip ) !== 0 ) { /** check if the user IP matches the entry IP */ 
  363. $owner = true; 
  364.  
  365. return $owner; 
  366.  
  367. /** 
  368. * Check the "Restrict Logged Out User" global setting and validate it against the current user 
  369. * 
  370. * @param boolean|object $action 
  371. * @param array $entry The Gravity Forms Entry 
  372. * @param array $settings The Gravity Form PDF Settings 
  373. * 
  374. * @return boolean|object 
  375. * 
  376. * @since 4.0 
  377. */ 
  378. public function middle_owner_restriction( $action, $entry, $settings ) { 
  379.  
  380. /** ensure another middleware filter hasn't already done validation */ 
  381. if ( ! is_wp_error( $action ) ) { 
  382. /** get the setting */ 
  383. $owner_restriction = ( isset( $settings['restrict_owner'] ) ) ? $settings['restrict_owner'] : 'No'; 
  384.  
  385. if ( $owner_restriction === 'Yes' && ! is_user_logged_in() ) { 
  386.  
  387. $this->log->addNotice( 'Redirecting to Login.', [ 
  388. 'entry' => $entry,  
  389. 'settings' => $settings,  
  390. ] ); 
  391.  
  392. /** prompt user to login */ 
  393. auth_redirect(); 
  394.  
  395. return $action; 
  396.  
  397. /** 
  398. * Check the "Logged Out Timeout" global setting and validate it against the current user 
  399. * 
  400. * @param boolean|object $action 
  401. * @param array $entry The Gravity Forms Entry 
  402. * @param array $settings The Gravity Form PDF Settings 
  403. * 
  404. * @return boolean|object 
  405. * 
  406. * @since 4.0 
  407. */ 
  408. public function middle_logged_out_timeout( $action, $entry, $settings ) { 
  409.  
  410. /** ensure another middleware filter hasn't already done validation */ 
  411. if ( ! is_wp_error( $action ) ) { 
  412.  
  413. /** only check if PDF timed out if our logged out restriction is not 'Yes' and the user is not logged in */ 
  414. if ( ! is_user_logged_in() && $this->is_current_pdf_owner( $entry, 'logged_out' ) === true ) { 
  415. /** get the global PDF settings */ 
  416. $timeout = (int) $this->options->get_option( 'logged_out_timeout', '30' ); 
  417.  
  418. /** if '0' there is no timeout, or if the logged out restrictions are enabled we'll ignore this */ 
  419. if ( $timeout !== 0 ) { 
  420.  
  421. $timeout_stamp = 60 * $timeout; /** 60 seconds multiplied by number of minutes */ 
  422. $entry_created = strtotime( $entry['date_created'] ); /** get entry timestamp */ 
  423. $timeout_expires = $entry_created + $timeout_stamp; /** get the timeout expiry based on the entry created time */ 
  424.  
  425. /** compare our two timestamps and throw error if outside the timeout */ 
  426. if ( time() > $timeout_expires ) { 
  427.  
  428. /** if there is no user account assigned to this entry throw error */ 
  429. if ( empty( $entry['created_by'] ) ) { 
  430. return new WP_Error( 'timeout_expired', esc_html__( 'Your PDF is no longer accessible.', 'gravity-forms-pdf-extended' ) ); 
  431. } else { 
  432.  
  433. $this->log->addNotice( 'Redirecting to Login.', [ 
  434. 'entry' => $entry,  
  435. 'settings' => $settings,  
  436. ] ); 
  437.  
  438. /** prompt to login */ 
  439. auth_redirect(); 
  440.  
  441. return $action; 
  442.  
  443. /** 
  444. * Check if the user is logged out and authenticate as needed 
  445. * 
  446. * @param boolean|object $action 
  447. * @param array $entry The Gravity Forms Entry 
  448. * @param array $settings The Gravity Form PDF Settings 
  449. * 
  450. * @return boolean|object 
  451. * 
  452. * @since 4.0 
  453. */ 
  454. public function middle_auth_logged_out_user( $action, $entry, $settings ) { 
  455.  
  456. if ( ! is_wp_error( $action ) ) { 
  457.  
  458. /** check if the user is not the current entry owner */ 
  459. if ( ! is_user_logged_in() && $this->is_current_pdf_owner( $entry, 'logged_out' ) === false ) { 
  460. /** check if there is actually a user who owns entry */ 
  461. if ( ! empty( $entry['created_by'] ) ) { 
  462.  
  463. $this->log->addNotice( 'Redirecting to Login.', [ 
  464. 'entry' => $entry,  
  465. 'settings' => $settings,  
  466. ] ); 
  467.  
  468. /** prompt user to login to get access */ 
  469. auth_redirect(); 
  470. } else { 
  471. /** there's no returning, throw generic error */ 
  472. return new WP_Error( 'error' ); 
  473.  
  474. return $action; 
  475.  
  476. /** 
  477. * Check the "User Restriction" global setting and validate it against the current user 
  478. * 
  479. * @param boolean|object $action 
  480. * @param array $entry The Gravity Forms Entry 
  481. * @param array $settings The Gravity Form PDF Settings 
  482. * 
  483. * @return boolean|object 
  484. * 
  485. * @since 4.0 
  486. */ 
  487. public function middle_user_capability( $action, $entry, $settings ) { 
  488.  
  489. if ( ! is_wp_error( $action ) ) { 
  490. /** check if the user is logged in but is not the current owner */ 
  491. if ( is_user_logged_in() && 
  492. ( ( $this->options->get_option( 'limit_to_admin', 'No' ) == 'Yes' ) || ( $this->is_current_pdf_owner( $entry, 'logged_in' ) === false ) ) 
  493. ) { 
  494.  
  495. /** Handle permissions checks */ 
  496. $admin_permissions = $this->options->get_option( 'admin_capabilities', [ 'gravityforms_view_entries' ] ); 
  497.  
  498. /** loop through permissions and check if the current user has any of those capabilities */ 
  499. $access = false; 
  500. foreach ( $admin_permissions as $permission ) { 
  501. if ( $this->gform->has_capability( $permission ) ) { 
  502. $access = true; 
  503.  
  504. /** throw error if no access granted */ 
  505. if ( ! $access ) { 
  506. return new WP_Error( 'access_denied', esc_html__( 'You do not have access to view this PDF.', 'gravity-forms-pdf-extended' ) ); 
  507.  
  508. return $action; 
  509.  
  510. /** 
  511. * Display PDF on Gravity Form entry list page 
  512. * 
  513. * @param integer $form_id Gravity Form ID 
  514. * @param integer $field_id Current field ID 
  515. * @param mixed $value Current value of field 
  516. * @param array $entry Entry Information 
  517. * 
  518. * @return void 
  519. * 
  520. * @since 4.0 
  521. */ 
  522. public function view_pdf_entry_list( $form_id, $field_id, $value, $entry ) { 
  523.  
  524. $controller = $this->getController(); 
  525. $pdf_list = $this->get_pdf_display_list( $entry ); 
  526.  
  527. $this->log->addNotice( 'Display PDF Entry List.', [ 
  528. 'pdfs' => $pdf_list,  
  529. 'entry' => $entry,  
  530. ] ); 
  531.  
  532. if ( ! empty( $pdf_list ) ) { 
  533.  
  534. if ( sizeof( $pdf_list ) > 1 ) { 
  535. $args = [ 
  536. 'pdfs' => $pdf_list,  
  537. 'view' => strtolower( $this->options->get_option( 'default_action' ) ),  
  538. ]; 
  539.  
  540. $controller->view->entry_list_pdf_multiple( $args ); 
  541. } else { 
  542. /** Only one PDF for this form so display a simple 'View PDF' link */ 
  543. $args = [ 
  544. 'pdf' => array_shift( $pdf_list ),  
  545. 'view' => strtolower( $this->options->get_option( 'default_action' ) ),  
  546. ]; 
  547.  
  548. $controller->view->entry_list_pdf_single( $args ); 
  549.  
  550. /** 
  551. * Display the PDF links on the entry detailed section of the admin area 
  552. * 
  553. * @param integer $form_id Gravity Form ID 
  554. * @param array $entry The entry information 
  555. * 
  556. * @return void 
  557. * 
  558. * @since 4.0 
  559. */ 
  560. public function view_pdf_entry_detail( $form_id, $entry ) { 
  561.  
  562. $controller = $this->getController(); 
  563. $pdf_list = $this->get_pdf_display_list( $entry ); 
  564.  
  565. $this->log->addNotice( 'Display PDF Entry Detail List.', [ 
  566. 'pdfs' => $pdf_list,  
  567. 'entry' => $entry,  
  568. ] ); 
  569.  
  570. if ( ! empty( $pdf_list ) ) { 
  571. $args = [ 
  572. 'pdfs' => $pdf_list,  
  573. ]; 
  574. $controller->view->entry_detailed_pdf( $args ); 
  575.  
  576. /** 
  577. * Get a preformatted list of active PDFs with name and URL 
  578. * 
  579. * @param array $entry 
  580. * 
  581. * @return array 
  582. * 
  583. * @since 4.0 
  584. */ 
  585. public function get_pdf_display_list( $entry ) { 
  586.  
  587. /** Stores our formatted PDFs */ 
  588. $args = []; 
  589.  
  590. /** Check if we have any PDFs */ 
  591. $form = $this->gform->get_form( $entry['form_id'] ); 
  592. $pdfs = ( isset( $form['gfpdf_form_settings'] ) ) ? $this->get_active_pdfs( $form['gfpdf_form_settings'], $entry ) : []; 
  593.  
  594. if ( ! empty( $pdfs ) ) { 
  595.  
  596. foreach ( $pdfs as $settings ) { 
  597.  
  598. $args[] = [ 
  599. 'name' => $this->get_pdf_name( $settings, $entry ),  
  600. 'view' => $this->get_pdf_url( $settings['id'], $entry['id'], false ),  
  601. 'download' => $this->get_pdf_url( $settings['id'], $entry['id'], true ),  
  602. ]; 
  603.  
  604. return $args; 
  605.  
  606. /** 
  607. * Generate the PDF Name 
  608. * 
  609. * @param array $settings The PDF Form Settings 
  610. * @param array $entry The Gravity Form entry details 
  611. * 
  612. * @return string The PDF Name 
  613. * 
  614. * @since 4.0 
  615. */ 
  616. public function get_pdf_name( $settings, $entry ) { 
  617.  
  618. $form = $this->gform->get_form( $entry['form_id'] ); 
  619. $name = $this->gform->process_tags( $settings['filename'], $form, $entry ); 
  620.  
  621. /** Decode HTML entities */ 
  622. $name = wp_specialchars_decode( $name, ENT_QUOTES ); 
  623.  
  624. /** 
  625. * Add filter to modify PDF name 
  626. * 
  627. * See https://gravitypdf.com/documentation/v4/gfpdf_pdf_filename/ for more details about this filter 
  628. */ 
  629. $name = apply_filters( 'gfpdf_pdf_filename', $name, $form, $entry, $settings ); 
  630.  
  631. /** Backwards compatible filter */ 
  632. $name = apply_filters( 'gfpdfe_pdf_filename', $name, $form, $entry, $settings ); 
  633.  
  634. /** Remove any characters that cannot be present in a filename */ 
  635. $name = $this->misc->strip_invalid_characters( $name ); 
  636.  
  637. return $name; 
  638.  
  639. /** 
  640. * Create a PDF Link based on the current PDF settings and entry 
  641. * 
  642. * @param integer $pid The PDF Form Settings ID 
  643. * @param integer $id The Gravity Form entry ID 
  644. * @param boolean $download Whether the PDF should be downloaded or not 
  645. * @param boolean $print Whether we should mark the PDF to be printed 
  646. * @param boolean $esc Whether to escape the URL or not 
  647. * 
  648. * @return string Direct link to the PDF 
  649. * 
  650. * @since 4.0 
  651. */ 
  652. public function get_pdf_url( $pid, $id, $download = false, $print = false, $esc = true ) { 
  653. global $wp_rewrite; 
  654.  
  655. /** Check if permalinks are enabled, otherwise fall back to our ugly link structure for 4.0 (not the same as our v3 links) */ 
  656. if ( $wp_rewrite->using_permalinks() ) { 
  657. $url = home_url() . '/' . $wp_rewrite->root; /** Handle "almost pretty" permalinks - fix for IIS servers without modrewrite */ 
  658. $url .= 'pdf/' . $pid . '/' . $id . '/'; 
  659.  
  660. if ( $download ) { 
  661. $url .= 'download/'; 
  662.  
  663. if ( $print ) { 
  664. $url .= '?print=1'; 
  665. } else { 
  666. $url = home_url() . '/?gpdf=1&pid=' . $pid . '&lid=' . $id; 
  667.  
  668. if ( $download ) { 
  669. $url .= '&action=download'; 
  670.  
  671. if ( $print ) { 
  672. $url .= '&print=1'; 
  673.  
  674. if ( $esc ) { 
  675. $url = esc_url( $url ); 
  676.  
  677. return $url; 
  678.  
  679. /** 
  680. * Filter out inactive PDFs and those who don't meet the conditional logic 
  681. * 
  682. * @param array $pdfs The PDF settings array 
  683. * @param array $entry The current entry information 
  684. * 
  685. * @return array The filtered PDFs 
  686. * 
  687. * @since 4.0 
  688. */ 
  689. public function get_active_pdfs( $pdfs, $entry ) { 
  690.  
  691. $filtered = []; 
  692. $form = $this->gform->get_form( $entry['form_id'] ); 
  693.  
  694. foreach ( $pdfs as $pdf ) { 
  695. if ( $pdf['active'] && ( empty( $pdf['conditionalLogic'] ) || $this->misc->evaluate_conditional_logic( $pdf['conditionalLogic'], $entry ) ) ) { 
  696. $filtered[ $pdf['id'] ] = $pdf; 
  697.  
  698. return $filtered; 
  699.  
  700. /** 
  701. * Generate and save PDF to disk 
  702. * 
  703. * @param \GFPDF\Helper\Helper_PDF $pdf The Helper_PDF object 
  704. * 
  705. * @return boolean 
  706. * 
  707. * @since 4.0 
  708. */ 
  709. public function process_and_save_pdf( Helper_PDF $pdf ) { 
  710.  
  711. /** Check that the PDF hasn't already been created this session */ 
  712. if ( ! $this->does_pdf_exist( $pdf ) ) { 
  713.  
  714. /** Ensure Gravity Forms depedancy loaded */ 
  715. $this->misc->maybe_load_gf_entry_detail_class(); 
  716.  
  717. /** Enable Multicurrency support */ 
  718. $this->misc->maybe_add_multicurrency_support(); 
  719.  
  720. /** Get required parameters */ 
  721. $entry = $pdf->get_entry(); 
  722. $settings = $pdf->get_settings(); 
  723. $form = $this->gform->get_form( $entry['form_id'] ); 
  724.  
  725. $args = $this->templates->get_template_arguments( 
  726. $form,  
  727. $this->misc->get_fields_sorted_by_id( $form['id'] ),  
  728. $entry,  
  729. $this->get_form_data( $entry ),  
  730. $settings,  
  731. $this->templates->get_config_class( $settings['template'] ),  
  732. $this->misc->get_legacy_ids( $entry['id'], $settings ) 
  733. ); 
  734.  
  735. /** Add backwards compatibility support */ 
  736. $GLOBALS['wp']->query_vars['pid'] = $settings['id']; 
  737. $GLOBALS['wp']->query_vars['lid'] = $entry['id']; 
  738.  
  739. try { 
  740.  
  741. /** Initialise our PDF helper class */ 
  742. $pdf->init(); 
  743. $pdf->set_template(); 
  744. $pdf->set_output_type( 'save' ); 
  745.  
  746. /** Increment our rudimentary PDF counter */ 
  747. $this->options->increment_pdf_count(); 
  748.  
  749. /** Add Backwards compatibility support for our v3 Tier 2 Add-on */ 
  750. if ( isset( $settings['advanced_template'] ) && strtolower( $settings['advanced_template'] ) == 'yes' ) { 
  751.  
  752. /** Check if we should process this document using our legacy system */ 
  753. if ( $this->handle_legacy_tier_2_processing( $pdf, $entry, $settings, $args ) ) { 
  754. return true; 
  755.  
  756. /** Render the PDF template HTML */ 
  757. $pdf->render_html( $args ); 
  758.  
  759. /** Generate and save the PDF */ 
  760. $pdf->save_pdf( $pdf->generate() ); 
  761.  
  762. return true; 
  763. } catch ( Exception $e ) { 
  764.  
  765. $this->log->addError( 'PDF Generation Error', [ 
  766. 'pdf' => $pdf,  
  767. 'exception' => $e->getMessage(),  
  768. ] ); 
  769.  
  770. return false; 
  771.  
  772. return true; 
  773.  
  774. /** 
  775. * Handles the loading and running of our legacy Tier 2 PDF templates 
  776. * 
  777. * @param \GFPDF\Helper\Helper_PDF $pdf The Helper_PDF object 
  778. * @param array $entry The Gravity Forms raw entry data 
  779. * @param array $settings The Gravity PDF settings 
  780. * @param array $args The data that should be passed directly to a PDF template 
  781. * 
  782. * @return bool 
  783. * 
  784. * @since 4.0 
  785. */ 
  786. public function handle_legacy_tier_2_processing( Helper_PDF $pdf, $entry, $settings, $args ) { 
  787.  
  788. $form = $this->gform->get_form( $entry['form_id'] ); 
  789.  
  790. $prevent_main_pdf_loader = apply_filters( 'gfpdfe_pre_load_template',  
  791. $form['id'],  
  792. $entry['id'],  
  793. basename( $pdf->get_template_path() ),  
  794. $form['id'] . $entry['id'],  
  795. $this->misc->backwards_compat_output( $pdf->get_output_type() ),  
  796. $pdf->get_filename(),  
  797. $this->misc->backwards_compat_conversion( $settings, $form, $entry ),  
  798. $args 
  799. ); /** Backwards Compatibility */ 
  800.  
  801. return ( $prevent_main_pdf_loader === true ) ? true : false; 
  802.  
  803. /** 
  804. * Generate and save the PDF to disk 
  805. * 
  806. * @param array $entry The Gravity Form entry array (usually passed in as a filter or pulled using GFAPI::get_entry( $id ) ) 
  807. * @param array $settings The PDF configuration settings for the particular entry / form being processed 
  808. * 
  809. * @return string|WP_Error Return the full path to the PDF, or a WP_Error on failure 
  810. * 
  811. * @since 4.0 
  812. */ 
  813. public function generate_and_save_pdf( $entry, $settings ) { 
  814.  
  815. $pdf_generator = new Helper_PDF( $entry, $settings, $this->gform, $this->data, $this->misc, $this->templates ); 
  816. $pdf_generator->set_filename( $this->get_pdf_name( $settings, $entry ) ); 
  817.  
  818. if ( $this->process_and_save_pdf( $pdf_generator ) ) { 
  819. $pdf_path = $pdf_generator->get_full_pdf_path(); 
  820.  
  821. if ( is_file( $pdf_path ) ) { 
  822.  
  823. /** Add appropriate filters so developers can access the PDF when it is generated */ 
  824. $form = $this->gform->get_form( $entry['form_id'] ); 
  825. $filename = basename( $pdf_path ); 
  826.  
  827. do_action( 'gfpdf_post_pdf_save', $form['id'], $entry['id'], $settings, $pdf_path ); /** Backwards compatibility */ 
  828.  
  829. /** See https://gravitypdf.com/documentation/v4/gfpdf_post_save_pdf/ for more details about these actions */ 
  830. do_action( 'gfpdf_post_save_pdf', $pdf_path, $filename, $settings, $entry, $form ); 
  831. do_action( 'gfpdf_post_save_pdf_' . $form['id'], $pdf_path, $filename, $settings, $entry, $form ); 
  832.  
  833. return $pdf_path; 
  834.  
  835. return new WP_Error( 'pdf_generation_failure', esc_html__( 'The PDF could not be saved.', 'gravity-forms-pdf-extended' ) ); 
  836.  
  837.  
  838. /** 
  839. * Check if the form has any PDFs, generate them and attach to the notification 
  840. * 
  841. * @param array $notifications Gravity Forms Notification Array 
  842. * @param array $form 
  843. * @param array $entry 
  844. * 
  845. * @return array 
  846. * 
  847. * @since 4.0 
  848. */ 
  849. public function notifications( $notifications, $form, $entry ) { 
  850.  
  851. /** 
  852. * Ensure our entry is stored in the database by checking it has an ID 
  853. * This resolves any issues with the "Save and Continue" feature 
  854. * See https://github.com/GravityPDF/gravity-pdf/issues/360 
  855. */ 
  856. if ( null === $entry['id'] ) { 
  857. return $notifications; 
  858.  
  859. $pdfs = ( isset( $form['gfpdf_form_settings'] ) ) ? $this->get_active_pdfs( $form['gfpdf_form_settings'], $entry ) : []; 
  860.  
  861. if ( sizeof( $pdfs ) > 0 ) { 
  862.  
  863. /** Ensure our notification has an array setup for the attachments key */ 
  864. $notifications['attachments'] = ( isset( $notifications['attachments'] ) ) ? $notifications['attachments'] : []; 
  865.  
  866. /** Loop through each PDF config and generate */ 
  867. foreach ( $pdfs as $settings ) { 
  868.  
  869. /** Reset the variables each loop */ 
  870. $filename = $tier_2_filename = ''; 
  871.  
  872. if ( $this->maybe_attach_to_notification( $notifications, $settings ) ) { 
  873.  
  874. /** Generate our PDF */ 
  875. $filename = $this->generate_and_save_pdf( $entry, $settings ); 
  876.  
  877. if ( ! is_wp_error( $filename ) ) { 
  878. $notifications['attachments'][] = $filename; 
  879.  
  880. $this->log->addNotice( 'Gravity Forms Attachments', [ 
  881. 'attachments' => $notifications['attachments'],  
  882. 'notification' => $notifications,  
  883. ] ); 
  884.  
  885.  
  886. return $notifications; 
  887.  
  888. /** 
  889. * Determine if the PDF should be attached to the current notification 
  890. * 
  891. * @param array $notification The Gravity Form Notification currently being processed 
  892. * @param array $settings The current Gravity PDF Settings 
  893. * 
  894. * @return boolean 
  895. * 
  896. * @since 4.0 
  897. */ 
  898. public function maybe_attach_to_notification( $notification, $settings ) { 
  899.  
  900. if ( isset( $settings['notification'] ) && is_array( $settings['notification'] ) ) { 
  901. if ( in_array( $notification['id'], $settings['notification'] ) ) { 
  902. return true; 
  903.  
  904. return false; 
  905.  
  906. /** 
  907. * Determine if the PDF should be saved to disk 
  908. * 
  909. * @param array $settings The current Gravity PDF Settings 
  910. * 
  911. * @return boolean 
  912. * 
  913. * @since 4.0 
  914. */ 
  915. public function maybe_always_save_pdf( $settings ) { 
  916. if ( isset( $settings['save'] ) && strtolower( $settings['save'] ) == 'yes' ) { 
  917. return true; 
  918.  
  919. return false; 
  920.  
  921. /** 
  922. * Creates a PDF on every submission, except when the PDF is already created during the notification hook 
  923. * 
  924. * @param array $entry The GF Entry Details 
  925. * @param array $form The Gravity Form 
  926. * 
  927. * @return void 
  928. * 
  929. * @since 4.0 
  930. */ 
  931. public function maybe_save_pdf( $entry, $form ) { 
  932. $pdfs = ( isset( $form['gfpdf_form_settings'] ) ) ? $this->get_active_pdfs( $form['gfpdf_form_settings'], $entry ) : []; 
  933.  
  934. if ( sizeof( $pdfs ) > 0 ) { 
  935.  
  936. /** Loop through each PDF config */ 
  937. foreach ( $pdfs as $pdf ) { 
  938. $settings = $this->options->get_pdf( $entry['form_id'], $pdf['id'] ); 
  939.  
  940. /** Only generate if the PDF wasn't created during the notification process */ 
  941. if ( ! is_wp_error( $settings ) && $this->maybe_always_save_pdf( $settings ) ) { 
  942. $this->generate_and_save_pdf( $entry, $settings ); 
  943.  
  944. /** 
  945. * Check if the current PDF to be processed already exists on disk 
  946. * 
  947. * @param \GFPDF\Helper\Helper_PDF $pdf The Helper_PDF Object 
  948. * 
  949. * @return boolean 
  950. * 
  951. * @since 4.0 
  952. */ 
  953. public function does_pdf_exist( Helper_PDF $pdf ) { 
  954.  
  955. if ( is_file( $pdf->get_full_pdf_path() ) ) { 
  956. return true; 
  957.  
  958. return false; 
  959.  
  960. /** 
  961. * To prevent ourt tmp directory getting huge we will clean it up every 24 hours 
  962. * 
  963. * @return void 
  964. * 
  965. * @since 4.0 
  966. */ 
  967. public function cleanup_tmp_dir() { 
  968.  
  969. $max_file_age = 24 * 3600; /** Max age is 24 hours old */ 
  970. $tmp_directory = $this->data->template_tmp_location; 
  971.  
  972. if ( is_dir( $tmp_directory ) ) { 
  973. /** Scan the tmp directory and get a list of files / folders */ 
  974. $directory_list = array_diff( scandir( $tmp_directory ), [ '..', '.', '.htaccess' ] ); 
  975.  
  976. foreach ( $directory_list as $item ) { 
  977. $file = $tmp_directory . $item; 
  978. $directory = false; 
  979.  
  980. /** Fix to allow filemtime to work on directories too */ 
  981. if ( is_dir( $file ) ) { 
  982. $file .= '.'; 
  983. $directory = true; 
  984.  
  985. /** Check if the file is too old and delete file / directory */ 
  986. if ( file_exists( $file ) && filemtime( $file ) < time() - $max_file_age ) { 
  987.  
  988. if ( $directory ) { 
  989. $this->misc->rmdir( substr( $file, 0, -1 ) ); 
  990. } else { 
  991. if ( ! unlink( $file ) ) { 
  992. $this->log->addError( 'Filesystem Delete Error', [ 
  993. 'file' => $file,  
  994. ] ); 
  995.  
  996. /** 
  997. * Remove the generated PDF from the server to save disk space 
  998. * 
  999. * @internal In future we may give the option to cache PDFs to save on processing power 
  1000. * 
  1001. * @param array $entry The GF Entry Data 
  1002. * @param array $form The Gravity Form 
  1003. * 
  1004. * @return void 
  1005. * 
  1006. * @since 4.0 
  1007. * 
  1008. * @todo Add PDF caching support to make software more performant. Need to review correct triggers for a cleanup (API-based, UI actions, 3rd-party add-on compatibility) 
  1009. */ 
  1010. public function cleanup_pdf( $entry, $form ) { 
  1011.  
  1012. $pdfs = ( isset( $form['gfpdf_form_settings'] ) ) ? $this->get_active_pdfs( $form['gfpdf_form_settings'], $entry ) : []; 
  1013.  
  1014. if ( sizeof( $pdfs ) > 0 ) { 
  1015.  
  1016. /** loop through each PDF config */ 
  1017. foreach ( $pdfs as $pdf ) { 
  1018. $settings = $this->options->get_pdf( $entry['form_id'], $pdf['id'] ); 
  1019.  
  1020. /** Only generate if the PDF wasn't during the notification process */ 
  1021. if ( ! is_wp_error( $settings ) ) { 
  1022.  
  1023. $pdf_generator = new Helper_PDF( $entry, $settings, $this->gform, $this->data, $this->misc, $this->templates ); 
  1024. $pdf_generator->set_filename( $this->get_pdf_name( $settings, $entry ) ); 
  1025.  
  1026. if ( $this->does_pdf_exist( $pdf_generator ) ) { 
  1027. try { 
  1028. $this->misc->rmdir( $pdf_generator->get_path() ); 
  1029. } catch ( Exception $e ) { 
  1030.  
  1031. $this->log->addError( 'Cleanup PDF Error', [ 
  1032. 'pdf' => $pdf,  
  1033. 'exception' => $e->getMessage(),  
  1034. ] ); 
  1035.  
  1036. /** 
  1037. * Triggered after the Gravity Form entry is updated 
  1038. * 
  1039. * @param array $form 
  1040. * @param int $entry_id 
  1041. */ 
  1042. public function cleanup_pdf_after_submission( $form, $entry_id ) { 
  1043. $entry = $this->gform->get_entry( $entry_id ); 
  1044. $this->cleanup_pdf( $entry, $form ); 
  1045.  
  1046. /** 
  1047. * Clean-up any PDFs stored on disk before we resend any notifications 
  1048. * 
  1049. * @param array $form The Gravity Forms object 
  1050. * @param array $leads An array of Gravity Form entry IDs 
  1051. * 
  1052. * @since 4.0 
  1053. * 
  1054. * @return array We tapped into a filter so we need to return the form object 
  1055. */ 
  1056. public function resend_notification_pdf_cleanup( $form, $leads ) { 
  1057. foreach ( $leads as $entry_id ) { 
  1058. $entry = $this->gform->get_entry( $entry_id ); 
  1059. $this->cleanup_pdf( $entry, $form ); 
  1060.  
  1061. return $form; 
  1062.  
  1063. /** 
  1064. * Changes mPDF's tmp folder 
  1065. * 
  1066. * @param string $path The current path 
  1067. * 
  1068. * @return string The new path 
  1069. */ 
  1070. public function mpdf_tmp_path( $path ) { 
  1071. return $this->data->template_tmp_location; 
  1072.  
  1073. /** 
  1074. * Changes mPDF's fontdata folders 
  1075. * 
  1076. * @param string $path The current path 
  1077. * 
  1078. * @return string The new path 
  1079. */ 
  1080. public function mpdf_tmp_font_path( $path ) { 
  1081. return $this->data->template_fontdata_location; 
  1082.  
  1083. /** 
  1084. * An mPDF filter that checks if mPDF has the font currently installed, otherwise 
  1085. * will look in the Gravity PDF font folder for an alternative. 
  1086. * 
  1087. * @param string $path The current path to the font mPDF is trying to load 
  1088. * @param string $font The current font name trying to be loaded 
  1089. * 
  1090. * @since 4.0 
  1091. * 
  1092. * @return string 
  1093. */ 
  1094. public function set_current_pdf_font( $path, $font ) { 
  1095.  
  1096. /** If the current font doesn't exist in mPDF core we'll look in our font folder */ 
  1097. if ( ! is_file( $path ) ) { 
  1098.  
  1099. if ( is_file( $this->data->template_font_location . $font ) ) { 
  1100. $path = $this->data->template_font_location . $font; 
  1101.  
  1102. return $path; 
  1103.  
  1104. /** 
  1105. * An mPDF filter which will register our custom font data with mPDF 
  1106. * 
  1107. * @param array $fonts The registered fonts 
  1108. * 
  1109. * @since 4.0 
  1110. * 
  1111. * @return array 
  1112. */ 
  1113. public function register_custom_font_data_with_mPDF( $fonts ) { 
  1114.  
  1115. $custom_fonts = $this->options->get_custom_fonts(); 
  1116.  
  1117. foreach ( $custom_fonts as $font ) { 
  1118.  
  1119. $fonts[ $font['shortname'] ] = array_filter( [ 
  1120. 'R' => basename( $font['regular'] ),  
  1121. 'B' => basename( $font['bold'] ),  
  1122. 'I' => basename( $font['italics'] ),  
  1123. 'BI' => basename( $font['bolditalics'] ),  
  1124. ] ); 
  1125.  
  1126. return $fonts; 
  1127.  
  1128. /** 
  1129. * Read all fonts from our fonts directory and auto-load them into mPDF if they are not found 
  1130. * 
  1131. * @param array $fonts The registered fonts 
  1132. * 
  1133. * @since 4.0 
  1134. * 
  1135. * @return array 
  1136. */ 
  1137. public function add_unregistered_fonts_to_mPDF( $fonts ) { 
  1138.  
  1139. $user_fonts = glob( $this->data->template_font_location . '*.[tT][tT][fF]' ); 
  1140. $user_fonts = ( is_array( $user_fonts ) ) ? $user_fonts : []; 
  1141.  
  1142. foreach ( $user_fonts as $font ) { 
  1143.  
  1144. /** Get font shortname */ 
  1145. $font_name = basename( $font ); 
  1146. $short_name = $this->options->get_font_short_name( substr( $font_name, 0, -4 ) ); 
  1147.  
  1148. /** Check if it exists already, otherwise add it */ 
  1149. if ( ! isset( $fonts[ $short_name ] ) && ! $this->misc->in_array( $font_name, $fonts ) ) { 
  1150. $fonts[ $short_name ] = [ 
  1151. 'R' => $font_name,  
  1152. ]; 
  1153.  
  1154. return $fonts; 
  1155.  
  1156. /** 
  1157. * Generates our $data array 
  1158. * 
  1159. * @param array $entry The Gravity Form Entry 
  1160. * 
  1161. * @return array The $data array 
  1162. * 
  1163. * @since 4.0 
  1164. */ 
  1165. public function get_form_data( $entry ) { 
  1166.  
  1167. if ( ! isset( $entry['form_id']) ) { 
  1168. return []; 
  1169.  
  1170. $form = $this->gform->get_form( $entry['form_id'] ); 
  1171.  
  1172. if( ! is_array( $form ) ) { 
  1173. return []; 
  1174.  
  1175. /** Setup our basic structure */ 
  1176. $data = [ 
  1177. 'misc' => [],  
  1178. 'field' => [],  
  1179. 'field_descriptions' => [],  
  1180. ]; 
  1181.  
  1182. /** 
  1183. * Create a product class for use 
  1184. * 
  1185. * @var Field_Products 
  1186. */ 
  1187. $products = new Field_Products( new GF_Field(), $entry, $this->gform, $this->misc ); 
  1188.  
  1189. /** Get the form details */ 
  1190. $form_meta = $this->get_form_data_meta( $form, $entry ); 
  1191.  
  1192. /** Get the survey, quiz and poll data if applicable */ 
  1193. $quiz = $this->get_quiz_results( $form, $entry ); 
  1194. $survey = $this->get_survey_results( $form, $entry ); 
  1195. $poll = $this->get_poll_results( $form, $entry ); 
  1196.  
  1197. /** Merge in the meta data and survey, quiz and poll data */ 
  1198. $data = array_replace_recursive( $data, $form_meta, $quiz, $survey, $poll ); 
  1199.  
  1200. /** 
  1201. * Loop through the form data, call the correct field object and 
  1202. * save the data to our $data array 
  1203. */ 
  1204. if ( isset( $form['fields'] ) ) { 
  1205. foreach ( $form['fields'] as $field ) { 
  1206.  
  1207. /** Skip over captcha, password and page fields */ 
  1208. $fields_to_skip = apply_filters( 'gfpdf_form_data_skip_fields', [ 
  1209. 'captcha',  
  1210. 'password',  
  1211. 'page',  
  1212. ] ); 
  1213.  
  1214. if ( in_array( $field->type, $fields_to_skip ) ) { 
  1215. continue; 
  1216.  
  1217. /** Include any field descriptions */ 
  1218. $data['field_descriptions'][ $field->id ] = ( ! empty( $field->description ) ) ? $field->description : ''; 
  1219.  
  1220. /** Get our field object */ 
  1221. $class = $this->get_field_class( $field, $form, $entry, $products ); 
  1222.  
  1223. /** Merge in the field object form_data() results */ 
  1224. $data = array_replace_recursive( $data, $class->form_data() ); 
  1225.  
  1226. /** Load our product array if products exist */ 
  1227. if ( ! $products->is_empty() ) { 
  1228. $data = array_replace_recursive( $data, $products->form_data() ); 
  1229.  
  1230. /** Re-order the array keys to make it more readable */ 
  1231. $order = [ 
  1232. 'misc',  
  1233. 'field',  
  1234. 'list',  
  1235. 'signature_details_id',  
  1236. 'products',  
  1237. 'products_totals',  
  1238. 'poll',  
  1239. 'survey',  
  1240. 'quiz',  
  1241. 'pages',  
  1242. 'html_id',  
  1243. 'section_break',  
  1244. 'field_descriptions',  
  1245. 'signature',  
  1246. 'signature_details',  
  1247. 'html',  
  1248. ]; 
  1249.  
  1250. foreach ( $order as $key ) { 
  1251.  
  1252. /** If item exists pop it onto the end of the array */ 
  1253. if ( isset( $data[ $key ] ) ) { 
  1254. $item = $data[ $key ]; 
  1255. unset( $data[ $key ] ); 
  1256. $data[ $key ] = $item; 
  1257.  
  1258. $this->log->addNotice( 'Form Data Array Created', [ 
  1259. 'data' => $data,  
  1260. ] ); 
  1261.  
  1262. return $data; 
  1263.  
  1264. /** 
  1265. * Return our general $data information 
  1266. * 
  1267. * @param array $form The Gravity Form 
  1268. * @param array $entry The Gravity Form Entry 
  1269. * 
  1270. * @return array The $data array 
  1271. * 
  1272. * @since 4.0 
  1273. */ 
  1274. public function get_form_data_meta( $form, $entry ) { 
  1275. $data = []; 
  1276.  
  1277. /** Add form_id and entry_id for convinience */ 
  1278. $data['form_id'] = $entry['form_id']; 
  1279. $data['entry_id'] = $entry['id']; 
  1280.  
  1281. /** Set title and dates (both US and international) */ 
  1282. $data['form_title'] = ( isset( $form['title'] ) ) ? $form['title'] : ''; 
  1283.  
  1284. $data['form_description'] = ( isset( $form['description'] ) ) ? $form['description'] : ''; 
  1285. $data['date_created'] = GFCommon::format_date( $entry['date_created'], false, 'j/n/Y', false ); 
  1286. $data['date_created_usa'] = GFCommon::format_date( $entry['date_created'], false, 'n/j/Y', false ); 
  1287.  
  1288. /** Include page names */ 
  1289. $data['pages'] = ( isset( $form['pagination']['pages'] ) ? $form['pagination']['pages'] : [] ); 
  1290.  
  1291. /** Add misc fields */ 
  1292. $data['misc']['date_time'] = GFCommon::format_date( $entry['date_created'], false, 'Y-m-d H:i:s', false ); 
  1293. $data['misc']['time_24hr'] = GFCommon::format_date( $entry['date_created'], false, 'H:i', false ); 
  1294. $data['misc']['time_12hr'] = GFCommon::format_date( $entry['date_created'], false, 'g:ia', false ); 
  1295.  
  1296. $include = [ 
  1297. 'is_starred',  
  1298. 'is_read',  
  1299. 'ip',  
  1300. 'source_url',  
  1301. 'post_id',  
  1302. 'currency',  
  1303. 'payment_status',  
  1304. 'payment_date',  
  1305. 'transaction_id',  
  1306. 'payment_amount',  
  1307. 'is_fulfilled',  
  1308. 'created_by',  
  1309. 'transaction_type',  
  1310. 'user_agent',  
  1311. 'status',  
  1312. ]; 
  1313.  
  1314. foreach ( $include as $item ) { 
  1315. $data['misc'][ $item ] = ( isset( $entry[ $item ] ) ) ? $entry[ $item ] : ''; 
  1316.  
  1317. return $data; 
  1318.  
  1319. /** 
  1320. * Pull the Survey Results into the $form_data array 
  1321. * 
  1322. * @param array $form The Gravity Form 
  1323. * @param array $entry The Gravity Form Entry 
  1324. * 
  1325. * @return array The results 
  1326. * 
  1327. * @since 4.0 
  1328. */ 
  1329. public function get_survey_results( $form, $entry ) { 
  1330.  
  1331. $data = []; 
  1332.  
  1333. if ( class_exists( 'GFSurvey' ) && $this->check_field_exists( 'survey', $form ) ) { 
  1334.  
  1335. /** Get survey fields */ 
  1336. $fields = GFCommon::get_fields_by_type( $form, [ 'survey' ] ); 
  1337.  
  1338. /** Include the survey score, if any */ 
  1339. if ( isset( $lead['gsurvey_score'] ) ) { 
  1340. $data['survey']['score'] = $lead['gsurvey_score']; 
  1341.  
  1342. $results = $this->get_addon_global_data( $form, [], $fields ); 
  1343.  
  1344. /** Loop through the global survey data and convert information correctly */ 
  1345. foreach ( $fields as $field ) { 
  1346.  
  1347. /** Check if we have a multifield likert and replace the row key */ 
  1348. if ( isset( $field['gsurveyLikertEnableMultipleRows'] ) && $field['gsurveyLikertEnableMultipleRows'] == 1 ) { 
  1349.  
  1350. foreach ( $field['gsurveyLikertRows'] as $row ) { 
  1351.  
  1352. $results['field_data'][ $field->id ] = $this->replace_key( $results['field_data'][ $field->id ], $row['value'], $row['text'] ); 
  1353.  
  1354. if ( isset( $field->choices ) && is_array( $field->choices ) ) { 
  1355. foreach ( $field->choices as $choice ) { 
  1356. $results['field_data'][ $field->id ][ $row['text'] ] = $this->replace_key( $results['field_data'][ $field->id ][ $row['text'] ], $choice['value'], $choice['text'] ); 
  1357.  
  1358. /** Replace the standard row data */ 
  1359. if ( isset( $field->choices ) && is_array( $field->choices ) ) { 
  1360. foreach ( $field->choices as $choice ) { 
  1361. $results['field_data'][ $field->id ] = $this->replace_key( $results['field_data'][ $field->id ], $choice['value'], $choice['text'] ); 
  1362.  
  1363. $data['survey']['global'] = $results; 
  1364.  
  1365. return $data; 
  1366.  
  1367. /** 
  1368. * Pull the Quiz Results into the $form_data array 
  1369. * 
  1370. * @param array $form The Gravity Form 
  1371. * @param array $entry The Gravity Form Entry 
  1372. * 
  1373. * @return array The results 
  1374. * 
  1375. * @since 4.0 
  1376. */ 
  1377. public function get_quiz_results( $form, $entry ) { 
  1378.  
  1379. $data = []; 
  1380.  
  1381. if ( class_exists( 'GFQuiz' ) && $this->check_field_exists( 'quiz', $form ) ) { 
  1382.  
  1383. /** Get quiz fields */ 
  1384. $fields = GFCommon::get_fields_by_type( $form, [ 'quiz' ] ); 
  1385.  
  1386. /** Store the quiz pass configuration */ 
  1387. $data['quiz']['config']['grading'] = ( isset( $form['gravityformsquiz']['grading'] ) ) ? $form['gravityformsquiz']['grading'] : ''; 
  1388. $data['quiz']['config']['passPercent'] = ( isset( $form['gravityformsquiz']['passPercent'] ) ) ? $form['gravityformsquiz']['passPercent'] : ''; 
  1389. $data['quiz']['config']['grades'] = ( isset( $form['gravityformsquiz']['grades'] ) ) ? $form['gravityformsquiz']['grades'] : ''; 
  1390.  
  1391. /** Store the user's quiz results */ 
  1392. $data['quiz']['results']['score'] = rgar( $entry, 'gquiz_score' ); 
  1393. $data['quiz']['results']['percent'] = rgar( $entry, 'gquiz_percent' ); 
  1394. $data['quiz']['results']['is_pass'] = rgar( $entry, 'gquiz_is_pass' ); 
  1395. $data['quiz']['results']['grade'] = rgar( $entry, 'gquiz_grade' ); 
  1396.  
  1397. /** Poll for the global quiz overall results */ 
  1398. $data['quiz']['global'] = $this->get_quiz_overall_data( $form, $fields ); 
  1399.  
  1400.  
  1401. return $data; 
  1402.  
  1403. /** 
  1404. * Pull the Poll Results into the $form_data array 
  1405. * 
  1406. * @param array $form The Gravity Form 
  1407. * @param array $entry The Gravity Form Entry 
  1408. * 
  1409. * @return array The results 
  1410. * 
  1411. * @since 4.0 
  1412. */ 
  1413. public function get_poll_results( $form, $entry ) { 
  1414.  
  1415. $data = []; 
  1416.  
  1417. if ( class_exists( 'GFPolls' ) && $this->check_field_exists( 'poll', $form ) ) { 
  1418.  
  1419. /** Get poll fields and the overall results */ 
  1420. $fields = GFCommon::get_fields_by_type( $form, [ 'poll' ] ); 
  1421. $results = $this->get_addon_global_data( $form, [], $fields ); 
  1422.  
  1423. /** Loop through our fields and update the results as needed */ 
  1424. foreach ( $fields as $field ) { 
  1425.  
  1426. /** Add the field name to a new 'misc' array key */ 
  1427. $results['field_data'][ $field->id ]['misc']['label'] = $field->label; 
  1428.  
  1429. /** Loop through the field choices */ 
  1430. foreach ( $field->choices as $choice ) { 
  1431. $results['field_data'][ $field->id ] = $this->replace_key( $results['field_data'][ $field->id ], $choice['value'], $choice['text'] ); 
  1432.  
  1433. $data['poll']['global'] = $results; 
  1434.  
  1435. return $data; 
  1436.  
  1437. /** 
  1438. * Parse the Quiz Overall Results 
  1439. * 
  1440. * @param array $form The Gravity Form 
  1441. * @param array $fields The quiz fields 
  1442. * 
  1443. * @return array The parsed results 
  1444. * 
  1445. * @since 4.0 
  1446. */ 
  1447. public function get_quiz_overall_data( $form, $fields ) { 
  1448.  
  1449. if ( ! class_exists( 'GFQuiz' ) ) { 
  1450. return []; 
  1451.  
  1452. /** GFQuiz is a singleton. Get the instance */ 
  1453. $quiz = GFQuiz::get_instance(); 
  1454.  
  1455. /** Create our callback to add additional data to the array specific to the quiz plugin */ 
  1456. $options['callbacks']['calculation'] = [ 
  1457. $quiz,  
  1458. 'results_calculation',  
  1459. ]; 
  1460.  
  1461. $results = $this->get_addon_global_data( $form, $options, $fields ); 
  1462.  
  1463. /** Loop through our fields and update our global results */ 
  1464. foreach ( $fields as $field ) { 
  1465.  
  1466. /** Replace ['totals'] key with ['misc'] key */ 
  1467. $results['field_data'][ $field->id ] = $this->replace_key( $results['field_data'][ $field->id ], 'totals', 'misc' ); 
  1468.  
  1469. /** Add the field name to the ['misc'] key */ 
  1470. $results['field_data'][ $field->id ]['misc']['label'] = $field->label; 
  1471.  
  1472. /** Loop through the field choices */ 
  1473. if ( is_array( $field->choices ) ) { 
  1474. foreach ( $field->choices as $choice ) { 
  1475. $results['field_data'][ $field->id ] = $this->replace_key( $results['field_data'][ $field->id ], $choice['value'], $choice['text'] ); 
  1476.  
  1477. /** Check if this is the correct field */ 
  1478. if ( isset( $choice['gquizIsCorrect'] ) && $choice['gquizIsCorrect'] == 1 ) { 
  1479. $results['field_data'][ $field->id ]['misc']['correct_option_name'][] = esc_html( $choice['text'] ); 
  1480.  
  1481. return $results; 
  1482.  
  1483. /** 
  1484. * Pull Gravity Forms global results Data 
  1485. * 
  1486. * @param array $form The Gravity Form array 
  1487. * @param array $options The global query options 
  1488. * @param array $fields The field array to use in our query 
  1489. * 
  1490. * @return array The results 
  1491. * 
  1492. * @since 4.0 
  1493. */ 
  1494. private function get_addon_global_data( $form, $options, $fields ) { 
  1495. /** If the results class isn't loaded, load it */ 
  1496. if ( ! class_exists( 'GFResults' ) ) { 
  1497. require_once( GFCommon::get_base_path() . '/includes/addon/class-gf-results.php' ); 
  1498.  
  1499. $form_id = $form['id']; 
  1500.  
  1501. /** Add form filter to keep in line with GF standard */ 
  1502. $form = apply_filters( 'gform_form_pre_results', $form ); 
  1503. $form = apply_filters( 'gform_form_pre_results_' . $form_id, $form ); 
  1504.  
  1505. /** Initiate the results class */ 
  1506. $gf_results = new GFResults( '', $options ); 
  1507.  
  1508. /** Ensure that only active leads are queried */ 
  1509. $search = [ 
  1510. 'field_filters' => [ 'mode' => '' ],  
  1511. 'status' => 'active',  
  1512. ]; 
  1513.  
  1514. /** Get the results */ 
  1515. $data = $gf_results->get_results_data( $form, $fields, $search ); 
  1516.  
  1517. /** Unset some array keys we don't need */ 
  1518. unset( $data['status'] ); 
  1519. unset( $data['timestamp'] ); 
  1520.  
  1521. return $data; 
  1522.  
  1523. /** 
  1524. * Sniff the form fields and determine if there are any of the $type available 
  1525. * 
  1526. * @param string $type the field type we are looking for 
  1527. * @param array $form the form array 
  1528. * 
  1529. * @return boolean Whether there is a match or not 
  1530. * 
  1531. * @since 4.0 
  1532. */ 
  1533. public function check_field_exists( $type, $form ) { 
  1534.  
  1535. if ( isset( $form['fields'] ) ) { 
  1536. foreach ( $form['fields'] as $field ) { 
  1537. if ( $field['type'] == $type ) { 
  1538. return true; 
  1539.  
  1540. return false; 
  1541.  
  1542. /** 
  1543. * Swap out the array key 
  1544. * 
  1545. * @param array $array The array to be modified 
  1546. * @param string $key The key to remove 
  1547. * @param string $replacement_key The new array key 
  1548. * 
  1549. * @return array The modified array 
  1550. * 
  1551. * @since 4.0 
  1552. */ 
  1553. public function replace_key( $array, $key, $replacement_key ) { 
  1554. if ( $key !== $replacement_key && isset( $array[ $key ] ) ) { 
  1555.  
  1556. /** Replace the array key with the actual field name */ 
  1557. $array[ $replacement_key ] = $array[ $key ]; 
  1558. unset( $array[ $key ] ); 
  1559.  
  1560. return $array; 
  1561.  
  1562. /** 
  1563. * Pass in a Gravity Form Field Object and get back a Gravity PDF Field Object 
  1564. * 
  1565. * @param object $field Gravity Form Field Object 
  1566. * @param array $form The Gravity Form Array 
  1567. * @param array $entry The Gravity Form Entry 
  1568. * @param \GFPDF\Helper\Fields\Field_Products $products A Field_Products Object 
  1569. * 
  1570. * @return object Gravity PDF Field Object 
  1571. * 
  1572. * @since 4.0 
  1573. */ 
  1574. public function get_field_class( $field, $form, $entry, Field_Products $products ) { 
  1575.  
  1576. $class_name = $this->misc->get_field_class( $field->type ); 
  1577.  
  1578. try { 
  1579. /** if we have a valid class name... */ 
  1580. if ( class_exists( $class_name ) ) { 
  1581.  
  1582. /** 
  1583. * Developer Note 
  1584. * 
  1585. * We've purposefully not added any filters to the Field_* child classes directly. 
  1586. * Instead, if you want to change how one of the fields are displayed or output (without effecting Gravity Forms itself) you should tap 
  1587. * into one of the filters below and override or extend the entire class. 
  1588. * 
  1589. * Your class MUST extend the \GFPDF\Helper\Helper_Abstract_Fields abstract class - either directly or by extending an existing \GFPDF\Helper\Fields class. 
  1590. * eg. class Fields_New_Text extends \GFPDF\Helper\Helper_Abstract_Fields or Fields_New_Text extends \GFPDF\Helper\Fields\Field_Text 
  1591. * 
  1592. * To make your life more simple you should either use the same namespace as the field classes (\GFPDF\Helper\Fields) or import the class directly (use \GFPDF\Helper\Fields\Field_Text) 
  1593. * We've tried to make the fields as modular as possible. If you have any feedback about this approach please submit a ticket on GitHub (https://github.com/GravityPDF/gravity-pdf/issues) 
  1594. */ 
  1595. if ( GFCommon::is_product_field( $field->type ) ) { 
  1596.  
  1597. /** Product fields are handled through a single function */ 
  1598. $product = new Field_Product( $field, $entry, $this->gform, $this->misc ); 
  1599. $product->set_products( $products ); 
  1600.  
  1601. $class = apply_filters( 'gfpdf_field_product_class', $product, $field, $entry, $form ); 
  1602. } else { 
  1603. /** 
  1604. * Load the selected class 
  1605. * See https://gravitypdf.com/documentation/v4/gfpdf_field_class/ for more details about these filters 
  1606. */ 
  1607. $class = apply_filters( 'gfpdf_field_class', new $class_name( $field, $entry, $this->gform, $this->misc ), $field, $entry, $form ); 
  1608. $class = apply_filters( 'gfpdf_field_class_' . $field->type, $class, $field, $entry, $form ); 
  1609.  
  1610. if ( empty( $class ) || ! ( $class instanceof Helper_Abstract_Fields ) ) { 
  1611. throw new Exception( 'Class not found' ); 
  1612. } catch ( Exception $e ) { 
  1613.  
  1614. $this->log->addError( 'Invalid Field Class.', [ 
  1615. 'exception' => $e->getMessage(),  
  1616. 'field' => $field,  
  1617. 'form_id' => $form['id'],  
  1618. 'entry' => $entry,  
  1619. ] ); 
  1620.  
  1621. /** Exception thrown. Load generic field loader */ 
  1622. $class = apply_filters( 'gfpdf_field_default_class', new Field_Default( $field, $entry, $this->gform, $this->misc ), $field, $entry, $form ); 
  1623.  
  1624. return $class; 
  1625.  
  1626. /** 
  1627. * Attempts to find a configuration which matches the legacy routing method 
  1628. * 
  1629. * @param array $config 
  1630. * 
  1631. * @return mixed 
  1632. * 
  1633. * @since 4.0 
  1634. */ 
  1635. public function get_legacy_config( $config ) { 
  1636.  
  1637. /** Get the form settings */ 
  1638. $pdfs = $this->options->get_form_pdfs( $config['fid'] ); 
  1639.  
  1640. if ( is_wp_error( $pdfs ) ) { 
  1641. return $pdfs; 
  1642.  
  1643. /** Reindex the $pdfs keys */ 
  1644. $pdfs = array_values( $pdfs ); 
  1645.  
  1646. /** Use the legacy aid to determine which PDF to load */ 
  1647. if ( $config['aid'] !== false ) { 
  1648. $selector = $config['aid'] - 1; 
  1649.  
  1650. if ( isset( $pdfs[ $selector ] ) && $pdfs[ $selector ]['template'] == $config['template'] ) { 
  1651. return $pdfs[ $selector ]['id']; 
  1652.  
  1653. /** The aid method failed so lets load the first matching configuration */ 
  1654. foreach ( $pdfs as $pdf ) { 
  1655. if ( $pdf['active'] === true && $pdf['template'] == $config['template'] ) { 
  1656. return $pdf['id']; 
  1657.  
  1658. return new WP_Error( 'pdf_configuration_error', esc_html__( 'Could not find PDF configuration requested', 'gravity-forms-pdf-extended' ) ); 
  1659.  
  1660. /** 
  1661. * Do any preprocessing to our arguments before they are sent to the template 
  1662. * 
  1663. * @param array $args 
  1664. * 
  1665. * @return array 
  1666. * 
  1667. * @since 4.0 
  1668. */ 
  1669. public function preprocess_template_arguments( $args ) { 
  1670.  
  1671. if ( isset( $args['settings']['header'] ) ) { 
  1672. $args['settings']['header'] = $this->gform->process_tags( $args['settings']['header'], $args['form'], $args['entry'] ); 
  1673. $args['settings']['header'] = $this->misc->fix_header_footer( $args['settings']['header'] ); 
  1674.  
  1675. if ( isset( $args['settings']['first_header'] ) ) { 
  1676. $args['settings']['first_header'] = $this->gform->process_tags( $args['settings']['first_header'], $args['form'], $args['entry'] ); 
  1677. $args['settings']['first_header'] = $this->misc->fix_header_footer( $args['settings']['first_header'] ); 
  1678.  
  1679. if ( isset( $args['settings']['footer'] ) ) { 
  1680. $args['settings']['footer'] = $this->gform->process_tags( $args['settings']['footer'], $args['form'], $args['entry'] ); 
  1681. $args['settings']['footer'] = $this->misc->fix_header_footer( $args['settings']['footer'] ); 
  1682.  
  1683. if ( isset( $args['settings']['first_footer'] ) ) { 
  1684. $args['settings']['first_footer'] = $this->gform->process_tags( $args['settings']['first_footer'], $args['form'], $args['entry'] ); 
  1685. $args['settings']['first_footer'] = $this->misc->fix_header_footer( $args['settings']['first_footer'] ); 
  1686.  
  1687. return $args; 
.