Changeset 2845715
- Timestamp:
- 01/09/2023 08:40:53 PM (3 years ago)
- Location:
- constellation-client-portal/trunk
- Files:
-
- 11 edited
-
README.txt (modified) (2 diffs)
-
admin/class-ars-constellation-client-portal-company.php (modified) (12 diffs)
-
admin/class-ars-constellation-client-portal-file.php (modified) (1 diff)
-
admin/css/ars-constellation-client-portal-admin.css (modified) (3 diffs)
-
admin/includes/accp-settings-page.php (modified) (3 diffs)
-
admin/js/ars-constellation-client-portal-admin.js (modified) (2 diffs)
-
ars-constellation-client-portal.php (modified) (2 diffs)
-
includes/class-ars-constellation-client-portal-deactivator.php (modified) (1 diff)
-
includes/class-ars-constellation-client-portal-utility-functions.php (modified) (1 diff)
-
includes/class-ars-constellation-client-portal.php (modified) (1 diff)
-
public/class-ars-constellation-client-portal-public.php (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
constellation-client-portal/trunk/README.txt
r2798490 r2845715 4 4 Tags: client portal, customer portal, business portal, client management, private files, private pages, invoice clients, invoicing, business managemenet 5 5 Requires at least: 5.0.0 6 Tested up to: 6. 06 Tested up to: 6.1 7 7 Stable tag: 1.5.0 8 8 Requires PHP: 7.4 … … 196 196 197 197 == Changelog == 198 = 1.5.2 (Pro) - 2022-13-10 = 198 = 1.5.3 (Pro) - 2023-1-9 = 199 * Update: Updated the deactivate function to clear the accp_automated_email_cron cron job if it is scheduled on plugin deactivation. 200 * Fix: Fixed notice relating to undefined company_id variable in class-ars-constellation-client-portal-pro-public.php. 201 * Fix: Fixed issue that prevented Published, Drafts, and Trash folders from displaying in File and Invoice posts lists. 202 * Feature: Added new functionality that allows file and invoice posts to be created in bulk via a CSV import. 203 * Improvement: Extended login and WP capability checks for admin settings pages. 204 * Improvement: Updated the note metabox functionality to only display the note form after the post has been saved to prevent confusion. 205 * Update: Improved the Send Reminder admin list button UI to wrap at smaller window widths. 206 * Improvement: Added functionality to display a product_total column within invoice CSV exports. 207 * Update: Relocated accp_save_invoice_cart_item_meta_as_order_item_meta, accp_change_invoice_status_on_woocommerce_payment_complete, accp_change_invoice_status_on_woocommerce_order_status_complete, and accp_change_invoice_status_to_completed functions and hooks from public to admin files. 208 * Improvement - Added functionality to add the Woocommerce order number as attribution within invoice notes when they are automatically marked as paid by Woocommerce. 209 210 = 1.5.3 (Core) - 2023-1-9 = 211 * Update: Made updates to the admin settings upgrade tab. 212 * Improvement: Added new link_title file and invoice shortcode attribute that removes the link from post titles in the loop if the attribute is set to nolink. 213 * Improvement: Added a role select field to the new primary user create form in the Company UI and updated the AJAX function to assign the role on user creation. 214 * Improvement: Added a "Company ID" column the the Company WP list table for easy access to the Company/post ID.s 215 * Improvement: Extended login and WP capability checks for admin settings pages. 216 217 = 1.5.2 (Pro) - 2022-10-13 = 199 218 * All Core 1.5.2 updates. 200 219 201 = 1.5.2 (Core) - 2022-1 3-10=220 = 1.5.2 (Core) - 2022-10-13 = 202 221 * Update: Moved the Select2 script related to the client-add-company-select user field to the core admin js file from the pro admin js file so that the Select2 UI works as expected in the core plugin. 203 222 * Update: Updated the functionality that checks for saved additional assigned companies in the extra_user_profile_fields function to ensure that an array is returned if no companies are assigned, to prevent errors on user edit pages in PHP 8. -
constellation-client-portal/trunk/admin/class-ars-constellation-client-portal-company.php
r2798490 r2845715 119 119 if ( 'title' === $column_name ) { 120 120 121 $new_columns['company_id'] = __( 'Company ID', 'accp_company' ); 121 122 $new_columns['company_status'] = __( 'Status', 'accp_company' ); 122 123 $new_columns['company_primary_user'] = __( 'Primary User', 'accp_company' ); … … 129 130 130 131 return $new_columns; 132 } 133 134 /** 135 * Display the Company ID column content - Client Company WP List Table - Company. 136 */ 137 public function clientcompany_column_display_company_id( $column_name, $post_id ) { 138 139 if ( 'company_id' != $column_name ) 140 return; 141 142 $company_id = get_the_ID() ? get_the_ID() : ''; 143 144 echo (int)$company_id; 145 131 146 } 132 147 … … 263 278 264 279 add_meta_box("company-home-page", "Company Home Page", array($this, "display_home_page_meta_options"), $screen, "side"); 265 add_meta_box("company-upload-dir", "Company Upload Directory", array($this, "display_company_upload_meta_options"), $screen, "normal", "high"); 266 add_meta_box("company-primary-user", "Company Primary User", array($this, "display_company_primary_user_meta_options"), $screen, "normal", "high"); 280 add_meta_box("company-upload-dir", "Company Upload Directory", array($this, "display_company_upload_meta_options"), $screen, "normal", "high"); 281 add_meta_box("company-primary-user", "Company Primary User", array($this, "display_company_primary_user_meta_options"), $screen, "normal", "high"); 267 282 add_meta_box("company-users", "Company Users", array($this, "display_company_user_meta_options"), $screen, "normal", "high"); 268 283 … … 573 588 574 589 <option value="">Select user</option> 575 <?php590 <?php 576 591 577 592 foreach($users as $user){ … … 583 598 584 599 ?> 585 <option value="<?php echo $user_id; ?>" <?php echo ($saved_primary_user && $saved_primary_user === $user_id) ? "selected" : ""; ?>><?php echo $user_name; ?> <?php echo $user_first_name ? ' - ' . $user_first_name : ''; ?> <?php echo $user_last_name ? ' ' . $user_last_name: ''; ?></option>600 <option value="<?php echo esc_attr($user_id); ?>" <?php echo ($saved_primary_user && $saved_primary_user === $user_id) ? "selected" : ""; ?>><?php echo esc_html($user_name); ?> <?php echo $user_first_name ? ' - ' . esc_html($user_first_name) : ''; ?> <?php echo $user_last_name ? ' ' . esc_html($user_last_name) : ''; ?></option> 586 601 <?php 587 602 … … 659 674 <span class="accp-new-user-field"> 660 675 676 <label class="accp-set-width-label-85">Role<abbr>*</abbr></label> 677 <select id="accp-new-user-role" name="accp_new_user_role"> 678 <?php wp_dropdown_roles( $selected = 'subscriber' ); ?> 679 </select> 680 681 </span> 682 683 <span class="accp-new-user-field"> 684 661 685 <label class="accp-set-width-label-85">Email Addres<abbr>*</abbr></label> 662 686 <input id="accp-new-user-email" class="accp-create-user-text-field" name="accp_new_user_email" type="email" value=""> … … 1043 1067 die(); 1044 1068 1045 if( !is_admin() || !current_user_can('manage_options') )1069 if( !is_admin() || !current_user_can('manage_options') || !is_user_logged_in() ) 1046 1070 die(); 1047 1071 1048 if( !isset($_POST['username']) || !isset($_POST['email']) || !isset($_POST['password']) || !isset($_POST['company_id']) )1072 if( !isset($_POST['username']) || !isset($_POST['email']) || !isset($_POST['password']) || !isset($_POST['company_id']) || !isset($_POST['role']) ) 1049 1073 die(); 1050 1074 … … 1061 1085 1062 1086 $username = sanitize_user($_POST['username']); 1087 $role = filter_var($_POST['role'], FILTER_SANITIZE_STRING); 1063 1088 $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); 1064 1089 $password = $_POST['password']; // sanitized within wp_insert_user in wp_create_user. … … 1076 1101 } 1077 1102 1103 if( !$role || empty($role) ){ 1104 1105 echo 'You must select a role for the new user.'; 1106 1107 wp_die(); 1108 1109 } 1110 1078 1111 if( !$company_id || empty($company_id) ){ 1079 1112 … … 1100 1133 * Add the new user. 1101 1134 */ 1102 $new_user_id = wp_create_user( $username, $password, $email); 1135 $user_data = array( 1136 'user_login' => $username, 1137 'user_pass' => $password, 1138 'user_email' => $email, 1139 'role' => $role 1140 ); 1141 1142 /** 1143 * Add the firt and last name to the 1144 * user data if the values are set. 1145 */ 1146 if($firstname && !empty($firstname)){ 1147 $user_data['first_name'] = $firstname; 1148 } 1149 1150 if($lastname && !empty($lastname)){ 1151 $user_data['last_name'] = $lastname; 1152 } 1153 1154 $new_user_id = wp_insert_user($user_data); 1155 1103 1156 1104 1157 /** … … 1123 1176 1124 1177 } 1178 1125 1179 1126 1180 /** … … 1134 1188 */ 1135 1189 update_user_meta($new_user_id, 'client_status', 'active'); 1136 1137 1138 /**1139 * Add the first and last names if present.1140 */1141 wp_update_user([1142 'ID' => (int)$new_user_id,1143 'first_name' => esc_html($firstname),1144 'last_name' => esc_html($lastname),1145 ]);1146 1190 1147 1191 -
constellation-client-portal/trunk/admin/class-ars-constellation-client-portal-file.php
r2773559 r2845715 436 436 437 437 $default_statuses = array( 438 'na' => 'NA',439 'incomplete' => 'Incomplete',440 'in-progress' => 'In Progress',438 'na' => 'NA', 439 'incomplete' => 'Incomplete', 440 'in-progress' => 'In Progress', 441 441 'completed' => 'Completed' 442 442 ); -
constellation-client-portal/trunk/admin/css/ars-constellation-client-portal-admin.css
r2707099 r2845715 140 140 } 141 141 142 .accp-upgrade-button{ 143 background-color: orange; 144 display: block; 142 .accp-upgrade-tab-banner { 143 background-color: #005be0; 144 color: #FFF; 145 padding: 30px; 146 margin-top: 17px; 147 margin-bottom: 20px 148 } 149 150 .accp-upgrade-tab-banner h2 { 151 color: #fff; 152 font-size: 40px; 153 font-weight: normal; 154 } 155 156 .accp-upgrade-tab-banner p { 157 font-size: 18px; 158 font-style: italic; 159 } 160 161 .accp-upgrade-feature-list-heading{ 162 color: #005be0; 163 font-size: 24px; 164 font-weight: normal; 165 margin-left: 20px; 166 } 167 168 .accp-upgrade-tab-banner a { 145 169 color: #fff; 146 170 text-decoration: none; 171 display: block; 172 margin-bottom: 10px; 173 text-align: right; 174 font-size: 22px; 175 } 176 177 .accp-upgrade-tab-banner a::after { 178 content: '>'; 179 font-weight: bold; 180 font-size: 22px; 181 margin-left: 5px; 182 } 183 184 .accp-upgrade-button { 185 background-color: orange; 186 display: block; 187 text-decoration: none; 147 188 text-align: center; 148 font-size: 2 0px;149 padding: 1 2px;150 width: 95px;189 font-size: 26px; 190 padding: 18px; 191 width: 126px; 151 192 margin-top: 15px; 152 193 border-radius: 2px; 194 color: #fff; 195 border: 3px solid; 196 margin-left: auto; 197 margin-right: auto; 198 text-transform: uppercase; 199 border-radius: 5px; 200 margin-bottom: 30px; 201 margin-top: 35px; 202 } 203 204 .accp-upgrade-button:hover{ 205 background-color: #ffa500e0; 153 206 } 154 207 … … 262 315 padding-right: 25px; 263 316 margin-bottom: 25px; 264 border: 1px solid #cccccc; 317 box-shadow: 2px 2px 7px rgba(0,0,0, 0.1); 318 } 319 320 .accp-settings-section h2 { 321 color: #005be0; 322 font-weight: normal; 323 font-size: 22px; 265 324 } 266 325 … … 501 560 font-weight: 600; 502 561 } 562 563 .accp-upgrade-pro-feature-item-container{ 564 box-shadow: 2px 2px 7px rgba(0,0,0, 0.1); 565 border-radius: 3px; 566 padding: 25px; 567 margin: 20px; 568 } 569 570 .accp-upgrade-pro-feature-item-container:hover { 571 background-color: #005be0; 572 } 573 574 .accp-upgrade-pro-feature-content-container{ 575 position: relative; 576 } 577 578 .accp-hover-upgrade-button{ 579 display: none; 580 color: #fff; 581 font-size: 20px; 582 text-transform: uppercase; 583 border: 2px solid; 584 text-align: center; 585 max-width: 90%; 586 padding-top: 7px; 587 padding-bottom: 9px; 588 width: 220px; 589 position: absolute; 590 top: 40%; 591 } 592 593 .accp-upgrade-pro-feature-item-container:hover > a .accp-upgrade-pro-feature-content-container{ 594 display: flex; 595 justify-content: center; 596 } 597 598 .accp-upgrade-pro-feature-item-container:hover > a .accp-upgrade-pro-feature-item-title { 599 color: #fff; 600 } 601 602 .accp-upgrade-pro-feature-item-container:hover > a p{ 603 visibility: hidden; 604 } 605 606 .accp-upgrade-pro-feature-item-container:hover > a .accp-hover-upgrade-button{ 607 display: block; 608 } 609 610 .accp-upgrade-pro-feature-item-title { 611 font-size: 21px; 612 line-height: 27px; 613 color: #005be0; 614 } 615 616 .accp-upgrade-pro-feature-item-container p { 617 color: #777; 618 font-size: 14px; 619 } 620 621 .accp-upgrade-pro-feature-item-container a { 622 text-decoration: none; 623 width: 100%; 624 height: 100%; 625 display: block; 626 } 627 628 @media screen and (min-width: 980px) { 629 630 .accp-upgrade-pro-feature-short-list { 631 display: grid; 632 grid-template-columns: auto auto auto; 633 } 634 635 } -
constellation-client-portal/trunk/admin/includes/accp-settings-page.php
r2697216 r2845715 8 8 } 9 9 10 if(!is_user_logged_in() || !current_user_can('manage_options') || !is_admin()) 11 exit; 12 10 13 $accp_utility_functions = new ACCP_Utility_Functions(); 11 14 ?> … … 292 295 <div class="accp-settings-tab" data-tab-id="5"> 293 296 294 <div class="accp-settings-section"> 295 296 <h2>Upgrade to Pro</h2> 297 298 <p>Upgrade to Constellation Client Portal Pro and unlock a wealth of new functionality for your site.</p> 297 <div> 298 299 <div class="accp-upgrade-tab-banner"> 300 301 <h2>Upgrade to Pro</h2> 302 303 <p>Upgrade to Constellation Client Portal Pro today and unlock a wealth of new functionality for your site.</p> 304 305 <a href="https://adrianrodriguezstudios.com/constellation-client-portal/?utm_source=accp-upgrade-tab" target="_blank">Learn More</a> 306 307 </div> 308 309 <a href="https://adrianrodriguezstudios.com/constellation-client-portal/?utm_source=accp-upgrade-tab" target="_blank" class="accp-upgrade-button" title="Get Pro Now">Get Pro</a> 310 311 <h2 class="accp-upgrade-feature-list-heading">Pro Features</h2> 312 313 <?php echo wp_kses_post(accp_generate_upgrade_feature_list_html()); ?> 299 314 300 <p>Visit the <a href="https://adrianrodriguezstudios.com/constellation-client-portal/?utm_source=accp-upgrade-tab" target="_blank">Pro plugin page</a> for more information.</p>301 302 <a href="https://adrianrodriguezstudios.com/constellation-client-portal/?utm_source=accp-upgrade-tab" target="_blank" class="accp-upgrade-button button-primary" title="Get Pro Now">Get Pro</a>303 315 304 316 <div style="clear: both;"></div> … … 325 337 326 338 </div> 339 340 341 <?php 342 /** 343 * Generate the Pro feature list for the upgrade tab. 344 */ 345 function accp_generate_upgrade_feature_list_html(){ 346 347 if(!is_admin() || !is_user_logged_in() || !current_user_can('manage_options')) 348 return; 349 350 $feature_list = array( 351 array( 352 'title' => 'Accept Payments for Invoices', 353 'content' => 'Integrate with WooCommerce and accept payments for invoices.' 354 ), 355 array( 356 'title' => 'Send Automated Email Notifications', 357 'content' => 'Automatically send new post and reminder notification emails to clients.' 358 ), 359 array( 360 'title' => 'Speed Up File and Invoice Creation', 361 'content' => 'Bulk create file and invoice posts by importing CSV files.' 362 ), 363 array( 364 'title' => 'Improve Reporting', 365 'content' => 'Export file and invoice lists to CSV for improved reporting and integration with other processes in your operation, such as accounting and bookkeeping.' 366 ), 367 array( 368 'title' => 'Improve Tracking and Communication', 369 'content' => 'Add internal notes to file and invoice posts for better issue tracking and communication.' 370 ), 371 array( 372 'title' => 'Improve Access Restrictions', 373 'content' => 'Fine tune access restrictions by user, role, and category for more granular control your of content.' 374 ), 375 ); 376 377 $html = ''; 378 379 $html .= '<div class="accp-upgrade-pro-feature-short-list">'; 380 381 foreach($feature_list as $feature){ 382 383 $title = $feature['title']; 384 $content = $feature['content']; 385 386 $html .= '<div class="accp-upgrade-pro-feature-item-container">'; 387 388 $html .= '<a href="https://adrianrodriguezstudios.com/constellation-client-portal/?utm_source=accp-upgrade-tab" target="_blank">'; 389 390 $html .= '<span class="accp-upgrade-pro-feature-item-title">'.wp_kses_post($title).'</span>'; 391 392 $html .= '<div class="accp-upgrade-pro-feature-content-container">'; 393 394 $html .= '<p>'.wp_kses_post($content).'</p>'; 395 396 $html .= '<span class="accp-hover-upgrade-button">Get Pro</span>'; 397 398 $html .= '</div>'; 399 400 $html .= '</a>'; 401 402 $html .= '</div>'; 403 404 } 405 406 $html .= '</div>'; 407 408 return $html; 409 410 } 411 ?> -
constellation-client-portal/trunk/admin/js/ars-constellation-client-portal-admin.js
r2798490 r2845715 484 484 var spinner_elem = '<span class="accp-create-user-spinner spinner" style="float: none;"></span>'; 485 485 var username = $('#accp-new-user-username').val(); 486 var role = $('#accp-new-user-role').val(); 486 487 var email = $('#accp-new-user-email').val(); 487 488 var password = $('#accp-new-user-password').val(); … … 510 511 nonce: nonce, 511 512 username: username, 513 role: role, 512 514 email: email, 513 515 password: password, -
constellation-client-portal/trunk/ars-constellation-client-portal.php
r2798490 r2845715 5 5 * Plugin URI: https://adrianrodriguezstudios.com/constellation-client-portal/ 6 6 * Description: Create private pages for each of your clients, post private files, and protect your client files from unauthorized users and search engines. <strong>Important:</strong> All Site-level File Protection features will cease to function if the plugin is disabled or uninstalled. 7 * Version: 1.5. 27 * Version: 1.5.3 8 8 * Author: ARS 9 9 * Author URI: https://adrianrodriguezstudios.com … … 41 41 */ 42 42 define('ACCP_PLUGIN_NAME', 'ARS_CONSTELLATION_CLIENT_PORTAL'); 43 define('ACCP_PLUGIN_VERSION', '1.5. 2'); // Change the version in the header as well.43 define('ACCP_PLUGIN_VERSION', '1.5.3'); // Change the version in the header as well. 44 44 define( ACCP_PLUGIN_NAME, ACCP_PLUGIN_VERSION ); 45 45 -
constellation-client-portal/trunk/includes/class-ars-constellation-client-portal-deactivator.php
r2773559 r2845715 38 38 } 39 39 40 41 /** 42 * Clear the automated email cron job if no 43 * automated emails are enabled. 44 */ 45 if ( wp_next_scheduled('accp_automated_email_cron') ) { 46 wp_clear_scheduled_hook( 'accp_automated_email_cron' ); 47 } 48 40 49 } 41 50 -
constellation-client-portal/trunk/includes/class-ars-constellation-client-portal-utility-functions.php
r2689088 r2845715 136 136 * Example return: '/var/www/html/wp-content/uploads/accp-clientfiles'. 137 137 */ 138 function accp_get_clientfiles_path(){ 138 function accp_get_clientfiles_path(){ 139 139 140 140 $clientfiles_dir_name = '/accp-clientfiles'; -
constellation-client-portal/trunk/includes/class-ars-constellation-client-portal.php
r2773559 r2845715 390 390 $this->loader->add_action( 'manage_accp_clientcompany_posts_custom_column', $plugin_company, 'clientcompany_column_display_status', 10, 2 ); 391 391 $this->loader->add_action( 'manage_accp_clientcompany_posts_custom_column', $plugin_company, 'clientcompany_column_display_primary_user', 10, 2 ); 392 $this->loader->add_action( 'manage_accp_clientcompany_posts_custom_column', $plugin_company, 'clientcompany_column_display_company_id', 10, 2 ); 392 393 393 394 // Client Company - Add custom fields -
constellation-client-portal/trunk/public/class-ars-constellation-client-portal-public.php
r2722149 r2845715 144 144 // Get the Company ID associated with the current user 145 145 $curr_user_company_id = get_user_meta( $user_id, 'client_company', true ); 146 147 // Link title 148 $link_title = ''; 149 150 if( isset($atts['link_title']) && 151 !empty($atts['link_title']) && 152 trim( strtolower($atts['link_title']) ) === 'nolink' 153 ){ 154 155 $link_title = 'nolink'; 156 157 } 146 158 147 159 /** … … 319 331 if($top_item_content){ 320 332 echo wp_kses_post($top_item_content); 321 } 322 333 } 334 335 if($link_title !== 'nolink'): 323 336 ?> 324 325 <span class="accp-file-list-item-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></span> 337 338 <span class="accp-file-list-item-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></span> 339 340 <?php 341 else: 342 ?> 343 344 <span class="accp-file-list-item-title"><?php the_title(); ?></span> 345 346 <?php 347 endif; 348 ?> 326 349 327 350 <div class="accp-list-item-meta"> … … 525 548 'show_post_date' => '', // Display the post date - default is false 526 549 'list_id' => '', // Integer - Useful when there are multiple shortcode lists on the same page - default is null 550 'link_title' => '' // nolink or empty. Used to disable post link titles - default is null. 527 551 ); 528 552 … … 615 639 // Get the Company ID associated with the current user 616 640 $curr_user_company_id = get_user_meta( $user_id, 'client_company', true ); 641 642 // Link title 643 $link_title = ''; 644 645 if( isset($atts['link_title']) && 646 !empty($atts['link_title']) && 647 trim( strtolower($atts['link_title']) ) === 'nolink' 648 ){ 649 650 $link_title = 'nolink'; 651 652 } 617 653 618 654 /** … … 800 836 echo wp_kses_post($top_item_content); 801 837 } 802 838 839 if($link_title !== 'nolink'): 803 840 ?> 804 805 <span class="accp-file-list-item-title"><a href="<?php the_permalink();?>"><?php the_title(); ?></a></span> 841 842 <span class="accp-file-list-item-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></span> 843 844 <?php 845 else: 846 ?> 847 848 <span class="accp-file-list-item-title"><?php the_title(); ?></span> 849 850 <?php 851 endif; 852 ?> 806 853 807 854 <div class="accp-list-item-meta"> … … 1046 1093 'show_post_date' => '', // Display the post date - default is false 1047 1094 'list_id' => '', // Integer - Useful when there are multiple shortcode lists on the same page - default is null 1095 'link_title' => '' // nolink or empty. Used to disable post link titles - default is null. 1048 1096 ); 1049 1097
Note: See TracChangeset
for help on using the changeset viewer.